diff --git a/app/src/components/ResourceInfoCard.tsx b/app/src/components/ResourceInfoCard.tsx
index 173407f..59c5dbd 100644
--- a/app/src/components/ResourceInfoCard.tsx
+++ b/app/src/components/ResourceInfoCard.tsx
@@ -39,24 +39,47 @@ export default function ResourceInfoCard({ resourceInfo, authoring }: ResourceIn
const ctx = trpc.useContext();
const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
- const deleteMutation = trpc.draft.deleteDraft.useMutation({
- onSuccess: () => {
- notifications.show({
- title: `Draft ${resourceInfo.resourceType} Deleted!`,
- message: `Draft ${resourceInfo.resourceType}/${resourceInfo.id} successfully deleted`,
- icon: ,
- color: 'green'
+ const successNotification = (resourceType: string, childArtifact: boolean, idOrUrl?: string) => {
+ let message;
+ if (childArtifact) {
+ message = `Draft of child ${resourceType} artifact of url ${idOrUrl} successfully deleted`;
+ } else {
+ message = `Draft of ${resourceType}/${idOrUrl} successfully deleted`;
+ }
+ notifications.show({
+ title: `${resourceType} Deleted!`,
+ message: message,
+ icon: ,
+ color: 'green'
+ });
+ ctx.draft.getDraftCounts.invalidate();
+ };
+
+ const errorNotification = (resourceType: string, errorMessage: string, childArtifact: boolean, idOrUrl?: string) => {
+ let message;
+ if (childArtifact) {
+ message = `Attempt to delete draft of child ${resourceType} artifact of url ${idOrUrl} failed with message: ${errorMessage}`;
+ } else {
+ message = `Attempt to delete draft of ${resourceType}/${idOrUrl} failed with message: ${errorMessage}`;
+ }
+ notifications.show({
+ title: `${resourceType} Deletion Failed!`,
+ message: message,
+ icon: ,
+ color: 'red'
+ });
+ };
+
+ const deleteMutation = trpc.draft.deleteParent.useMutation({
+ onSuccess: (data, variables) => {
+ successNotification(variables.resourceType, false, variables.id);
+ data.children.forEach(c => {
+ successNotification(c.resourceType, true, c.url);
});
- ctx.draft.getDraftCounts.invalidate();
ctx.draft.getDrafts.invalidate();
},
- onError: e => {
- notifications.show({
- title: `Draft ${resourceInfo.resourceType} Deletion Failed!`,
- message: `Attempt to delete draft of ${resourceInfo.resourceType}/${resourceInfo.id} failed with message: ${e.message}`,
- icon: ,
- color: 'red'
- });
+ onError: (e, variables) => {
+ errorNotification(variables.resourceType, e.message, false, variables.id);
}
});
@@ -67,7 +90,7 @@ export default function ResourceInfoCard({ resourceInfo, authoring }: ResourceIn
onClose={() => setIsConfirmationModalOpen(false)}
modalText={`This will delete draft ${resourceInfo.resourceType} "${
resourceInfo.name ? resourceInfo.name : `${resourceInfo.resourceType}/${resourceInfo.id}`
- }" permanently.`}
+ }" and any child artifacts permanently.`}
onConfirm={() => {
deleteMutation.mutate({
resourceType: resourceInfo.resourceType,
@@ -130,19 +153,28 @@ export default function ResourceInfoCard({ resourceInfo, authoring }: ResourceIn
- {authoring && (
-
- setIsConfirmationModalOpen(true)}
- >
-
-
-
- )}
+ {authoring &&
+ (resourceInfo.isChild ? (
+
+
+
+
+
+
+
+ ) : (
+
+ setIsConfirmationModalOpen(true)}
+ >
+
+
+
+ ))}
diff --git a/app/src/server/db/dbOperations.ts b/app/src/server/db/dbOperations.ts
index ced70a1..41ad233 100644
--- a/app/src/server/db/dbOperations.ts
+++ b/app/src/server/db/dbOperations.ts
@@ -49,17 +49,15 @@ export async function batchCreateDraft(drafts: FhirArtifact[]) {
const client = await clientPromise;
const session = client.startSession();
try {
- session.startTransaction();
- const inserts = drafts.map(draft => {
- const collection = client.db().collection(draft.resourceType);
- return collection.insertOne(draft as any, { session });
+ await session.withTransaction(async () => {
+ for (const draft of drafts) {
+ const collection = await client.db().collection(draft.resourceType);
+ await collection.insertOne(draft as any, { session });
+ }
});
- await Promise.all(inserts);
- await session.commitTransaction();
console.log('Batch drafts transaction committed.');
} catch (err) {
console.error('Batch drafts transaction failed: ' + err);
- await session.abortTransaction();
error = err;
} finally {
await session.endSession();
@@ -93,3 +91,27 @@ export async function deleteDraft(resourceType: ArtifactResourceType, id: string
const collection = client.db().collection(resourceType);
return collection.deleteOne({ id });
}
+
+/**
+ * Deletes a parent artifact and all of its children (if applicable) in a batch
+ */
+export async function batchDeleteDraft(drafts: FhirArtifact[]) {
+ let error = null;
+ const client = await clientPromise;
+ const deleteSession = client.startSession();
+ try {
+ await deleteSession.withTransaction(async () => {
+ for (const draft of drafts) {
+ const collection = await client.db().collection(draft.resourceType);
+ await collection.deleteOne({ id: draft.id }, { session: deleteSession });
+ }
+ });
+ console.log('Batch delete transaction committed.');
+ } catch (err) {
+ console.error('Batch delete transaction failed: ' + err);
+ error = err;
+ } finally {
+ await deleteSession.endSession();
+ }
+ if (error) throw error;
+}
diff --git a/app/src/server/trpc/routers/draft.ts b/app/src/server/trpc/routers/draft.ts
index bf3ad08..a0bc75e 100644
--- a/app/src/server/trpc/routers/draft.ts
+++ b/app/src/server/trpc/routers/draft.ts
@@ -7,9 +7,11 @@ import {
getDraftCount,
updateDraft,
deleteDraft,
- getDraftByUrl
+ getDraftByUrl,
+ batchDeleteDraft
} from '../../db/dbOperations';
import { publicProcedure, router } from '../trpc';
+import { getDraftChildren } from '@/util/serviceUtils';
/** one big router with resource types passed in */
export const draftRouter = router({
@@ -67,5 +69,33 @@ export const draftRouter = router({
.mutation(async ({ input }) => {
const res = await deleteDraft(input.resourceType, input.id);
return { draftId: input.id, resourceType: input.resourceType, ...res };
+ }),
+
+ deleteParent: publicProcedure
+ .input(z.object({ id: z.string(), resourceType: z.enum(['Measure', 'Library']) }))
+ .mutation(async ({ input }) => {
+ // get the parent draft artifact by id
+ const draftRes = await getDraftById(input.id, input.resourceType);
+
+ if (!draftRes) {
+ throw new Error(`No draft artifact found for resourceType ${input.resourceType}, id ${input.id}`);
+ }
+
+ // recursively get any child artifacts from the artifact if they exist
+ const children = draftRes?.relatedArtifact ? await getDraftChildren(draftRes.relatedArtifact) : [];
+
+ const childDrafts = children.map(async child => {
+ const draft = await getDraftByUrl(child.url, child.version, child.resourceType);
+ if (!draft) {
+ throw new Error('No artifacts found in search');
+ }
+ return draft;
+ });
+
+ const draftArtifacts = [draftRes].concat(await Promise.all(childDrafts));
+
+ await batchDeleteDraft(draftArtifacts);
+
+ return { draftId: draftRes.id, children: children };
})
});
diff --git a/app/src/util/modifyResourceFields.ts b/app/src/util/modifyResourceFields.ts
index dd6db7c..4828757 100644
--- a/app/src/util/modifyResourceFields.ts
+++ b/app/src/util/modifyResourceFields.ts
@@ -11,6 +11,7 @@ import { getDraftByUrl } from '@/server/db/dbOperations';
export async function modifyResourceToDraft(artifact: FhirArtifact) {
artifact.id = uuidv4();
artifact.status = 'draft';
+ let count = 0;
// initial version coercion and increment
// we can only increment artifacts whose versions are either semantic, can be coerced
@@ -35,7 +36,6 @@ export async function modifyResourceToDraft(artifact: FhirArtifact) {
// check for existing draft with proposed version
let existingDraft = await getDraftByUrl(artifact.url, artifact.version, artifact.resourceType);
// only increment a limited number of times
- let count = 0;
while (existingDraft && count < 10) {
// increment artifact version
const incVersion = inc(artifact.version, 'patch');
@@ -56,7 +56,11 @@ export async function modifyResourceToDraft(artifact: FhirArtifact) {
)
) {
const url = ra.resource.split('|')[0];
- const version = ra.resource.split('|')[1];
+ let version = ra.resource.split('|')[1];
+ while (count !== 0) {
+ version = incrementArtifactVersion(version);
+ count--;
+ }
ra.resource = url + '|' + incrementArtifactVersion(version);
}
});
diff --git a/app/src/util/resourceCardUtils.ts b/app/src/util/resourceCardUtils.ts
index 2c3f4d3..b4c867c 100644
--- a/app/src/util/resourceCardUtils.ts
+++ b/app/src/util/resourceCardUtils.ts
@@ -19,7 +19,10 @@ export function extractResourceInfo(resource: FhirArtifact) {
name: resource.name ?? null,
url: resource.url ?? null,
version: resource.version ?? null,
- status: resource.status ?? null
+ status: resource.status ?? null,
+ isChild: !!resource.extension?.find(
+ ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/artifact-isOwned' && ext.valueBoolean === true
+ )
};
return resourceInfo;
}
diff --git a/app/src/util/types/fhir.ts b/app/src/util/types/fhir.ts
index 1919692..abdd7f1 100644
--- a/app/src/util/types/fhir.ts
+++ b/app/src/util/types/fhir.ts
@@ -24,4 +24,5 @@ export interface ResourceInfo {
url: string | null;
version: string;
status: string | null;
+ isChild: boolean;
}