From 8e745ed968525016fc14a3f5b541c5cba1c049f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Sun, 27 Sep 2026 16:31:08 +0100 Subject: [PATCH 1/5] fix: type the audit-log renderers (CM-1528) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gašper Grom --- .../config/audit-logs/log-rendering/index.ts | 33 ++++++++++++------- .../log-rendering/integrations-connect.ts | 11 +++++-- .../log-rendering/integrations-reconnect.ts | 11 +++++-- .../log-rendering/members-create.ts | 7 +++- .../log-rendering/members-edit-identities.ts | 5 ++- .../members-edit-manual-affiliation.ts | 26 ++++++++++----- .../members-edit-organizations.ts | 24 +++++++++----- .../log-rendering/members-edit-profile.ts | 15 ++++++--- .../audit-logs/log-rendering/members-merge.ts | 4 +-- .../log-rendering/members-unmerge.ts | 4 +-- .../log-rendering/organizations-create.ts | 7 +++- .../organizations-edit-identities.ts | 5 ++- .../organizations-edit-profile.ts | 8 +++-- .../log-rendering/organizations-merge.ts | 4 +-- .../log-rendering/organizations-unmerge.ts | 4 +-- .../components/logs/sections/log-changes.vue | 4 +-- .../src/modules/lf/segments/types/AuditLog.ts | 8 ++--- 17 files changed, 121 insertions(+), 59 deletions(-) diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/index.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/index.ts index 191c6ecd6c..e58127baad 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/index.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/index.ts @@ -15,19 +15,28 @@ import organizationsEditProfile from './organizations-edit-profile'; import organizationsMerge from './organizations-merge'; import organizationsUnmerge from './organizations-unmerge'; -export interface LogRenderingConfig { +export interface LogChanges { + removals: string[]; + additions: string[]; + changes: string[]; +} + +// Merge/unmerge payloads: member or organization snapshots; merged `primary` holds only changed fields. +interface MergeLogEntity { + id?: string; + displayName?: string; +} + +export interface MergeLogState { + primary: MergeLogEntity; + secondary?: MergeLogEntity; +} + +export interface LogRenderingConfig { label: string; - description: (log: AuditLog) => string; - properties?: (log: AuditLog) => {label: string, value: string;}[]; - changes?: (log: AuditLog) => Promise<{ - removals: string[] - additions: string[] - changes: string[] - } | null> | { - removals: string[] - additions: string[] - changes: string[] - } | null + description(log: AuditLog): string; + properties?(log: AuditLog): {label: string, value: string;}[]; + changes?(log: AuditLog): Promise | LogChanges | null; } export const logRenderingConfig: Record = { diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/integrations-connect.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/integrations-connect.ts index 378eb9133c..f7ebee30cf 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/integrations-connect.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/integrations-connect.ts @@ -1,18 +1,23 @@ import type { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; import { lfIdentities } from '@/config/identities'; -const integrationsConnect: LogRenderingConfig = { +// Integration row written by IntegrationRepository.create; oldState is {} for create actions. +interface IntegrationState { + platform?: string; +} + +const integrationsConnect: LogRenderingConfig = { label: 'Integration connected', changes: () => null, description: (log) => { - const integration = lfIdentities[log.newState?.platform || log.oldState?.platform]; + const integration = lfIdentities[log.newState?.platform || log.oldState?.platform || '']; if (integration) { return `Integration: ${integration.name}`; } return ''; }, properties: (log) => { - const integration = lfIdentities[log.newState?.platform || log.oldState?.platform]; + const integration = lfIdentities[log.newState?.platform || log.oldState?.platform || '']; if (integration) { return [{ label: 'Integration', diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/integrations-reconnect.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/integrations-reconnect.ts index efa3b3d0f1..98f26cf6f2 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/integrations-reconnect.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/integrations-reconnect.ts @@ -1,18 +1,23 @@ import type { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; import { lfIdentities } from '@/config/identities'; -const integrationsReconnect: LogRenderingConfig = { +// Integration row written by IntegrationRepository.create; oldState is {} for create actions. +interface IntegrationState { + platform?: string; +} + +const integrationsReconnect: LogRenderingConfig = { label: 'Integration re-connected', changes: () => null, description: (log) => { - const integration = lfIdentities[log.newState?.platform || log.oldState?.platform]; + const integration = lfIdentities[log.newState?.platform || log.oldState?.platform || '']; if (integration) { return `Integration: ${integration.name}`; } return ''; }, properties: (log) => { - const integration = lfIdentities[log.newState?.platform || log.oldState?.platform]; + const integration = lfIdentities[log.newState?.platform || log.oldState?.platform || '']; if (integration) { return [{ label: 'Integration', diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-create.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-create.ts index 4001b277ee..e59e837034 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-create.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-create.ts @@ -1,6 +1,11 @@ import type { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; -const membersCreate: LogRenderingConfig = { +// Member snapshot written by MemberRepository.create and the public createMember API. +interface MemberCreateState { + displayName?: string; +} + +const membersCreate: LogRenderingConfig = { label: 'Profile created', changes: () => null, description: (log) => { diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-identities.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-identities.ts index 5e9965c2f0..2d9669c5d9 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-identities.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-identities.ts @@ -1,7 +1,10 @@ import type { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; import { lfIdentities } from '@/config/identities'; -const membersEditIdentities: LogRenderingConfig = { +// Platform -> usernames map this renderer reads; current backend writers capture identity arrays. +type MemberIdentitiesState = Record; + +const membersEditIdentities: LogRenderingConfig = { label: 'Profile identities updated', changes: (log) => { const removals = []; diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-manual-affiliation.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-manual-affiliation.ts index 37f0ea6845..3a1a17049b 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-manual-affiliation.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-manual-affiliation.ts @@ -1,9 +1,17 @@ -import type { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; +import type { LogChanges, LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; import { OrganizationService } from '@/modules/organization/organization-service'; import { LfService } from '@/modules/lf/segments/lf-segments-service'; import { dateHelper } from '@/shared/date-helper/date-helper'; -const formatDateRange = (dateStart, dateEnd) => { +// Rows written by MemberRepository.setAffiliations and patchProjectAffiliation (memberSegmentAffiliations). +interface MemberAffiliationStateRow { + organizationId: string | null; + segmentId: string; + dateStart?: string | null; + dateEnd?: string | null; +} + +const formatDateRange = (dateStart?: string | null, dateEnd?: string | null) => { // eslint-disable-next-line no-nested-ternary const dateStartFormat = dateStart ? dateHelper(dateStart).utc().format('MMMM YYYY') @@ -15,10 +23,10 @@ const formatDateRange = (dateStart, dateEnd) => { return `${dateStartFormat} -> ${dateEndFormat}`; }; -const membersEditManualAffiliation: LogRenderingConfig = { +const membersEditManualAffiliation: LogRenderingConfig = { label: 'Profile affiliation updated', changes: async (log) => { - const changes = { + const changes: LogChanges = { removals: [], additions: [], changes: [], @@ -41,15 +49,15 @@ const membersEditManualAffiliation: LogRenderingConfig = { ]), ]; - const orgs = await OrganizationService.listByIds(orgIds); - const segments = await LfService.listSegmentsByIds(segmentIds); + const orgs: { id: string; displayName: string }[] = await OrganizationService.listByIds(orgIds); + const segments: { id: string; name: string }[] = await LfService.listSegmentsByIds(segmentIds); - const orgById = orgs.reduce((obj, org) => ({ + const orgById = orgs.reduce>((obj, org) => ({ ...obj, [org.id]: org.displayName, }), {}); - const segmentById = segments.reduce((obj, org) => ({ + const segmentById = segments.reduce>((obj, org) => ({ ...obj, [org.id]: org.name, }), {}); @@ -66,7 +74,7 @@ const membersEditManualAffiliation: LogRenderingConfig = { ); } else { const newOrg = newStateMap.get(org.organizationId); - if (org.dateStart !== newOrg.dateStart || org.dateEnd !== newOrg.dateEnd || org.segmentId !== newOrg.segmentId) { + if (newOrg && (org.dateStart !== newOrg.dateStart || org.dateEnd !== newOrg.dateEnd || org.segmentId !== newOrg.segmentId)) { changes.changes.push( `${org.organizationId ? (orgById[org.organizationId]) : 'Individual'} :
${org.segmentId ? segmentById[org.segmentId] : 'None'} (${formatDateRange(org.dateStart, org.dateEnd)}) diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-organizations.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-organizations.ts index 0ae75231bd..91b031eabd 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-organizations.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-organizations.ts @@ -1,8 +1,16 @@ -import type { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; +import type { LogChanges, LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; import { OrganizationService } from '@/modules/organization/organization-service'; import { dateHelper } from '@/shared/date-helper/date-helper'; -const formatDateRange = (dateStart, dateEnd) => { +// Rows written by CommonMemberService.updateMemberOrganizations (memberOrganizations). +interface MemberOrganizationStateRow { + organizationId: string; + title?: string | null; + dateStart: string | null; + dateEnd: string | null; +} + +const formatDateRange = (dateStart: string | null, dateEnd: string | null) => { // eslint-disable-next-line no-nested-ternary const dateStartFormat = dateStart ? dateHelper(dateStart) @@ -18,10 +26,10 @@ const formatDateRange = (dateStart, dateEnd) => { return `${dateStartFormat} -> ${dateEndFormat}`; }; -const membersEditOrganizations: LogRenderingConfig = { +const membersEditOrganizations: LogRenderingConfig = { label: 'Profile work experience updated', changes: async (log) => { - const changes = { + const changes: LogChanges = { removals: [], additions: [], changes: [], @@ -41,8 +49,8 @@ const membersEditOrganizations: LogRenderingConfig = { ]), ]; - const orgs = await OrganizationService.listByIds(orgIds); - const orgById = orgs.reduce((obj, org) => ({ + const orgs: { id: string; displayName: string }[] = await OrganizationService.listByIds(orgIds); + const orgById = orgs.reduce>((obj, org) => ({ ...obj, [org.id]: org.displayName, }), {}); @@ -53,9 +61,9 @@ const membersEditOrganizations: LogRenderingConfig = { changes.removals.push(`Organization: ${org.organizationId ? (orgById[org.organizationId]) : 'Individual'}`); } else { const newOrg = newStateMap.get(org.organizationId); - if ( + if (newOrg && ( formatDateRange(org.dateStart, org.dateEnd) !== formatDateRange(newOrg.dateStart, newOrg.dateEnd) - || (org.title || '') !== (newOrg.title || '')) { + || (org.title || '') !== (newOrg.title || ''))) { changes.changes.push(`Organization: ${org.organizationId ? (orgById[org.organizationId]) : 'Individual'}
${org.title ? `${org.title}: ` : ''}${formatDateRange(org.dateStart, org.dateEnd)}
${newOrg.title ? `${org.title}: ` : ''}${formatDateRange(newOrg.dateStart, newOrg.dateEnd)} diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-profile.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-profile.ts index 853d47eb84..b6e7d5eedc 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-profile.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-edit-profile.ts @@ -19,17 +19,24 @@ function flattenObject(obj) { return flattenedObj; } -const membersEditProfile: LogRenderingConfig = { +// Member row (or { attributes }) written by MemberRepository.update and MemberAttributesService.update. +interface MemberProfileState { + attributes?: Record>; + [key: string]: unknown; +} + +const membersEditProfile: LogRenderingConfig = { label: 'Profile updated', changes: (log) => { const additions: any[] = []; const removals: any[] = []; const changes: any[] = []; - const oldState = { ...log.oldState, ...flattenObject(log.oldState.attributes) }; - const newState = { ...log.newState, ...flattenObject(log.newState.attributes) }; + const oldState: Record = { ...log.oldState, ...flattenObject(log.oldState.attributes) }; + const newState: Record = { ...log.newState, ...flattenObject(log.newState.attributes) }; const diff = { - ...log.diff, + // diff is the deep-object-diff of the two states, always an object. + ...(log.diff as Record), ...{ ...flattenObject(log.oldState.attributes), ...flattenObject(log.newState.attributes) }, }; delete oldState.attributes; diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-merge.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-merge.ts index f9c00a024e..0d5d3ed970 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-merge.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-merge.ts @@ -1,6 +1,6 @@ -import type { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; +import type { LogRenderingConfig, MergeLogState } from '@/modules/lf/config/audit-logs/log-rendering/index'; -const membersMerge: LogRenderingConfig = { +const membersMerge: LogRenderingConfig = { label: 'Profiles merged', changes: (log) => { const primary = log.oldState?.primary; diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-unmerge.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-unmerge.ts index 45d5823e04..12b044239d 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-unmerge.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-unmerge.ts @@ -1,6 +1,6 @@ -import type { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; +import type { LogRenderingConfig, MergeLogState } from '@/modules/lf/config/audit-logs/log-rendering/index'; -const membersMerge: LogRenderingConfig = { +const membersMerge: LogRenderingConfig = { label: 'Profiles unmerged', changes: (log) => { const primary = log.oldState?.primary; diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-create.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-create.ts index 7b23695f1a..511d0a8332 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-create.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-create.ts @@ -1,6 +1,11 @@ import type { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; -const organizationsCreate: LogRenderingConfig = { +// Organization snapshot written by OrganizationRepository.create and the public createOrganization API. +interface OrganizationCreateState { + displayName?: string; +} + +const organizationsCreate: LogRenderingConfig = { label: 'Organization created', changes: () => null, description: (log) => { diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-edit-identities.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-edit-identities.ts index 332d49d116..4775cc4ea7 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-edit-identities.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-edit-identities.ts @@ -1,7 +1,10 @@ import type { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; import { lfIdentities } from '@/config/identities'; -const organizationsEditIdentities: LogRenderingConfig = { +// Platform -> identities map written by OrganizationRepository.update (convertIdentitiesForAudit). +type OrganizationIdentitiesState = Record; + +const organizationsEditIdentities: LogRenderingConfig = { label: 'Organization identities updated', changes: ({ oldState, newState }) => { const additions: any[] = []; diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-edit-profile.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-edit-profile.ts index 6817ae3874..2a560dc209 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-edit-profile.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-edit-profile.ts @@ -5,14 +5,18 @@ function camelCaseToName(camelCase) { .replace(/^./, (str) => str.toUpperCase()); } -const organizationsEditProfile: LogRenderingConfig = { +// Organization row written by OrganizationRepository.update. +type OrganizationProfileState = Record; + +const organizationsEditProfile: LogRenderingConfig = { label: 'Organization profile updated', changes: ({ oldState, newState, diff }) => { const additions = []; const removals = []; const changes = []; - Object.keys(diff).forEach((key) => { + // diff is the deep-object-diff of the two states, always an object. + Object.keys(diff as Record).forEach((key) => { const keyName = camelCaseToName(key); if (!!oldState[key] && !newState[key] && newState[key] !== undefined) { const display = typeof oldState[key] === 'object' ? JSON.stringify(oldState[key]) : `${oldState[key]}`; diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-merge.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-merge.ts index 1f14c08370..653d5435ac 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-merge.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-merge.ts @@ -1,6 +1,6 @@ -import type { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; +import type { LogRenderingConfig, MergeLogState } from '@/modules/lf/config/audit-logs/log-rendering/index'; -const organizationsMerge: LogRenderingConfig = { +const organizationsMerge: LogRenderingConfig = { label: 'Organizations merged', changes: (log) => { const primary = log.oldState?.primary; diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-unmerge.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-unmerge.ts index 7f2775dc23..cde326068f 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-unmerge.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-unmerge.ts @@ -1,6 +1,6 @@ -import type { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; +import type { LogRenderingConfig, MergeLogState } from '@/modules/lf/config/audit-logs/log-rendering/index'; -const organizationsMerge: LogRenderingConfig = { +const organizationsMerge: LogRenderingConfig = { label: 'Organizations unmerged', changes: (log) => { const primary = log.oldState?.primary; diff --git a/frontend/src/modules/lf/segments/components/logs/sections/log-changes.vue b/frontend/src/modules/lf/segments/components/logs/sections/log-changes.vue index d80bfc87cb..130dc3e2cc 100644 --- a/frontend/src/modules/lf/segments/components/logs/sections/log-changes.vue +++ b/frontend/src/modules/lf/segments/components/logs/sections/log-changes.vue @@ -33,14 +33,14 @@