Skip to content

Commit ba04091

Browse files
committed
feat: update lifecycle hooks and JSON forms renderer to improve data handling and synchronization
- Changed default dateKey from 'lastLifecycleUpdate' to 'lastSync' in lifecycle hooks service. - Enhanced trigger validation logic to correctly handle numeric values. - Updated JSON forms renderer to include entityId and schemaBodyParams for better data management. - Added new computed properties in various components to streamline data flow and improve UI responsiveness.
1 parent 0a1e743 commit ba04091

File tree

10 files changed

+192
-69
lines changed

10 files changed

+192
-69
lines changed

apps/api/src/management/lifecycle/_dto/config-rules.dto.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { IdentityLifecycleDefault } from '~/management/identities/_enums/lifecyc
2525
*/
2626
function transformTriggerToSeconds(value: number | string): number | undefined {
2727
let isValid = false
28+
const secondsInDay = 24 * 60 * 60
2829

2930
if (value === undefined || value === null) {
3031
return undefined
@@ -41,7 +42,7 @@ function transformTriggerToSeconds(value: number | string): number | undefined {
4142
* - Pour les chaînes : doit correspondre au format '\d+[dms]' (nombre suivi de d, m ou s)
4243
*/
4344
if (isNumber(value)) {
44-
isValid = value < 0
45+
isValid = value > 0
4546
} else if (isString(value)) {
4647
const timeRegex = /^\d+[dms]$/
4748
if (timeRegex.test(value)) {
@@ -62,7 +63,17 @@ function transformTriggerToSeconds(value: number | string): number | undefined {
6263
* Le signe du nombre est préservé (négatif reste négatif)
6364
*/
6465
if (isNumber(value)) {
65-
return value * 24 * 60 * 60 // Conversion jours → secondes avec préservation du signe
66+
/**
67+
* Compat:
68+
* - petites valeurs numériques (< 1 jour) sont historiquement des jours
69+
* - grandes valeurs numériques (>= 1 jour en secondes) sont considérées déjà en secondes
70+
* pour supporter les triggers explicitement fournis en secondes (ex: 2592000)
71+
*/
72+
if (value >= secondsInDay) {
73+
return value
74+
}
75+
76+
return value * secondsInDay
6677
}
6778

6879
/**
@@ -159,7 +170,7 @@ function ValidateRulesOrTrigger(validationOptions?: ValidationOptions) {
159170
* @example
160171
* {
161172
* sources: ['OFFICIAL'],
162-
* dateKey: 'lastLifecycleUpdate',
173+
* dateKey: 'lastSync',
163174
* trigger: 90, // 90 jours
164175
* target: 'MANUAL',
165176
* mutation: { status: 'archived' }
@@ -192,15 +203,15 @@ export class ConfigRulesObjectIdentitiesDTO {
192203
* Clé de date utilisée pour calculer le déclencheur temporel
193204
*
194205
* @type {string}
195-
* @default 'lastLifecycleUpdate'
206+
* @default 'lastSync'
196207
* @description Nom du champ de date dans le document d'identité à utiliser
197208
* comme référence pour calculer le délai du trigger.
198209
*
199-
* @example 'lastLifecycleUpdate', 'createdAt', 'lastModified'
210+
* @example 'lastSync', 'createdAt', 'lastModified'
200211
*/
201212
@IsOptional()
202213
@IsString()
203-
public dateKey: string = 'lastLifecycleUpdate'
214+
public dateKey: string = 'lastSync'
204215

205216
/**
206217
* Règles de filtrage conditionnelles pour l'application de la transition

apps/api/src/management/lifecycle/lifecycle-hooks.service.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,14 +277,14 @@ export class LifecycleHooksService extends AbstractLifecycleService {
277277
for (const lfr of lifecycleRules) {
278278
for (const idRule of lfr.identities) {
279279
if (typeof idRule.trigger === 'number' && (idRule.trigger > 0 || ignoreTrigger)) {
280-
const dateKey = idRule.dateKey || 'lastLifecycleUpdate'
280+
const dateKey = idRule.dateKey || 'lastSync'
281281

282282
try {
283283
const checkDate = new Date(Date.now() - (idRule.trigger * 1000))
284284
const filterDate = {}
285285
filterDate[dateKey] = { $lte: checkDate }
286286

287-
const identities = await this.identitiesService.model.find({
287+
const req = {
288288
...idRule.rules,
289289
lifecycle: {
290290
$in: idRule.sources,
@@ -294,9 +294,11 @@ export class LifecycleHooksService extends AbstractLifecycleService {
294294
// [dateKey]: {
295295
// $lte: new Date(Date.now() - (idRule.trigger * 1000)),
296296
// },
297-
})
297+
}
298+
const identities = await this.identitiesService.model.find(req)
298299
this.logger.log(`Found ${identities.length} identities to process for trigger in source <${idRule.sources}>`)
299300
this.logger.verbose(`identities process triggered`, JSON.stringify(idRule, null, 2))
301+
this.logger.verbose(`identities process request`, JSON.stringify(req, null, 2))
300302

301303
for (const identity of identities) {
302304
const updated = await this.identitiesService.model.findOneAndUpdate(

apps/web/src/components/core/jsonforms-renderer.vue

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
client-only
77
json-forms(
88
v-if="schema && uischema"
9+
:key="jsonFormsInstanceKey"
910
@change="onChange"
1011
@error="onJsonFormsError"
1112
validationMode="ValidateAndShow"
@@ -26,7 +27,8 @@ import { JsonForms } from '@jsonforms/vue'
2627
import { quasarRenderers } from '~/jsonforms'
2728
import { createAjv } from '~/jsonforms/utils/validator'
2829
import type { ErrorObject } from 'ajv'
29-
import localize from 'ajv-i18n/localize'
30+
// import localize from 'ajv-i18n/localize'
31+
import { useErrorHandling } from '~/composables/useErrorHandling'
3032
3133
export default defineNuxtComponent({
3234
name: 'SesameCoreJsonformsRendererComponent',
@@ -49,13 +51,21 @@ export default defineNuxtComponent({
4951
type: String,
5052
default: '/management/identities/validation/',
5153
},
54+
schemaBodyParams: {
55+
type: Object,
56+
default: () => ({}),
57+
},
5258
schemaName: {
5359
type: String,
5460
},
5561
modelValue: {
5662
type: Object,
5763
default: () => ({}),
5864
},
65+
entityId: {
66+
type: [String, Number],
67+
default: null,
68+
},
5969
validations: {
6070
type: Object || null,
6171
default: {},
@@ -71,27 +81,12 @@ export default defineNuxtComponent({
7181
},
7282
setup(props) {
7383
const renderers = Object.freeze([...quasarRenderers])
84+
const { $http } = useNuxtApp()
7485
75-
const employeeType = computed(() => {
76-
// `modelValue` can be the full identity object or directly inetOrgPerson.
77-
return props.modelValue?.employeeType || props.modelValue?.inetOrgPerson?.employeeType || 'LOCAL'
78-
})
79-
80-
81-
if (!props.schemaName && (!props.manualSchema || !props.manualUiSchema)) {
82-
throw new Error('Either schemaName or manualSchema/manualUiSchema props must be provided')
83-
}
84-
85-
const createTranslator = (locale) => (key: string, defaultMessage: string | undefined, context: { error: ErrorObject }) => {
86-
const regex = /^(?!.*\s)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+)*$/
87-
if (regex.test(key) || defaultMessage || !context.error.schema) {
86+
const createTranslator = (locale: string) => {
87+
return (key: string, defaultMessage: string | undefined, context: { error: ErrorObject }) => {
8888
return defaultMessage
8989
}
90-
91-
const err = [context.error]
92-
localize[locale](err)
93-
94-
return err[0].message
9590
}
9691
9792
if (!props.schemaName) {
@@ -116,27 +111,44 @@ export default defineNuxtComponent({
116111
method: 'GET',
117112
})
118113
119-
const {
120-
data: resultUi,
121-
pending: pendingUi,
122-
error: errorUi,
123-
refresh: refreshUi,
124-
} = useHttp<any>(`${props.baseUrlSchema}${props.schemaName}`, {
125-
method: 'POST',
126-
params: {
127-
mode: props.mode,
128-
},
129-
query: {
130-
mode: props.mode,
131-
},
132-
watch: [employeeType],
133-
body: computed(() => ({
134-
employeeType: employeeType.value,
135-
})),
136-
})
114+
const schemaBodyParamsSnapshot = computed(() => JSON.stringify(props.schemaBodyParams ?? {}))
115+
const schemaBodyParams = computed(() => JSON.parse(schemaBodyParamsSnapshot.value))
116+
const resultUi = ref<any>(null)
117+
const pendingUi = ref(false)
118+
const errorUi = ref<any>(null)
119+
let fetchUiRequestId = 0
120+
121+
const refreshUi = async () => {
122+
const requestId = ++fetchUiRequestId
123+
pendingUi.value = true
124+
errorUi.value = null
125+
try {
126+
const response = await $http.$post(`${props.baseUrlSchema}${props.schemaName}`, {
127+
query: {
128+
mode: props.mode,
129+
},
130+
body: schemaBodyParams.value,
131+
})
132+
133+
// Ignore stale responses when multiple requests overlap.
134+
if (requestId === fetchUiRequestId) {
135+
resultUi.value = response
136+
}
137+
} catch (error: any) {
138+
if (requestId === fetchUiRequestId) {
139+
errorUi.value = error
140+
}
141+
} finally {
142+
if (requestId === fetchUiRequestId) {
143+
pendingUi.value = false
144+
}
145+
}
146+
}
147+
148+
watch([() => props.schemaName, () => props.mode, schemaBodyParamsSnapshot], refreshUi, { immediate: true })
137149
138150
const schema = computed(() => result.value?.data)
139-
const uischema = computed(() => resultUi.value?.data)
151+
const uischema = computed(() => resultUi.value?.data ?? resultUi.value)
140152
141153
return {
142154
schema,
@@ -214,6 +226,9 @@ export default defineNuxtComponent({
214226
},
215227
}
216228
},
229+
jsonFormsInstanceKey(): string {
230+
return [this.schemaName || 'manual', this.mode, this.entityId || this.$route.fullPath].join(':')
231+
},
217232
},
218233
})
219234
</script>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<template lang="pug">
2+
q-bar.row.items-left
3+
span
4+
q-tooltip.text-body2.bg-green-8.text-white(anchor="top middle" self="bottom middle")
5+
span Créé par: {{ data.metadata.createdBy }}
6+
br
7+
span Créé le: {{ $dayjs(data.metadata.createdAt).format('DD/MM/YYYY HH:mm:ss') }}
8+
q-icon(name="mdi-database-plus" size="16px" left)
9+
small {{ data.metadata.createdBy || 'N/A' }},&nbsp;
10+
small &nbsp;{{ $dayjs(data.metadata.createdAt || 'N/A').fromNow() }}
11+
q-separator.q-mx-sm(vertical inset)
12+
span
13+
q-tooltip.text-body2.bg-orange-8.text-white(anchor="top middle" self="bottom middle" )
14+
span Dernière modification par: {{ data.metadata.lastUpdatedBy }}
15+
br
16+
span Dernière modification le: {{ $dayjs(data.metadata.lastUpdatedAt).format('DD/MM/YYYY HH:mm:ss') }}
17+
q-icon(name="mdi-database-clock" size="16px" left)
18+
small {{ data.metadata.lastUpdatedBy || 'N/A' }},&nbsp;
19+
small {{ $dayjs(data.metadata.lastUpdatedAt || 'N/A').fromNow() }}
20+
q-separator.q-mx-sm(vertical inset v-if="$slots.hasOwnProperty('actions-left')")
21+
slot(name="actions-left")
22+
q-space
23+
slot(name="actions-right")
24+
</template>
25+
26+
<script lang="ts">
27+
export default defineNuxtComponent({
28+
name: 'SesameCorePanInformationsComponent',
29+
props: {
30+
data: {
31+
type: Object,
32+
required: true,
33+
},
34+
},
35+
})
36+
</script>

apps/web/src/pages/identities/fusion.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,19 @@ q-page.container
8383
template(#items="{ tabs }")
8484
q-tab-panel.q-pa-none(name="inetOrgPerson")
8585
sesame-core-jsonforms-renderer(
86+
:key="`inetOrgPerson:${identity.inetOrgPerson.employeeType ?? ''}`"
8687
schemaName="inetOrgPerson"
88+
v-model:entityId="identity._id"
89+
:schema-body-params="schemaBodyParams"
8790
v-model="identity.inetOrgPerson"
8891
v-model:validations="validations"
8992
)
9093
q-tab-panel.q-pa-none(v-for="t in tabs" :key="t" :name="t")
9194
sesame-core-jsonforms-renderer(
95+
:key="`${t}:${identity.inetOrgPerson.employeeType ?? ''}`"
9296
:schema-name="t"
97+
v-model:entityId="identity._id"
98+
:schema-body-params="schemaBodyParams"
9399
v-model="identity.additionalFields.attributes[t]"
94100
v-model:validations="validations"
95101
)
@@ -171,6 +177,11 @@ export default defineNuxtComponent({
171177
}
172178
},
173179
computed: {
180+
schemaBodyParams() {
181+
return {
182+
employeeType: this.identity?.inetOrgPerson?.employeeType,
183+
}
184+
},
174185
includeIgnoredFilter(): string {
175186
return (this.$route.query['includeIgnored'] as string) || '0'
176187
},

apps/web/src/pages/identities/table/[_id]/index.vue

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -162,23 +162,50 @@
162162
top-offset='38px'
163163
)
164164
template(#items="{ tabs }")
165-
q-tab-panel.q-pa-none(name="inetOrgPerson")
166-
sesame-core-jsonforms-renderer(
167-
schemaName="inetOrgPerson"
168-
v-model="identity.inetOrgPerson"
169-
v-model:validations="validations"
170-
:readonly="identity.state === IdentityState.TO_SYNC || !hasPermission('/management/identities', 'update')"
171-
:mode="isNew ? 'create' : 'update'"
165+
q-tab-panel.q-pa-none.column.no-wrap.full-height(name="inetOrgPerson")
166+
.col.overflow-auto(style="min-height: 0")
167+
sesame-core-jsonforms-renderer.full-width(
168+
:key="`inetOrgPerson:${schemaBodyParams.employeeType ?? ''}`"
169+
schemaName="inetOrgPerson"
170+
:entityId="identity._id"
171+
:schema-body-params="schemaBodyParams"
172+
v-model="identity.inetOrgPerson"
173+
v-model:validations="validations"
174+
:readonly="identity.state === IdentityState.TO_SYNC || !hasPermission('/management/identities', 'update')"
175+
:mode="isNew ? 'create' : 'update'"
176+
)
177+
sesame-core-pan-informations(
178+
v-if="identity?._id"
179+
:data="identity"
172180
)
173-
q-tab-panel.q-pa-none(v-for="t in tabs" :key="t" :name="t")
174-
sesame-core-jsonforms-renderer(
175-
:schema-name="t"
176-
v-model="identity.additionalFields.attributes[t]"
177-
v-model:validations="validations"
178-
:readonly="identity.state === IdentityState.TO_SYNC || !hasPermission('/management/identities', 'update')"
179-
:mode="isNew ? 'create' : 'update'"
181+
template(#actions-right)
182+
span
183+
q-tooltip.text-body2.bg-lime-8.text-white(anchor="top middle" self="bottom middle")
184+
span Dernier import: {{ identity.lastSync ? $dayjs(identity.lastSync).format('DD/MM/YYYY HH:mm:ss') : 'N/A' }}
185+
q-icon(name="mdi-database-import" size="16px" left)
186+
small {{ identity.lastSync ? $dayjs(identity.lastSync).fromNow() : 'N/A' }}
187+
q-separator.q-mx-sm(vertical inset)
188+
span
189+
q-tooltip.text-body2.bg-purple-8.text-white(anchor="top middle" self="bottom middle")
190+
span Dernière synchronisation: {{ identity.lastBackendSync ? $dayjs(identity.lastBackendSync).format('DD/MM/YYYY HH:mm:ss') : 'N/A' }}
191+
q-icon(name="mdi-database-export" size="16px" left)
192+
small {{ identity.lastBackendSync ? $dayjs(identity.lastBackendSync).fromNow() : 'N/A' }}
193+
q-tab-panel.q-pa-none.column.no-wrap.full-height(v-for="t in tabs" :key="t" :name="t")
194+
.col.overflow-auto(style="min-height: 0")
195+
sesame-core-jsonforms-renderer.full-width(
196+
:key="`${t}:${schemaBodyParams.employeeType ?? ''}`"
197+
:schema-name="t"
198+
:entityId="identity._id"
199+
:schema-body-params="schemaBodyParams"
200+
v-model="identity.additionalFields.attributes[t]"
201+
v-model:validations="validations"
202+
:readonly="identity.state === IdentityState.TO_SYNC || !hasPermission('/management/identities', 'update')"
203+
:mode="isNew ? 'create' : 'update'"
204+
)
205+
sesame-core-pan-informations(
206+
v-if="identity?._id"
207+
:data="identity"
180208
)
181-
182209
q-dialog(v-model="resetPasswordModal" persistent medium)
183210
q-card(style="width:800px")
184211
q-toolbar.bg-primary(flat)
@@ -230,6 +257,11 @@ export default defineNuxtComponent({
230257
validations() {
231258
return this.identity?.additionalFields?.validations || {}
232259
},
260+
schemaBodyParams() {
261+
return {
262+
employeeType: this.identity?.inetOrgPerson?.employeeType,
263+
}
264+
},
233265
hasValidations() {
234266
if (this.validations) {
235267
for (const field in this.validations) {

0 commit comments

Comments
 (0)