Skip to content

Commit

Permalink
active review workflow and lint/prettier
Browse files Browse the repository at this point in the history
  • Loading branch information
lmd59 committed Oct 2, 2024
1 parent 7a87e8c commit ae9600e
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 79 deletions.
6 changes: 3 additions & 3 deletions app/src/pages/[resourceType].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { ArtifactResourceType, ResourceInfo, FhirArtifact } from '@/util/types/f
import ResourceCards from '@/components/ResourceCards';
import Link from 'next/link';
import { extractResourceInfo } from '@/util/resourceCardUtils';
import { trpc } from '@/util/trpc';

/**
* Component which displays list of all resources of some type as passed in by (serverside) props
Expand Down Expand Up @@ -54,8 +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
// TODO: send this through a procedure instead?
const res = await fetch(`${process.env.MRS_SERVER}/${checkedResourceType}?_elements=id,identifier,name,url,version&status=active`);
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
1 change: 0 additions & 1 deletion 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
28 changes: 13 additions & 15 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 Down Expand Up @@ -56,11 +55,9 @@ export default function CommentPage() {
}
});


const utils = trpc.useUtils();
// Currently we can only update draft artifact resources. TODO: should we enable active resource review?
const resourceReview = trpc.draft.reviewDraft.useMutation({
onSuccess: (data) => {
onSuccess: data => {
notifications.show({
title: 'Review successfully added!',
message: `Review successfully added to ${resourceType}/${resourceID}`,
Expand All @@ -75,8 +72,11 @@ export default function CommentPage() {
color: 'green'
});
});
utils.draft.getDrafts.invalidate();
ctx.draft.getDraftById.invalidate();
if (authoring) {
ctx.draft.getDraftById.invalidate();
} else {
ctx.service.getArtifactById.invalidate();
}
},
onError: e => {
notifications.show({
Expand Down Expand Up @@ -229,15 +229,13 @@ export default function CommentPage() {
}
setIsLoading(false);
}, 1000);
if (authoring === 'true') {
resourceReview.mutate({
resourceType: resourceType as ArtifactResourceType,
id: resourceID as string,
type: form.values.type,
summary: form.values.comment,
author: form.values.name
});
}
resourceReview.mutate({
resourceType: resourceType as ArtifactResourceType,
id: resourceID as string,
type: form.values.type,
summary: form.values.comment,
author: form.values.name
});
}
}}
>
Expand Down
107 changes: 61 additions & 46 deletions app/src/server/trpc/routers/draft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ export const draftRouter = router({
} as const;
}),

getDrafts: publicProcedure
.input(z.enum(['Measure', 'Library']).optional())
.query(async ({ input }) => {
if (!input) return null;
const artifactBundle = await fetch(
`${process.env.MRS_SERVER}/${input}?status=draft`
).then(resArtifacts => resArtifacts.json() as Promise<fhir4.Bundle<FhirArtifact>>);
const artifactList = artifactBundle.entry?.filter(entry => entry.resource).map(entry => entry.resource as FhirArtifact);
return artifactList;
}),
getDrafts: publicProcedure.input(z.enum(['Measure', 'Library']).optional()).query(async ({ input }) => {
if (!input) return null;
const artifactBundle = await fetch(`${process.env.MRS_SERVER}/${input}?status=draft`).then(
resArtifacts => resArtifacts.json() as Promise<fhir4.Bundle<FhirArtifact>>
);
const artifactList = artifactBundle.entry
?.filter(entry => entry.resource)
.map(entry => entry.resource as FhirArtifact);
return artifactList;
}),

getDraftById: publicProcedure
.input(z.object({ id: z.string().optional(), resourceType: z.enum(['Measure', 'Library']).optional() }))
Expand All @@ -47,12 +47,12 @@ export const draftRouter = router({
const res = await fetch(`${process.env.MRS_SERVER}/${input.resourceType}`, {
method: 'POST',
headers: {
'Accept': 'application/json+fhir',
Accept: 'application/json+fhir',
'Content-Type': 'application/json+fhir'
},
body: JSON.stringify(input.draft)
});
if(res.status === 201){
if (res.status === 201) {
// get resultant id from location header
return { draftId: res.headers.get('Location')?.split('/')[2] as string };
}
Expand All @@ -61,29 +61,27 @@ export const draftRouter = router({
}),

updateDraft: publicProcedure
.input(
z.object({ resourceType: z.enum(['Measure', 'Library']), values: z.any(), id: z.string() })
)
.input(z.object({ resourceType: z.enum(['Measure', 'Library']), values: z.any(), id: z.string() }))
.mutation(async ({ input }) => {
const raw = await fetch(`${process.env.MRS_SERVER}/${input.resourceType}/${input.id}`);
const resource: FhirArtifact = await raw.json();
resource.url = input.values.url;
resource.identifier = [{system: input.values.identifierSystem, value:input.values.identifierValue}];
resource.identifier = [{ system: input.values.identifierSystem, value: input.values.identifierValue }];
resource.name = input.values.name;
resource.title = input.values.title;
resource.description = input.values.description;
if(input.resourceType === 'Measure'){
if (input.resourceType === 'Measure') {
(resource as CRMIShareableMeasure).library = input.values.library;
}
const res = await fetch(`${process.env.MRS_SERVER}/${input.resourceType}/${input.id}`, {
method: 'PUT',
headers: {
'Accept': 'application/json+fhir',
Accept: 'application/json+fhir',
'Content-Type': 'application/json+fhir'
},
body: JSON.stringify(resource)
});
if(res.status === 200){
if (res.status === 200) {
return {};
}
const outcome: OperationOutcome = await res.json();
Expand All @@ -96,39 +94,40 @@ export const draftRouter = router({
const res = await fetch(`${process.env.MRS_SERVER}/${input.resourceType}/${input.id}`, {
method: 'DELETE',
headers: {
'Accept': 'application/json+fhir',
Accept: 'application/json+fhir',
'Content-Type': 'application/json+fhir'
}
});

if(res.status === 204){
if (res.status === 204) {
const resData = { draftId: input.id, resourceType: input.resourceType, children: [] as FhirArtifact[] };

// TODO: update to use server-side batch delete to find child information once it returns a 200/bundle
// const resBundle: Bundle<FhirArtifact> = await res.json();
// if (!resBundle.entry || resBundle.entry.length === 0) {
// throw new Error(`No deletions found from deleting ${input.resourceType}, id ${input.id}`);
// }
// resBundle.entry.forEach(e => {
// if(e.resource?.extension?.find(ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/artifact-isOwned' && ext.valueBoolean)){
// resData.children.push(e.resource);
// }
// });
// const resBundle: Bundle<FhirArtifact> = await res.json();
// if (!resBundle.entry || resBundle.entry.length === 0) {
// throw new Error(`No deletions found from deleting ${input.resourceType}, id ${input.id}`);
// }
// resBundle.entry.forEach(e => {
// if(e.resource?.extension?.find(ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/artifact-isOwned' && ext.valueBoolean)){
// resData.children.push(e.resource);
// }
// });
return resData;
}
const outcome: OperationOutcome = await res.json();
throw new Error(`Received ${res.status} error on delete: ${outcome.issue[0].details?.text}`);
}),


cloneParent: publicProcedure
.input(z.object({ id: z.string(), resourceType: z.enum(['Measure', 'Library']) }))
.mutation(async ({ input }) => {
const raw = await fetch(`${process.env.MRS_SERVER}/${input.resourceType}/${input.id}`);
const resource = (await raw.json()) as FhirArtifact;
const version = await calculateVersion(input.resourceType, resource.url, resource.version);
// $clone with calculated version
const res = await fetch(`${process.env.MRS_SERVER}/${input.resourceType}/${input.id}/$clone?version=${version}&url=${resource.url}`);
const res = await fetch(
`${process.env.MRS_SERVER}/${input.resourceType}/${input.id}/$clone?version=${version}&url=${resource.url}`
);

if (res.status !== 200) {
const outcome: OperationOutcome = await res.json();
Expand All @@ -141,11 +140,15 @@ export const draftRouter = router({
throw new Error(`No clones found from cloning ${input.resourceType}, id ${input.id}`);
}

const resData = { cloneId: undefined as string|undefined, children: [] as FhirArtifact[] };
const resData = { cloneId: undefined as string | undefined, children: [] as FhirArtifact[] };
resBundle.entry.forEach(e => {
if(e.resource?.extension?.find(ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/artifact-isOwned' && ext.valueBoolean)){
if (
e.resource?.extension?.find(
ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/artifact-isOwned' && ext.valueBoolean
)
) {
resData.children.push(e.resource);
}else{
} else {
resData.cloneId = e.resource?.id;
}
});
Expand All @@ -154,22 +157,30 @@ export const draftRouter = router({

// passes in type, summary, and author from user (set date and target automatically)
reviewDraft: publicProcedure
.input(z.object({ id: z.string(), resourceType: z.enum(['Measure', 'Library']), type: z.string(), summary: z.string(), author: z.string() }))
.input(
z.object({
id: z.string(),
resourceType: z.enum(['Measure', 'Library']),
type: z.string(),
summary: z.string(),
author: z.string()
})
)
.mutation(async ({ input }) => {
const raw = await fetch(`${process.env.MRS_SERVER}/${input.resourceType}/${input.id}`);
const resource = (await raw.json()) as FhirArtifact;
const date = new Date().toISOString();
const canonical = `${resource.url}|${resource.version}`;

const params = new URLSearchParams({
'reviewDate': date,
'artifactAssessmentType': input.type,
'artifactAssessmentSummary': input.summary,
'artifactAssessmentTarget': canonical,
'artifactAssessmentAuthor': input.author
reviewDate: date,
artifactAssessmentType: input.type,
artifactAssessmentSummary: input.summary,
artifactAssessmentTarget: canonical,
artifactAssessmentAuthor: input.author
});
const res = await fetch(`${process.env.MRS_SERVER}/${input.resourceType}/${input.id}/$review?${params}`);

if (res.status !== 200) {
const outcome: OperationOutcome = await res.json();
throw new Error(`Received ${res.status} error on $review: ${outcome.issue[0].details?.text}`);
Expand All @@ -181,11 +192,15 @@ export const draftRouter = router({
throw new Error(`No updated resources found from reviewing ${input.resourceType}, id ${input.id}`);
}

const resData = { reviewId: undefined as string|undefined, children: [] as FhirArtifact[] };
const resData = { reviewId: undefined as string | undefined, children: [] as FhirArtifact[] };
resBundle.entry.forEach(e => {
if(e.resource?.extension?.find(ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/artifact-isOwned' && ext.valueBoolean)){
if (
e.resource?.extension?.find(
ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/artifact-isOwned' && ext.valueBoolean
)
) {
resData.children.push(e.resource);
}else{
} else {
resData.reviewId = e.resource?.id;
}
});
Expand Down
25 changes: 17 additions & 8 deletions app/src/server/trpc/routers/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,15 @@ export const serviceRouter = router({
throw new Error(`No drafts found from drafting ${input.resourceType}, id ${input.id}`);
}

const resData = { draftId: undefined as string|undefined, children: [] as FhirArtifact[] };
const resData = { draftId: undefined as string | undefined, children: [] as FhirArtifact[] };
resBundle.entry.forEach(e => {
if(e.resource?.extension?.find(ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/artifact-isOwned' && ext.valueBoolean)){
if (
e.resource?.extension?.find(
ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/artifact-isOwned' && ext.valueBoolean
)
) {
resData.children.push(e.resource);
}else{
} else {
resData.draftId = e.resource?.id;
}
});
Expand All @@ -103,8 +107,9 @@ export const serviceRouter = router({
releaseParent: publicProcedure
.input(z.object({ resourceType: z.enum(['Measure', 'Library']), id: z.string(), version: z.string() }))
.mutation(async ({ input }) => {

const res = await fetch(`${process.env.MRS_SERVER}/${input.resourceType}/${input.id}/$release?releaseVersion=${input.version}&versionBehavior=force`);
const res = await fetch(
`${process.env.MRS_SERVER}/${input.resourceType}/${input.id}/$release?releaseVersion=${input.version}&versionBehavior=force`
);

if (res.status !== 200) {
const outcome: OperationOutcome = await res.json();
Expand All @@ -122,11 +127,15 @@ export const serviceRouter = router({
id: string;
}[] = [{ resourceType: input.resourceType, id: input.id }]; //start with parent and add children
resBundle.entry.forEach(e => {
if(e.resource?.extension?.find(ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/artifact-isOwned' && ext.valueBoolean)){
released.push({resourceType: e.resource.resourceType, id: e.resource.id});
if (
e.resource?.extension?.find(
ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/artifact-isOwned' && ext.valueBoolean
)
) {
released.push({ resourceType: e.resource.resourceType, id: e.resource.id });
}
});

return { location: `/${input.resourceType}/${input.id}`, released: released, status: res.status, error: null };
})
});
6 changes: 2 additions & 4 deletions app/src/util/versionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import { Bundle } from 'fhir/r4';
* It increments the version if the artifact has one or sets it to
* 0.0.1 if it does not
*/
export async function calculateVersion(resourceType: 'Library'|'Measure', url: string, version: string) {

export async function calculateVersion(resourceType: 'Library' | 'Measure', url: string, version: string) {
let newVersion = '0.0.1';

// initial version coercion and increment
Expand Down Expand Up @@ -94,8 +93,7 @@ function checkVersionFormat(version: string): boolean {
// in order to decide whether to increment the version further
async function getResourceByUrl(url: string, version: string, resourceType: string) {
const res = await fetch(`${process.env.MRS_SERVER}/${resourceType}?url=${url}&version=${version}`);
const bundle:Bundle<FhirArtifact> = await res.json();
const bundle: Bundle<FhirArtifact> = await res.json();
// return first entry found in bundle
return bundle.entry && bundle.entry.length > 0 ? bundle.entry[0].resource : null;
}

8 changes: 6 additions & 2 deletions service/src/requestSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,9 @@ export const ApproveArgs = z
artifactAssessmentSummary: z.string().optional(),
artifactAssessmentTarget: checkUri.optional(),
artifactAssessmentRelatedArtifact: checkUri.optional(),
artifactAssessmentAuthor: z.union([z.object({ reference: z.string()}).transform((val) => val.reference), z.string()]).optional() //object from POST or string from GET
artifactAssessmentAuthor: z
.union([z.object({ reference: z.string() }).transform(val => val.reference), z.string()])
.optional() //object from POST or string from GET
})
.strict()
.superRefine(catchInvalidParams([catchMissingId, catchMissingTypeAndSummary]));
Expand All @@ -220,7 +222,9 @@ export const ReviewArgs = z
artifactAssessmentSummary: z.string().optional(),
artifactAssessmentTarget: checkUri.optional(),
artifactAssessmentRelatedArtifact: checkUri.optional(),
artifactAssessmentAuthor: z.union([z.object({ reference: z.string()}).transform((val) => val.reference), z.string()]).optional() //object from POST or string from GET
artifactAssessmentAuthor: z
.union([z.object({ reference: z.string() }).transform(val => val.reference), z.string()])
.optional() //object from POST or string from GET
})
.strict()
.superRefine(catchInvalidParams([catchMissingId, catchMissingTypeAndSummary]));
Expand Down

0 comments on commit ae9600e

Please sign in to comment.