Skip to content

Commit 712b08b

Browse files
committed
Merge branch 'main' of github.com:devforth/adminforth-audit-log
2 parents 5ffb6f0 + 214a4cb commit 712b08b

5 files changed

Lines changed: 126 additions & 47 deletions

File tree

‎custom/RelatedLogsLink.vue‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import { computed } from 'vue';
2020
import { useRoute } from 'vue-router';
2121
import { IconClockSolid } from '@iconify-prerendered/vue-flowbite';
22+
import { encodeRecordId } from '@/utils/recordId';
2223
import { useRouter } from 'vue-router';
2324
2425
const router = useRouter();
@@ -50,10 +51,21 @@ const to = computed(() => {
5051
if (!resourceColumns) return null;
5152
5253
const pkName = props.meta?.pkName
54+
const pkNames = props.meta?.pkNames?.length ? props.meta.pkNames : [pkName];
5355
5456
const isShowPage = route.name === 'resource-show' || route.name === 'resource-edit';
5557
56-
let recordId = props.record?.[pkName];
58+
// for composite primary key record id is glued from all key columns, so build it the same way as backend does
59+
const pkResource = props.resource?.columns?.some((c: any) => c.primaryKey)
60+
? props.resource
61+
: { columns: pkNames.map((name: string) => ({ name, primaryKey: true })) };
62+
63+
let recordId: any = undefined;
64+
try {
65+
recordId = props.record ? encodeRecordId(pkResource as any, props.record) : undefined;
66+
} catch (e) {
67+
// record has no values of all key columns, fall back to record id from route below
68+
}
5769
5870
if (!recordId && isShowPage) {
5971
recordId = route.params.primaryKey || route.params.id;

‎custom/ResourceIdLink.vue‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<template>
2+
<Link class="font-semibold no-underline" v-if="targetResourceId" :to="`/resource/${targetResourceId}`">
3+
{{ label }}
4+
</Link>
5+
<span v-else-if="label">{{ label }}</span>
6+
<span v-else class="text-gray-400">-</span>
7+
</template>
8+
9+
<script setup lang="ts">
10+
import { computed } from 'vue';
11+
import { Link } from '@/afcl';
12+
import { useCoreStore } from '@/stores/core';
13+
14+
const props = defineProps<{
15+
column: any;
16+
record: any;
17+
meta: any;
18+
resource: any;
19+
adminUser: any;
20+
}>();
21+
22+
const coreStore = useCoreStore();
23+
24+
const value = computed(() => props.record?.[props.column?.name] ?? null);
25+
26+
// resource might be deleted from config or excluded from the plugin, in this case we render plain text
27+
const targetResourceId = computed(() => {
28+
if (!value.value) {
29+
return null;
30+
}
31+
const knownFromEnum = (props.column?.enum || []).some((e: any) => e.value === value.value);
32+
return knownFromEnum || coreStore.resourceById?.[value.value] ? value.value : null;
33+
});
34+
35+
const label = computed(() => {
36+
if (!value.value) {
37+
return null;
38+
}
39+
return (props.column?.enum || []).find((e: any) => e.value === value.value)?.label
40+
|| coreStore.resourceById?.[value.value]?.label
41+
|| value.value;
42+
});
43+
</script>

‎index.ts‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
import dayjs from 'dayjs'
88
import utc from 'dayjs/plugin/utc.js';
99

10-
import { AdminForthPlugin, AllowedActionsEnum, AdminForthSortDirections, AdminForthDataTypes, HttpExtra, ActionCheckSource, Filters, afLogger, } from "adminforth";
10+
import { AdminForthPlugin, AllowedActionsEnum, AdminForthSortDirections, AdminForthDataTypes, HttpExtra, ActionCheckSource, Filters, afLogger, encodeRecordId, isCompositePrimaryKey, primaryKeyColumnNames, } from "adminforth";
1111
import { PluginOptions } from "./types.js";
1212

1313
dayjs.extend(utc);
@@ -103,8 +103,8 @@ export default class AuditLogPlugin extends AdminForthPlugin {
103103
return { ok: true };
104104
}
105105

106-
const recordIdFieldName = resource.columns.find((c) => c.primaryKey === true)?.name;
107-
const recordId = data?.[recordIdFieldName] || oldRecord?.[recordIdFieldName];
106+
// record id is built from all primary key columns, so resources with composite primary key are supported
107+
const recordId = encodeRecordId(resource, { ...(oldRecord || {}), ...(data || {}) });
108108
const connector = this.adminforth.connectors[resource.dataSource];
109109

110110
const newRecord = action == AllowedActionsEnum.delete ? {} : (await connector.getRecordByPrimaryKey(resource, recordId)) || {};
@@ -276,6 +276,8 @@ export default class AuditLogPlugin extends AdminForthPlugin {
276276
auditLogResourceId: this.auditLogResource,
277277
resourceColumns: this.options.resourceColumns,
278278
pkName: resource.columns.find((c) => c.primaryKey)?.name || 'id',
279+
// all primary key columns, so component can build record id of composite primary key resource
280+
pkNames: primaryKeyColumnNames(resource),
279281
title: 'Edit History'
280282
}
281283
}
@@ -345,6 +347,8 @@ export default class AuditLogPlugin extends AdminForthPlugin {
345347
auditLogResourceId: this.auditLogResource,
346348
resourceColumns: this.options.resourceColumns,
347349
pkName: resource.columns.find((c) => c.primaryKey)?.name || 'id',
350+
// all primary key columns, so component can build record id of composite primary key resource
351+
pkNames: primaryKeyColumnNames(resource),
348352
title: 'Edit History'
349353
}
350354
});
@@ -353,9 +357,26 @@ export default class AuditLogPlugin extends AdminForthPlugin {
353357
})
354358
columnToModify.enum = existingResources;
355359

360+
// make resource id clickable in show view, so user can navigate to the list of that resource
361+
columnToModify.components = columnToModify.components || {};
362+
if (!columnToModify.components.show) {
363+
columnToModify.components.show = {
364+
file: this.componentPath('ResourceIdLink.vue'),
365+
meta: {
366+
pluginInstanceId: this.pluginInstanceId,
367+
}
368+
};
369+
}
370+
356371
const recordIdColumn = resourceConfig.columns.find((c) => c.name === this.options.resourceColumns.resourceRecordIdColumnName);
357372
if (!recordIdColumn.foreignResource) {
358373
for (const resource of existingResources) {
374+
// core does not support foreignResource pointing to resource with composite primary key yet,
375+
// so such resources are still logged, but their record id is not clickable
376+
const targetResource = this.adminforth.config.resources.find((r) => r.resourceId === resource.value);
377+
if (targetResource && isCompositePrimaryKey(targetResource)) {
378+
continue;
379+
}
359380
if(!recordIdColumn.foreignResource?.polymorphicResources) {
360381
recordIdColumn.foreignResource = {
361382
polymorphicResources: [],

‎package.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
3535
},
3636
"peerDependencies": {
37-
"adminforth": "^3.7.1"
37+
"adminforth": "^3.20.0"
3838
},
3939
"release": {
4040
"plugins": [
@@ -63,7 +63,7 @@
6363
},
6464
"devDependencies": {
6565
"@types/node": "^22.10.7",
66-
"adminforth": "^3.7.1",
66+
"adminforth": "^3.20.0",
6767
"semantic-release": "^24.2.1",
6868
"semantic-release-slack-bot": "^4.0.2",
6969
"typescript": "^5.7.3"

‎pnpm-lock.yaml‎

Lines changed: 44 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)