Skip to content

Commit

Permalink
Use endpoints (#113)
Browse files Browse the repository at this point in the history
* Initial remove draft database and update trpc procedures

* Cleanup and version calculation

* Fix version issues and clean up TODOs

* Implement review procedure and cleanup version calculation

* review workflow and fix author validation

* active review workflow and lint/prettier

* Remove comment

* add comment on string for POST validation

* Clean comments, errors, and imports
  • Loading branch information
lmd59 authored Oct 8, 2024
1 parent eb19b42 commit 9e6e8a2
Show file tree
Hide file tree
Showing 19 changed files with 420 additions and 713 deletions.
3 changes: 1 addition & 2 deletions app/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
PUBLIC_MRS_SERVER=http://localhost:3000/4_0_1
MRS_SERVER=http://localhost:3000/4_0_1
MONGODB_URI=mongodb://localhost:27017/draft-repository?replicaSet=rs0
MRS_SERVER=http://localhost:3000/4_0_1
22 changes: 3 additions & 19 deletions app/src/components/ReleaseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,6 @@ export default function ReleaseModal({ open = true, onClose, id, resourceType }:
});
const ctx = trpc.useContext();

const deleteMutation = trpc.draft.deleteDraft.useMutation({
onSuccess: data => {
console.log(`Successfully delete ${data.resourceType}/${data.draftId} from the database.`);
},
onError: e => {
console.error(e);
}
});

