@tekkare/romulus 0.1.106 → 0.1.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "0.1.105",
7
+ "version": "0.1.107",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -75,6 +75,22 @@ const FR = {
75
75
  "La vue a changé sans que la requête de l'alerte suive. Ouvrez la page surveillée pour la réarmer.",
76
76
  failing: 'Le dernier passage a été refusé, la référence précédente est conservée.',
77
77
  },
78
+ refusal: {
79
+ too_broad: (n: string, max: string) =>
80
+ `Cette vue est trop large pour être surveillée : ${n} documents, alors qu’une alerte en suit ${max} au maximum. Resserrez le périmètre de la vue, sur une marque ou une molécule par exemple.`,
81
+ collapse: (n: string) =>
82
+ `Les résultats se sont effondrés (${n} documents restants) : c’est un incident sur les données, pas une nouvelle. Rien n’a été signalé, l’alerte reprendra au prochain passage sain.`,
83
+ growth: (n: string) =>
84
+ `Les résultats ont brutalement augmenté (${n} documents) : c’est un incident sur les données, pas une nouvelle. Rien n’a été signalé.`,
85
+ id_overlap: (n: string) =>
86
+ `Les documents suivis ne sont plus reconnus (${n} retrouvés) : la source a probablement été réindexée. Rien n’a été signalé pour éviter d’annoncer tout le périmètre comme nouveau.`,
87
+ field_sweep: (n: string) =>
88
+ `Un même champ a changé sur ${n} documents d’un coup : c’est une modification de la source, pas une actualité. Rien n’a été signalé.`,
89
+ shard_failure:
90
+ 'La source n’a répondu que partiellement. Rien n’a été signalé : un résultat incomplet se lit comme une disparition générale.',
91
+ es_error:
92
+ 'La source n’a pas répondu. Rien n’a été signalé, l’alerte réessaiera au prochain passage.',
93
+ },
78
94
  reseeded: 'La vue a changé, l’alerte est repartie d’une nouvelle référence le',
79
95
  }
80
96
 
@@ -112,19 +128,63 @@ const EN = {
112
128
  "The view changed and the alert's query did not follow. Open the watched page to re-arm it.",
113
129
  failing: 'The last run was refused; the previous baseline is kept.',
114
130
  },
131
+ refusal: {
132
+ too_broad: (n: string, max: string) =>
133
+ `This view is too wide to watch: ${n} documents, where an alert tracks at most ${max}. Narrow the view, to one brand or molecule for instance.`,
134
+ collapse: (n: string) =>
135
+ `Results collapsed (${n} documents left): that is a data incident, not news. Nothing was reported; the alert resumes on the next healthy run.`,
136
+ growth: (n: string) =>
137
+ `Results grew abruptly (${n} documents): that is a data incident, not news. Nothing was reported.`,
138
+ id_overlap: (n: string) =>
139
+ `The tracked documents are no longer recognised (${n} found again): the source was probably reindexed. Nothing was reported, to avoid announcing the whole scope as new.`,
140
+ field_sweep: (n: string) =>
141
+ `One field changed on ${n} documents at once: that is a change to the source, not news. Nothing was reported.`,
142
+ shard_failure:
143
+ 'The source only answered partially. Nothing was reported: an incomplete result reads like a mass disappearance.',
144
+ es_error: 'The source did not answer. Nothing was reported; the alert will retry.',
145
+ },
115
146
  reseeded: 'The view changed, the alert restarted from a new baseline on',
116
147
  }
117
148
 
118
149
  const T = computed(() => (props.lang === 'en' ? EN : FR))
119
150
 
151
+ /** Nombres ecrits comme on les lit : 19 751, pas 19751. */
152
+ const number = (value: number) =>
153
+ new Intl.NumberFormat(props.lang === 'en' ? 'en-GB' : 'fr-FR').format(value)
154
+
155
+ /**
156
+ * Pourquoi le dernier passage a ete refuse, dit en clair.
157
+ *
158
+ * Le moteur garde son message brut (il nomme des shards et des index) ; la
159
+ * carte, elle, part de la CAUSE et ecrit sa propre phrase. Faute de cause
160
+ * connue, le message brut reste, valant mieux que rien.
161
+ */
162
+ const refusal = computed<string | null>(() => {
163
+ const kind = props.alert.lastErrorKind
164
+ if (!kind) return props.alert.lastError ?? null
165
+
166
+ const sentences = T.value.refusal as Record<string, any>
167
+ const sentence = sentences[kind]
168
+ if (!sentence) return props.alert.lastError ?? null
169
+ if (typeof sentence === 'string') return sentence
170
+
171
+ const count = number(props.alert.lastErrorCount ?? 0)
172
+ return kind === 'too_broad'
173
+ ? sentence(count, number(props.alert.maxTracked ?? 10000))
174
+ : sentence(count)
175
+ })
176
+
120
177
  /** Les etats qui demandent une explication, pas seulement une pastille. */
