Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release draft #58

Merged
merged 12 commits into from
Sep 18, 2023
2 changes: 2 additions & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@types/fhir": "^0.0.36",
"dayjs": "^1.11.7",
"html-react-parser": "^3.0.15",
"luxon": "^3.3.0",
"next": "13.2.4",
"prism-react-renderer": "^1.3.5",
"react": "18.2.0",
Expand All @@ -38,6 +39,7 @@
"zod": "^3.21.4"
},
"devDependencies": {
"@types/luxon": "^3.3.0",
"@types/node": "18.15.3",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
Expand Down
140 changes: 140 additions & 0 deletions app/src/components/ReleaseModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { trpc } from '@/util/trpc';
import { ArtifactResourceType } from '@/util/types/fhir';
import { Button, Center, Group, Modal, Stack, TextInput, Text, Tooltip } from '@mantine/core';
import { DateTime } from 'luxon';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { AlertCircle, CircleCheck, InfoCircle } from 'tabler-icons-react';
import { notifications } from '@mantine/notifications';

export interface ReleaseModalProps {
open: boolean;
onClose: () => void;
id: string;
resourceType: ArtifactResourceType;
}

export default function ReleaseModal({ open = true, onClose, id, resourceType }: ReleaseModalProps) {
const router = useRouter();
const [version, setVersion] = useState('');

const { data: resource } = trpc.draft.getDraftById.useQuery({
id: id,
resourceType: resourceType
});
const ctx = trpc.useContext();
const deleteMutation = trpc.draft.deleteDraft.useMutation({
onSuccess: () => {
notifications.show({
title: `Draft ${resource?.resourceType} released!`,
message: `Draft ${resource?.resourceType}/${resource?.id} successfully released to the Publishable Measure Repository!`,
icon: <CircleCheck />,
color: 'green'
});
ctx.draft.getDraftCounts.invalidate();
ctx.draft.getDrafts.invalidate();
},
onError: e => {
console.error(e);
notifications.show({
title: `Release Failed!`,
message: `Attempt to release ${resourceType} failed with message: ${e.message}`,
icon: <AlertCircle />,
color: 'red'
});
}
});

async function confirm() {
// requirements:
// https://build.fhir.org/ig/HL7/cqf-measures/measure-repository-service.html#release
// TODO: release recursively all children (ignore for now).
if (resource) {
resource.version = version;
resource.status = 'active';
resource.date = DateTime.now().toISO() || '';
}

const res = await fetch(`${process.env.NEXT_PUBLIC_MRS_SERVER}/${resourceType}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json+fhir'
},
body: JSON.stringify(resource)
});

// Conditionally remove the base version if one is present
let location = res.headers.get('Location');
if (location?.substring(0, 5) === '4_0_1') {
location = location?.substring(5); // remove 4_0_1 (version)
}

if (res.status !== 201) {
console.error(res.statusText);
notifications.show({
title: `Release Failed!`,
message: `Server unable to process request`,
icon: <AlertCircle />,
color: 'red'
});
} else if (!location) {
console.error('No resource location for released artifact');
notifications.show({
title: `Release Failed!`,
message: `No resource location exists for draft artifact`,
icon: <AlertCircle />,
color: 'red'
});
} else {
// delete draft
deleteMutation.mutate({
resourceType: resourceType,
id: id
});

// direct user to published artifact detail page
router.push(location);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could address this in a follow-up task, but this app is getting pretty slow due to the redirects.
Screenshot below is the logs that I see on the backend. Looks like the data requirements are being calculated automatically, which we expect, but seems like dependent libraries are being found multiple times for the same libraries?
Screenshot 2023-09-14 at 10 45 22 AM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this needs to be looked at. I think this should be done in a separate task since it looks like this is happening when the user just navigates to an artifact on the publishable measure repository, so it is a bit out of scope for this task.

}
onClose();
}

return (
<Modal opened={open} onClose={onClose} withCloseButton={false} size="lg">
<Stack>
<Center>
<Group spacing="xs">
Release {resourceType}/{id}?
<Tooltip
multiline
label="Releasing a draft artifact changes the artifact's status from 'draft' to 'active', adds the user-specified version to the artifact, and sends the artifact to the Publishable Measure Repository. This action also deletes this draft artifact from the Authoring Measure Repository."
>
<div>
<InfoCircle size="1rem" style={{ display: 'block', opacity: 0.5 }} />
</div>
</Tooltip>
</Group>
</Center>
<Text size="xs" fw={700}>
NOTE: By releasing this artifact to the Publishable Measure Repository, this draft instance will be removed.
</Text>
<TextInput
label="Add version"
value={version}
onChange={e => setVersion(e.target.value)}
withAsterisk
description="An artifact must have a version before it can be released to the Publishable Measure Repository"
/>
<Center>
<Group pt={8} position="right">
<Button onClick={confirm} disabled={!version}>
Release
</Button>
<Button variant="default" onClick={onClose}>
Cancel
</Button>
</Group>
</Center>
</Stack>
</Modal>
);
}
65 changes: 42 additions & 23 deletions app/src/pages/authoring/[resourceType]/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { notifications } from '@mantine/notifications';
import { AlertCircle, CircleCheck, InfoCircle } from 'tabler-icons-react';
import { ArtifactResourceType } from '@/util/types/fhir';
import ArtifactFieldInput from '@/components/ArtifactFieldInput';
import ReleaseModal from '@/components/ReleaseModal';

interface DraftArtifactUpdates {
url?: string;
Expand Down Expand Up @@ -36,6 +37,8 @@ export default function ResourceAuthoringPage() {
resourceType: resourceType as ArtifactResourceType
});

const [isModalOpen, setIsModalOpen] = useState(false);

// checks if the field inputs have been changed by the user by checking
// that they are different from the saved field values on the draft artifact
// if the input is undefined on the draft artifact, then it is treated as
Expand Down Expand Up @@ -226,29 +229,38 @@ export default function ResourceAuthoringPage() {
data={libOptions}
/>
)}
<Button
w={120}
onClick={() => {
const [additions, deletions] = parseUpdate(
url,
identifierValue,
identifierSystem,
name,
title,
description,
library
);
resourceUpdate.mutate({
resourceType: resourceType as ArtifactResourceType,
id: id as string,
additions: additions,
deletions: deletions
});
}}
disabled={!isChanged()} // only enable the submit button when a field has changed
>
Submit
</Button>
<Group>
<Button
w={120}
onClick={() => {
const [additions, deletions] = parseUpdate(
url,
identifierValue,
identifierSystem,
name,
title,
description,
library
);
resourceUpdate.mutate({
resourceType: resourceType as ArtifactResourceType,
id: id as string,
additions: additions,
deletions: deletions
});
}}
disabled={!isChanged()} // only enable the submit button when a field has changed
>
Update
</Button>
<Button
w={120}
onClick={() => setIsModalOpen(true)}
//TODO/question: disabled={} any disable needed? any necessary fields?
>
Release
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This falls out of scope of "release" but interested in your thoughts on this - should we apply the updates to the draft measure automatically instead of having an "update" button? There's some pros/cons of both approaches, but it might be nice to automatically update the artifact content similar to how we automatically re-run calculation in fqm-testify whenever changes are made.

As an example, I made an update to my draft artifact (removed the description) without clicking the "update" button, and then released the artifact. The resulting active artifact had the old description, which could cause issues if someone were to accidentally forget to click the "update" button in practice. If we want to reduce burden on the user, it would be nice to automatically update the artifact

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I also noticed this! I think we should make a task for this. However, in the meantime, do you think it would be useful to at least have some sort of indication that changes have not been saved? So the user knows to update before releasing? Or should we just wait to do this task?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can hold off on this for now - I feel like this is a rare case and in general I feel like a user is not likely to make updates and then release directly afterward

</Button>
</Group>
</Stack>
</Grid.Col>
<Grid.Col span={6}>
Expand All @@ -262,6 +274,13 @@ export default function ResourceAuthoringPage() {
</Paper>
</Grid.Col>
</Grid>

<ReleaseModal
open={isModalOpen}
onClose={() => setIsModalOpen(false)}
id={id as string}
resourceType={resourceType as ArtifactResourceType}
/>
</div>
);
}
Loading
Loading