const releaseMutation = trpc.service.releaseParent.useMutation({
onSuccess: data => {
if (data.status !== 200) {
Expand All @@ -49,24 +40,17 @@ export default function ReleaseModal({ open = true, onClose, id, resourceType }:
color: 'red'
});
} else {
data.deletable?.forEach(d => {
data.released?.forEach(r => {
notifications.show({
title: `Draft ${d.resourceType} released!`,
message: `Draft ${d.resourceType}/${d.id} successfully released to the Publishable Measure Repository!`,
title: `Draft ${r.resourceType} released!`,
message: `Draft ${r.resourceType}/${r.id} successfully released!`,
icon: <CircleCheck />,
color: 'green'
});
});
ctx.draft.getDraftCounts.invalidate();
ctx.draft.getDrafts.invalidate();
router.push(data.location);

data.deletable?.forEach(d => {
deleteMutation.mutate({
resourceType: d.resourceType,
id: d.id
});
});
}
onClose();
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/ResourceInfoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export default function ResourceInfoCard({ resourceInfo, authoring }: ResourceIn
}
});

const deleteMutation = trpc.draft.deleteParent.useMutation({
const deleteMutation = trpc.draft.deleteDraft.useMutation({
onSuccess: (data, variables) => {
successNotification(variables.resourceType, false, 'delete', variables.id);
data.children.forEach(c => {
Expand Down
4 changes: 3 additions & 1 deletion app/src/pages/[resourceType].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ export const getServerSideProps: GetServerSideProps<{
const checkedResourceType = resourceType as ArtifactResourceType;

// Fetch resource data with the _elements parameter so we only get the elements that we need
const res = await fetch(`${process.env.MRS_SERVER}/${checkedResourceType}?_elements=id,identifier,name,url,version`);
const res = await fetch(
`${process.env.MRS_SERVER}/${checkedResourceType}?_elements=id,identifier,name,url,version&status=active`
);
const bundle = (await res.json()) as fhir4.Bundle<FhirArtifact>;
if (!bundle.entry) {
// Measure Repository should not provide a bundle without an entry
Expand Down
63 changes: 11 additions & 52 deletions app/src/pages/authoring/[resourceType]/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@ import { ArtifactResourceType } from '@/util/types/fhir';
import ArtifactFieldInput from '@/components/ArtifactFieldInput';
import ReleaseModal from '@/components/ReleaseModal';

interface DraftArtifactUpdates {
url?: string;
identifier?: fhir4.Identifier[];
name?: string;
title?: string;
description?: string;
library?: string[] | null;
}

export default function ResourceAuthoringPage() {
const router = useRouter();
const { resourceType, id } = router.query;
Expand Down Expand Up @@ -129,49 +120,19 @@ export default function ResourceAuthoringPage() {
}
});

function parseUpdate(
url: string,
identifierValue: string,
identifierSystem: string,
name: string,
title: string,
description: string,
library: string | null
) {
const additions: DraftArtifactUpdates = {};
const deletions: DraftArtifactUpdates = {};

url.trim() !== '' ? (additions['url'] = url) : (deletions['url'] = '');
if (identifierValue.trim() !== '') {
if (identifierSystem.trim() !== '') {
additions['identifier'] = [{ system: identifierSystem, value: identifierValue }];
} else {
additions['identifier'] = [{ value: identifierValue }];
}
} else {
deletions['identifier'] = [{ system: '', value: '' }];
}
name.trim() !== '' ? (additions['name'] = name) : (deletions['name'] = '');
title.trim() !== '' ? (additions['title'] = title) : (deletions['title'] = '');
description.trim() !== '' ? (additions['description'] = description) : (deletions['description'] = '');
library ? (additions['library'] = [library]) : (deletions['library'] = null);

return [additions, deletions];
}

// set up main library options
let libOptions: { value: string; label: string; disabled: boolean }[] = []; // default to empty
if (resourceType === 'Measure') {
const { data: libraries } = trpc.draft.getDrafts.useQuery('Library' as ArtifactResourceType);
if (libraries) {
libOptions = libraries.map(l => {
if (l.url) {
if (l?.url) {
// prioritizes use of url/version
const val = `${l.url}${l.version ? `|${l.version}` : ''}`;
return { value: val, label: val, disabled: false };
} else {
// uses id (assumed to exist in this context), but option disabled if no url exists (required for canonical reference)
const val = l.id ?? '';
const val = l?.id ?? '';
return { value: val, label: val, disabled: true };
}
});
Expand Down Expand Up @@ -233,20 +194,18 @@ export default function ResourceAuthoringPage() {
<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
values: {
url: url,
identifierValue: identifierValue,
identifierSystem: identifierSystem,
name: name,
title: title,
description: description,
library: library
}
});
}}
disabled={!isChanged()} // only enable the submit button when a field has changed
Expand Down
3 changes: 1 addition & 2 deletions app/src/pages/authoring/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
createStyles,
Loader
} from '@mantine/core';
import { v4 as uuidv4 } from 'uuid';
import { useState } from 'react';
import { trpc } from '../../util/trpc';
import { MeasureSkeleton, LibrarySkeleton } from '@/util/authoringFixtures';
Expand Down Expand Up @@ -109,8 +108,8 @@ export default function AuthoringPage() {
});

const createResource = () => {
// TODO: randomize skeleton url or increment draft version so a user can make a number of skeleton drafts without having a url/version conflict
const newResource = resourceType === 'Measure' ? { ...MeasureSkeleton } : { ...LibrarySkeleton };
newResource.id = uuidv4();
draftMutation.mutate({ resourceType, draft: newResource });
};

Expand Down
114 changes: 28 additions & 86 deletions app/src/pages/review/[resourceType]/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
Box,
Button,
Center,
Checkbox,
Divider,
Grid,
Group,
Expand All @@ -29,19 +28,12 @@ import { isNotEmpty, useForm } from '@mantine/form';
import { notifications } from '@mantine/notifications';
import ArtifactTimeline from '@/components/ArtifactTimeline';

interface DraftArtifactUpdates {
extension?: fhir4.Extension[];
date?: string;
lastReviewDate?: string;
}

/**
* Component which renders a page that displays the JSON data of a resource. Also will eventually
* provide the user with the ability to make review comments and visualize previous review comments.
*/
export default function CommentPage() {
const ctx = trpc.useContext();
const [dateSelected, setDateSelected] = useState(false);
const ref = useRef<HTMLInputElement>(null);
const [isLoading, setIsLoading] = useState(false);

Expand All @@ -54,8 +46,7 @@ export default function CommentPage() {
initialValues: {
type: '',
comment: '',
name: '',
date: ''
name: ''
},
// An error will be thrown if these fields aren't entered properly
validate: {
Expand All @@ -64,21 +55,32 @@ export default function CommentPage() {
}
});

// Currently we can only update draft artifact resources.
const resourceUpdate = trpc.draft.updateDraft.useMutation({
onSuccess: () => {
const resourceReview = trpc.draft.reviewDraft.useMutation({
onSuccess: data => {
notifications.show({
title: 'Comment Successfully added!',
message: `Comment Successfully added to ${resourceType}/${resourceID}`,
title: 'Review successfully added!',
message: `Review successfully added to ${resourceType}/${resourceID}`,
icon: <CircleCheck />,
color: 'green'
});
ctx.draft.getDraftById.invalidate();
data.children.forEach(c => {
notifications.show({
title: 'Review successfully added!',
message: `Draft of child ${resourceType} artifact of url ${c.url} successfully reviewed`,
icon: <CircleCheck />,
color: 'green'
});
});
if (authoring) {
ctx.draft.getDraftById.invalidate();
} else {
ctx.service.getArtifactById.invalidate();
}
},
onError: e => {
notifications.show({
title: 'Update Failed!',
message: `Attempt to update ${resourceType} failed with message: ${e.message}`,
title: 'Review Failed!',
message: `Attempt to review ${resourceType} failed with message: ${e.message}`,
icon: <AlertCircle />,
color: 'red'
});
Expand All @@ -99,47 +101,6 @@ export default function CommentPage() {
}
}

function parseUpdate(comment: string, type: string, userName: string, dateSelected: boolean) {
const additions: DraftArtifactUpdates = {};
const deletions: DraftArtifactUpdates = {};
const currentDate = new Date().toISOString();

const newExtension: fhir4.Extension[] = [];
newExtension.push({ url: 'type', valueCode: type }, { url: 'text', valueMarkdown: comment });

if (userName !== '') {
newExtension.push({ url: 'user', valueString: userName });
}
if (dateSelected === true) {
newExtension.push({ url: 'authoredOn', valueDateTime: currentDate });
}

if (resource) {
if (resource.extension) {
resource.extension.push({
extension: newExtension,
url: 'http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-artifactComment'
});
additions.extension = resource.extension;
} else {
resource.extension = [
{
extension: newExtension,
url: 'http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-artifactComment'
}
];
additions.extension = resource.extension;
}

// update resource dates
resource.date = currentDate;
additions.date = resource.date;
resource.lastReviewDate = currentDate;
additions.lastReviewDate = resource.lastReviewDate;
}
return [additions, deletions];
}

return (
<div>
<Center>
Expand Down Expand Up @@ -249,18 +210,7 @@ export default function CommentPage() {
/>
<Space h="md" />
<Group grow>
<TextInput radius="lg" label="Endorser Name" placeholder="Name" {...form.getInputProps('name')} />
<Checkbox
ref={ref}
id="checkbox"
color="white"
mt="md"
label="Include Date in Comment"
{...form.getInputProps('date')}
onChange={() => {
setDateSelected(!dateSelected);
}}
/>
<TextInput radius="lg" label="Endorser URI" placeholder="URI" {...form.getInputProps('name')} />
<Space h="md" />
</Group>
<Space h="md" />
Expand All @@ -272,27 +222,19 @@ export default function CommentPage() {
if (form.isValid()) {
setIsLoading(true);
setTimeout(() => {
setDateSelected(false);
form.reset();
if (ref?.current?.checked) {
ref.current.checked = false;
}
setIsLoading(false);
}, 1000);
if (authoring === 'true') {
const [additions, deletions] = parseUpdate(
form.values.comment,
form.values.type,
form.values.name,
dateSelected
);
resourceUpdate.mutate({
resourceType: resourceType as ArtifactResourceType,
id: resourceID as string,
additions: additions,
deletions: deletions
});
}
resourceReview.mutate({
resourceType: resourceType as ArtifactResourceType,
id: resourceID as string,
type: form.values.type,
summary: form.values.comment,
author: form.values.name
});
}
}}
>
Expand Down
Loading

0 comments on commit 9e6e8a2

Please sign in to comment.