121
178
  const notice = computed(() => {
122
179
  const status = props.alert.status
123
180
  if (status === 'active') return null
181
+ // Un refus explique se suffit : redoubler « le dernier passage a ete
182
+ // refuse » avec « cette vue est trop large » dit deux fois la meme chose.
183
+ const text = status === 'failing' && refusal.value ? refusal.value : T.value.notice[status]
124
184
  return {
125
185
  tone: status === 'stale' || status === 'failing' ? 'warn' : 'info',
126
- text: T.value.notice[status],
127
- detail: status === 'failing' ? props.alert.lastError : null,
186
+ text,
187
+ detail: null as string | null,
128
188
  }
129
189
  })
130
190
 
@@ -46,6 +46,14 @@ const props = withDefaults(
46
46
  /** Nombre d'elements que la vue tient a l'ecran, s'il est connu. */
47
47
  scopeCount?: number | null
48
48
  submitting?: boolean
49
+ /**
50
+ * `edit` prefixe le formulaire avec une alerte existante et retire le
51
+ * choix de la vue : changer de vue voudrait dire recapturer une requete,
52
+ * ce qui n'est possible que depuis la page. On en cree une autre.
53
+ */
54
+ mode?: 'create' | 'edit'
55
+ /** Alerte a modifier, en mode `edit`. */
56
+ initial?: Partial<RmAlertDraft>
49
57
  lang?: 'fr' | 'en'
50
58
  }>(),
51
59
  {
@@ -54,6 +62,8 @@ const props = withDefaults(
54
62
  fields: () => [],
55
63
  scopeCount: null,
56
64
  submitting: false,
65
+ mode: 'create',
66
+ initial: undefined,
57
67
  lang: 'fr',
58
68
  },
59
69
  )
