Skip to content

Commit

Permalink
Child artifact delete (#94)
Browse files Browse the repository at this point in the history
* Initial delete functionality

* Remove old functions

* Add child artifact to the delete confirmation modal
  • Loading branch information
elsaperelli authored Apr 24, 2024
1 parent f784542 commit 5d5bfe8
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 40 deletions.
90 changes: 61 additions & 29 deletions app/src/components/ResourceInfoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: <CircleCheck />,
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: <CircleCheck />,
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: <AlertCircle />,
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: <AlertCircle />,
color: 'red'
});
onError: (e, variables) => {
errorNotification(variables.resourceType, e.message, false, variables.id);
}
});

Expand All @@ -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,
Expand Down Expand Up @@ -130,19 +153,28 @@ export default function ResourceInfoCard({ resourceInfo, authoring }: ResourceIn
</ActionIcon>
</Tooltip>
</Link>
{authoring && (
<Tooltip label={'Delete Draft Resource'} openDelay={1000}>
<ActionIcon
radius="md"
size="md"
variant="subtle"
color="red"
onClick={() => setIsConfirmationModalOpen(true)}
>
<Trash size="24" />
</ActionIcon>
</Tooltip>
)}
{authoring &&
(resourceInfo.isChild ? (
<Tooltip label={'Child artifacts cannot be directly deleted'} openDelay={1000}>
<span>
<ActionIcon radius="md" size="md" disabled={true}>
<Trash size="24" />
</ActionIcon>
</span>
</Tooltip>
) : (
<Tooltip label={'Delete Draft Resource'} openDelay={1000}>
<ActionIcon
radius="md"
size="md"
variant="subtle"
color="red"
onClick={() => setIsConfirmationModalOpen(true)}
>
<Trash size="24" />
</ActionIcon>
</Tooltip>
))}
</Group>
</Grid>
</Paper>
Expand Down
36 changes: 29 additions & 7 deletions app/src/server/db/dbOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
32 changes: 31 additions & 1 deletion app/src/server/trpc/routers/draft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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 };
})
});
8 changes: 6 additions & 2 deletions app/src/util/modifyResourceFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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');
Expand All @@ -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);
}
});
Expand Down
5 changes: 4 additions & 1 deletion app/src/util/resourceCardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
1 change: 1 addition & 0 deletions app/src/util/types/fhir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ export interface ResourceInfo {
url: string | null;
version: string;
status: string | null;
isChild: boolean;
}

0 comments on commit 5d5bfe8

Please sign in to comment.