From a2b9c75afcd342aac61668c4f129233b8e69a8f0 Mon Sep 17 00:00:00 2001 From: Michael Haufe Date: Thu, 10 Oct 2024 16:49:09 -0500 Subject: [PATCH] Implemented Recycle Bin feature (#392) --- components/XDataTable.vue | 71 +++++-- migrations/.snapshot-cathedral.json | 180 ++++++++---------- migrations/Migration20241010185435.ts | 20 ++ .../environment/assumptions.client.vue | 4 +- .../environment/components.client.vue | 2 +- .../environment/constraints.client.vue | 3 +- .../environment/effects.client.vue | 4 +- .../environment/glossary.client.vue | 4 +- .../environment/invariants.client.vue | 4 +- .../goals/functionality.client.vue | 2 +- .../goals/limitations.client.vue | 4 +- .../goals/obstacles.client.vue | 4 +- .../[solution-slug]/goals/outcomes.client.vue | 4 +- .../goals/scenarios.client.vue | 4 +- .../goals/stakeholders.client.vue | 3 +- .../project/roles-personnel.client.vue | 4 +- .../system/components.client.vue | 3 +- .../system/functionality.client.vue | 2 +- .../system/scenarios.client.vue | 8 +- pages/o/[organization-slug]/users.vue | 3 +- server/api/audit-log/deleted.get.ts | 65 +++++++ server/data/subscribers/AuditSubscriber.ts | 4 +- server/domain/AuditLog.ts | 14 +- 23 files changed, 271 insertions(+), 145 deletions(-) create mode 100644 migrations/Migration20241010185435.ts create mode 100644 server/api/audit-log/deleted.get.ts diff --git a/components/XDataTable.vue b/components/XDataTable.vue index b7074fc1..1556a718 100644 --- a/components/XDataTable.vue +++ b/components/XDataTable.vue @@ -12,11 +12,12 @@ export type FormFieldType = 'text' | 'textarea' | { type: 'number', min: number, const props = defineProps<{ datasource: RowType[] | null, + entityName: string, + showRecycleBin: boolean, viewModel: { [K in keyof RowType]?: ViewFieldType }, createModel: { [K in keyof RowType]?: FormFieldType }, editModel: { [K in keyof RowType]?: FormFieldType }, loading: boolean, - showHistory: boolean, organizationSlug: string, onCreate: (data: RowType) => Promise, onDelete: (id: string) => Promise, @@ -43,7 +44,12 @@ const dataTable = ref(), historyDialogVisible = ref(false), historyItems = ref<{ date: string, entity: Record }[]>([]), selectedHistoryItem = ref<{ date: string, entity: Record }>({ date: '', entity: {} }), - historyDialogLoading = ref(false) + historyDialogLoading = ref(false), + recycleDialog = ref(), + recycleDialogVisible = ref(false), + recycleDialogLoading = ref(false), + recycleItems = ref<{ date: string, entity: Record }[]>([]), + recycleBin = ref() const filters = ref>({ 'global': { value: null, matchMode: FilterMatchMode.CONTAINS } @@ -57,23 +63,41 @@ const openEditDialog = (item: RowType) => { editDialogItem.value = { ...item } } +const openRecycleDialog = async () => { + recycleDialogLoading.value = true + recycleDialogVisible.value = true + const recycleBinItems = (await $fetch(`/api/audit-log/deleted`, { + method: 'GET', + query: { + entityName: props.entityName, + organizationSlug: props.organizationSlug + } + })).map(log => ({ + date: new Date(log.createdAt).toLocaleString(), + entity: JSON.parse(log.entity) + })) + + recycleItems.value = recycleBinItems + recycleDialogLoading.value = false +} + const openHistoryDialog = async (item: RowType) => { historyDialogLoading.value = true historyDialogVisible.value = true - const auditLog = await $fetch(`/api/audit-log`, { + const auditLog = (await $fetch(`/api/audit-log`, { method: 'GET', query: { entityId: item.id, organizationSlug: props.organizationSlug } - }) + })).map(log => ({ + date: new Date(log.createdAt).toLocaleString(), + entity: JSON.parse(log.entity) + })) historyItems.value = [ { date: 'Current', entity: item }, - ...auditLog.map(log => ({ - date: new Date(log.createdAt).toLocaleString(), - entity: JSON.parse(log.entity) - })) + ...auditLog ] selectedHistoryItem.value = historyItems.value[0] @@ -155,7 +179,9 @@ const onEditDialogCancel = () => { + + +
+ + + + + + + + +
+ +
+ \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/environment/components.client.vue b/pages/o/[organization-slug]/[solution-slug]/environment/components.client.vue index 5989c323..0d05c5fe 100644 --- a/pages/o/[organization-slug]/[solution-slug]/environment/components.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/environment/components.client.vue @@ -69,6 +69,6 @@ const onUpdate = async (data: EnvironmentComponent) => { + :organizationSlug="organizationslug" entityName="EnvironmentComponent" :showRecycleBin="true"> \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/environment/constraints.client.vue b/pages/o/[organization-slug]/[solution-slug]/environment/constraints.client.vue index 914520d7..37464f71 100644 --- a/pages/o/[organization-slug]/[solution-slug]/environment/constraints.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/environment/constraints.client.vue @@ -75,6 +75,7 @@ const onUpdate = async (data: Constraint) => { :createModel="{ name: 'text', category: Object.values(ConstraintCategory), statement: 'text' }" :editModel="{ id: 'hidden', name: 'text', category: Object.values(ConstraintCategory), statement: 'text' }" :datasource="constraints" :on-create="onCreate" :on-delete="onDelete" :on-update="onUpdate" - :loading="status === 'pending'" :show-history="true" :organizationSlug="organizationslug"> + :loading="status === 'pending'" :organizationSlug="organizationslug" entityName="Constraint" + :showRecycleBin="true"> \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/environment/effects.client.vue b/pages/o/[organization-slug]/[solution-slug]/environment/effects.client.vue index aecc3889..244e313d 100644 --- a/pages/o/[organization-slug]/[solution-slug]/environment/effects.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/environment/effects.client.vue @@ -70,7 +70,7 @@ const onDelete = async (id: string) => {

+ :on-delete="onDelete" :on-update="onUpdate" :loading="status === 'pending'" :organizationSlug="organizationslug" + entityName="Effect" :showRecycleBin="true"> \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/environment/glossary.client.vue b/pages/o/[organization-slug]/[solution-slug]/environment/glossary.client.vue index d81fb0e1..8c57e99a 100644 --- a/pages/o/[organization-slug]/[solution-slug]/environment/glossary.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/environment/glossary.client.vue @@ -70,7 +70,7 @@ const onDelete = async (id: string) => {

+ :on-delete="onDelete" :on-update="onUpdate" :loading="status === 'pending'" :organizationSlug="organizationslug" + entityName="GlossaryTerm" :showRecycleBin="true"> \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/environment/invariants.client.vue b/pages/o/[organization-slug]/[solution-slug]/environment/invariants.client.vue index 950e3d7a..7472073d 100644 --- a/pages/o/[organization-slug]/[solution-slug]/environment/invariants.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/environment/invariants.client.vue @@ -72,7 +72,7 @@ const onDelete = async (id: string) => { + :on-update="onUpdate" :on-delete="onDelete" :loading="status === 'pending'" :organizationSlug="organizationslug" + entityName="Invariant" :showRecycleBin="true"> \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/functionality.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/functionality.client.vue index fade1150..29ba3c8b 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/functionality.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/functionality.client.vue @@ -75,6 +75,6 @@ const onDelete = async (id: string) => { + :organizationSlug="organizationslug" entityName="FunctionalBehavior" :showRecycleBin="true"> \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/limitations.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/limitations.client.vue index e437091c..3a5bfff9 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/limitations.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/limitations.client.vue @@ -71,7 +71,7 @@ const onDelete = async (id: string) => { + :on-update="onUpdate" :on-delete="onDelete" :loading="status === 'pending'" :organizationSlug="organizationslug" + entityName="Limit" :showRecycleBin="true"> \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/obstacles.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/obstacles.client.vue index 085bf4a5..0f6f1d27 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/obstacles.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/obstacles.client.vue @@ -68,7 +68,7 @@ const onDelete = async (id: string) => { + :on-update="onUpdate" :on-delete="onDelete" :loading="status === 'pending'" :organizationSlug="organizationslug" + entityName="Obstacle" :showRecycleBin="true"> \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/outcomes.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/outcomes.client.vue index db478df6..9d16139c 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/outcomes.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/outcomes.client.vue @@ -72,7 +72,7 @@ const onDelete = async (id: string) => { + :onUpdate="onUpdate" :onDelete="onDelete" :loading="status === 'pending'" :organizationSlug="organizationslug" + entityName="Outcome" :showRecycleBin="true"> \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/scenarios.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/scenarios.client.vue index f534ee24..462eef87 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/scenarios.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/scenarios.client.vue @@ -119,7 +119,7 @@ const onUserStoryDelete = async (id: string) => { functionalBehavior: { type: 'requirement', options: functionalBehaviors ?? [] }, outcome: { type: 'requirement', options: outcomes ?? [] } }" :datasource="userStories" :onCreate="onUserStoryCreate" :onUpdate="onUserStoryUpdate" - :onDelete="onUserStoryDelete" :loading="status === 'pending'" :show-history="true" - :organizationSlug="organizationslug"> + :onDelete="onUserStoryDelete" :loading="status === 'pending'" :organizationSlug="organizationslug" + entityName="UserStory" :showRecycleBin="true"> \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/stakeholders.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/stakeholders.client.vue index bf17aae2..158b0342 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/stakeholders.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/stakeholders.client.vue @@ -139,7 +139,8 @@ const onDelete = async (id: string) => { category: Object.values(StakeholderCategory), segmentation: Object.values(StakeholderSegmentation) }" :datasource="stakeholders" :on-create="onCreate" :on-update="onUpdate" :on-delete="onDelete" - :loading="status === 'pending'" :show-history="true" :organizationSlug="organizationslug"> + :loading="status === 'pending'" :organizationSlug="organizationslug" entityName="Stakeholder" + :showRecycleBin="true"> diff --git a/pages/o/[organization-slug]/[solution-slug]/project/roles-personnel.client.vue b/pages/o/[organization-slug]/[solution-slug]/project/roles-personnel.client.vue index 3d5bdbde..03c3cd6a 100644 --- a/pages/o/[organization-slug]/[solution-slug]/project/roles-personnel.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/project/roles-personnel.client.vue @@ -72,7 +72,7 @@ const onDelete = async (id: string) => { + :on-update="onUpdate" :on-delete="onDelete" :loading="status === 'pending'" :organizationSlug="organizationslug" + entityName="Person" :showRecycleBin="true"> \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/system/components.client.vue b/pages/o/[organization-slug]/[solution-slug]/system/components.client.vue index 1cb2efc3..5f7ae1f2 100644 --- a/pages/o/[organization-slug]/[solution-slug]/system/components.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/system/components.client.vue @@ -83,6 +83,7 @@ const onDelete = async (id: string) => { statement: 'text', parentComponent: { type: 'requirement', options: systemComponents ?? [] } }" :datasource="systemComponents" :onCreate="onCreate" :onUpdate="onUpdate" :onDelete="onDelete" - :loading="status === 'pending'" :show-history="true" :organizationSlug="organizationslug"> + :loading="status === 'pending'" :organizationSlug="organizationslug" entityName="SystemComponent" + :showRecycleBin="true"> \ No newline at end of file diff --git a/pages/o/[organization-slug]/[solution-slug]/system/functionality.client.vue b/pages/o/[organization-slug]/[solution-slug]/system/functionality.client.vue index 8d3949c1..464a5706 100644 --- a/pages/o/[organization-slug]/[solution-slug]/system/functionality.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/system/functionality.client.vue @@ -105,7 +105,7 @@ const componentSortField = ref('name')
 { This section is disabled temporarily. }