@@ -102,6 +112,8 @@ const FR = {
102
112
  cadenceHint:
103
113
  'Un plafond, pas un rythme : rien ne part quand rien n’a bougé, et la détection suit celle des données.',
104
114
  submit: 'Créer l’alerte',
115
+ save: 'Enregistrer',
116
+ reseed: 'Changer les champs suivis repart d’une nouvelle référence : vous ne serez prévenu que de ce qui bouge ensuite.',
105
117
  cancel: 'Annuler',
106
118
  baseline:
107
119
  'À la création, la référence est capturée sans rien signaler : vous ne serez prévenu que de ce qui bouge ensuite.',
@@ -141,6 +153,8 @@ const EN = {
141
153
  cadenceHint:
142
154
  'A cap, not a rhythm: nothing goes out when nothing moved, and detection follows the data.',
143
155
  submit: 'Create the alert',
156
+ save: 'Save',
157
+ reseed: 'Changing the watched fields restarts from a new baseline: you are only told about what moves afterwards.',
144
158
  cancel: 'Cancel',
145
159
  baseline:
146
160
  'On creation the baseline is captured silently: you are only told about what moves afterwards.',
@@ -165,16 +179,23 @@ const selectedView = computed(
165
179
  () => viewOptions.value.find((option) => option.id === selectedViewId.value) ?? viewOptions.value[0],
166
180
  )
167
181
 
168
- const title = ref(props.viewTitle)
169
- const triggers = ref<RmAlertTrigger[]>(['appeared'])
170
- const watchedFields = ref<string[]>([])
182
+ const editing = computed(() => props.mode === 'edit')
183
+
184
+ const title = ref(props.initial?.title ?? props.viewTitle)
185
+ const triggers = ref<RmAlertTrigger[]>(props.initial?.triggers ?? ['appeared'])
186
+ const watchedFields = ref<string[]>(
187
+ (props.initial?.watchedFields ?? []).filter((field) => field !== RM_ALERT_ANY_FIELD),
188
+ )
171
189
  /** Surveiller le document entier plutot qu'une liste de champs. */
172
- const anyField = ref(false)
173
- const emailOn = ref(false)
174
- const cadence = ref<RmAlertCadence>('weekly')
190
+ const anyField = ref(!!props.initial?.watchedFields?.includes(RM_ALERT_ANY_FIELD))
191
+ const emailOn = ref(!!props.initial?.channels?.includes('email'))
192
+ const cadence = ref<RmAlertCadence>(props.initial?.cadence ?? 'weekly')
193
+
194
+ /** Champs suivis au chargement, pour dire quand la reference va repartir. */
195
+ const initialFields = JSON.stringify([...(props.initial?.watchedFields ?? [])].sort())
175
196
 
176
197
  /** Titre libre des que l'utilisateur y touche : on ne l'ecrase plus. */
177
- const titleTouched = ref(false)
198
+ const titleTouched = ref(props.mode === 'edit')
178
199
 
179
200
  watch(
180
201
  () => props.viewTitle,
@@ -235,6 +256,21 @@ const valid = computed(
235
256
  () => title.value.trim().length > 0 && triggers.value.length > 0 && !missingFields.value,
236
257
  )
237
258
 
259
+ /**
260
+ * Les champs suivis ont bouge depuis l'ouverture.
261
+ *
262
+ * C'est le seul changement qui fait repartir l'alerte d'une nouvelle
263
+ * reference cote API : le hachage ne couvre plus les memes valeurs.
264
+ */
265
+ const fieldsChanged = computed(() => {
266
+ const next = anyField.value
267
+ ? [RM_ALERT_ANY_FIELD]
268
+ : watchesChanges.value
269
+ ? watchedFields.value
270
+ : []
271
+ return JSON.stringify([...next].sort()) !== initialFields
272
+ })
273
+
238
274
  function submit() {
239
275
  if (!valid.value) return
240
276
  emit('submit', {
@@ -268,7 +304,7 @@ function submit() {
268
304
  "
269
305
  />
270
306
 
271
- <div class="rm-alert-form__view">
307
+ <div v-if="!editing" class="rm-alert-form__view">
272
308
  <p class="rm-alert-form__label">{{ T.view }}</p>
273
309
  <p class="rm-alert-form__hint">
274
310
  <!-- L'avertissement sur l'application ne vaut que pour une vue deja
@@ -297,6 +333,11 @@ function submit() {
297
333
  <p v-if="!selectedViewId" class="rm-alert-form__hint">{{ T.screenNote }}</p>
298
334
  </div>
299
335
 
336
+ <p v-else class="rm-alert-form__view-name">
337
+ <RmIcon name="BookmarkSimple" :size="14" />
338
+ {{ T.view }} : {{ viewTitle }}
339
+ </p>
340
+
300
341
  <fieldset class="rm-alert-form__group">
301
342
  <legend class="rm-alert-form__label">{{ T.triggers }}</legend>
302
343
  <p class="rm-alert-form__hint">{{ T.triggersHint }}</p>
@@ -352,9 +393,12 @@ function submit() {
352
393
  </div>
353
394
  </fieldset>
354
395
 
355
- <p class="rm-alert-form__baseline">
396
+ <p
397
+ v-if="!editing || fieldsChanged"
398
+ class="rm-alert-form__baseline"
399
+ >
356
400
  <RmIcon name="Info" :size="14" />
357
- {{ T.baseline }}
401
+ {{ editing ? T.reseed : T.baseline }}
358
402
  </p>
359
403
 
360
404
  <div class="rm-alert-form__actions">
@@ -365,7 +409,7 @@ function submit() {
365
409
  :disabled="!valid || submitting"
366
410
  :loading="submitting"
367
411
  >
368
- {{ T.submit }}
412
+ {{ editing ? T.save : T.submit }}
369
413
  </RmButton>
370
414
  </div>
371
415
  </form>
@@ -37,8 +37,18 @@ export interface RmAlert {
37
37
  trackedCount?: number;
38
38
  /** Evenements pas encore lus. */
39
39
  unreadCount?: number;
40
- /** Message du dernier passage refuse. */
40
+ /** Message brut du dernier passage refuse. Repli, quand la cause est inconnue. */
41
41
  lastError?: string | null;
42
+ /**
43
+ * Cause du refus, telle que le moteur la nomme. C'est elle qui choisit la
44
+ * phrase affichee : un message technique en anglais n'a rien a faire sous
45
+ * les yeux d'un utilisateur.
46
+ */
47
+ lastErrorKind?: string | null;
48
+ /** Chiffre sur lequel le refus porte (documents trouves, retrouves...). */
49
+ lastErrorCount?: number | null;
50
+ /** Plafond de documents qu'une alerte peut suivre. */
51
+ maxTracked?: number | null;
42
52
  lastRunAt?: string | null;
43
53
  /** Date de la derniere remise a zero de la reference, s'il y en a eu une. */
44
54
  reseededAt?: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.106",
3
+ "version": "0.1.108",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",