@raclettejs/workbench 0.1.34 → 0.1.36-canary.0
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/CHANGELOG.md +18 -2
- package/config/compositions.js +18 -0
- package/config/interactionLinks.js +19 -0
- package/i18n/de-DE.json +15 -1
- package/i18n/en-EU.json +52 -1
- package/i18n/sk-SK.json +15 -1
- package/package.json +3 -4
- package/packages.json +3 -1
- package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionConfiguration.vue +96 -14
- package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionIntegration.vue +29 -1
- package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetRenderer.vue +1 -6
- package/plugins/pacifico__compositions/frontend/composables/prefillInteractionLinkFromComposition.ts +54 -0
- package/plugins/pacifico__compositions/frontend/widgets/CompositionCreateWidget.vue +145 -9
- package/plugins/pacifico__compositions/frontend/widgets/CompositionEditWidget.vue +11 -2
- package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +26 -6
- package/plugins/pacifico__core/frontend/generated-config.ts +22 -0
- package/plugins/pacifico__interactionLinks/frontend/components/InteractionLinkConfiguration.vue +3 -65
- package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkListWidget.vue +1 -1
- package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +21 -4
- package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +1 -1
- package/plugins/pacifico__users/frontend/widgets/UserListWidget.vue +1 -1
- package/plugins/raclette__cache/backend/helpers/cacheEntryBuilder.ts +94 -0
- package/plugins/raclette__cache/backend/helpers/cacheEntrySanitizer.ts +86 -0
- package/plugins/raclette__cache/backend/helpers/valkeyExplorer.ts +89 -0
- package/plugins/raclette__cache/backend/index.ts +21 -0
- package/plugins/raclette__cache/backend/routes/route.cache.cacheEntries.get.ts +118 -0
- package/plugins/raclette__cache/backend/routes/route.cache.cacheEntry.get.ts +91 -0
- package/plugins/raclette__cache/backend/routes.ts +8 -0
- package/plugins/raclette__cache/frontend/components/TableColumnConfigDialog.vue +95 -0
- package/plugins/raclette__cache/frontend/composables/useTableColumnConfig.ts +66 -0
- package/plugins/raclette__cache/frontend/generated-config.ts +33 -0
- package/plugins/raclette__cache/frontend/utils/cacheValueTableRows.ts +340 -0
- package/plugins/raclette__cache/frontend/widgets/CacheListWidget.vue +742 -0
- package/plugins/raclette__cache/raclette.plugin.ts +8 -0
- package/services/frontend/src/app/components/dynamicForm/DynamicForm.vue +3 -1
- package/services/frontend/src/app/components/interactionLink/InteractionLinkBehaviorForm.vue +99 -0
- package/services/frontend/src/app/components/stepNavigator/StepNavigator.vue +175 -24
- package/services/frontend/src/app/components/stepNavigator/StepNavigatorTypes.ts +18 -0
- package/services/frontend/src/app/composables/useInteractionLinkBehaviorFields.ts +83 -0
- package/services/frontend/src/app/composables/useRacletteUserIsAdmin.ts +25 -0
- package/services/frontend/src/orchestrator/assets/styles/themes/dark.ts +1 -0
- package/services/frontend/src/orchestrator/assets/styles/themes/light.ts +1 -1
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
export type ValueTableRow = Record<string, string>
|
|
2
|
+
|
|
3
|
+
/** Minimal cache row fields needed to build the value table. */
|
|
4
|
+
export type CacheEntryValueSource = {
|
|
5
|
+
key: string
|
|
6
|
+
value: string
|
|
7
|
+
valueParsed?: unknown
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type ValueTableHeader = {
|
|
11
|
+
title: string
|
|
12
|
+
key: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const CACHE_KEY_COLUMN = "_cacheKey"
|
|
16
|
+
export const MAP_ENTRY_KEY_COLUMN = "entryKey"
|
|
17
|
+
const SCALAR_COLUMN = "value"
|
|
18
|
+
const RAW_COLUMN = "raw"
|
|
19
|
+
|
|
20
|
+
const stringifyCell = (value: unknown): string => {
|
|
21
|
+
if (value === null) {
|
|
22
|
+
return "null"
|
|
23
|
+
}
|
|
24
|
+
if (value === undefined) {
|
|
25
|
+
return ""
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return value.length === 0 ? "" : JSON.stringify(value)
|
|
29
|
+
}
|
|
30
|
+
if (isPlainObject(value)) {
|
|
31
|
+
return Object.keys(value).length === 0 ? "" : JSON.stringify(value)
|
|
32
|
+
}
|
|
33
|
+
if (typeof value === "object") {
|
|
34
|
+
return JSON.stringify(value)
|
|
35
|
+
}
|
|
36
|
+
return String(value)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const tryParseJson = (value: string): unknown | undefined => {
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(value)
|
|
42
|
+
} catch {
|
|
43
|
+
return undefined
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
48
|
+
value !== null && typeof value === "object" && !Array.isArray(value)
|
|
49
|
+
|
|
50
|
+
const isScalarValue = (value: unknown): value is string | number | boolean | null =>
|
|
51
|
+
value === null ||
|
|
52
|
+
typeof value === "string" ||
|
|
53
|
+
typeof value === "number" ||
|
|
54
|
+
typeof value === "boolean"
|
|
55
|
+
|
|
56
|
+
const MAP_ENTRY_KEY_PATTERN =
|
|
57
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
58
|
+
|
|
59
|
+
const isMapEntryKey = (key: string): boolean => MAP_ENTRY_KEY_PATTERN.test(key)
|
|
60
|
+
|
|
61
|
+
const isHomogeneousObjectMap = (value: Record<string, unknown>): boolean => {
|
|
62
|
+
const objectValues = Object.values(value)
|
|
63
|
+
if (objectValues.length === 0) {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
return (
|
|
67
|
+
objectValues.every(isPlainObject) || objectValues.every(isScalarValue)
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const headersFromKeys = (keys: string[]): ValueTableHeader[] =>
|
|
72
|
+
keys.map((key) => ({ title: key, key }))
|
|
73
|
+
|
|
74
|
+
const sortedKeys = (keys: Iterable<string>): string[] =>
|
|
75
|
+
[...keys].sort((a, b) => a.localeCompare(b))
|
|
76
|
+
|
|
77
|
+
const rowFromRecord = (
|
|
78
|
+
record: Record<string, unknown>,
|
|
79
|
+
columns: string[],
|
|
80
|
+
): ValueTableRow => {
|
|
81
|
+
const row: ValueTableRow = {}
|
|
82
|
+
for (const column of columns) {
|
|
83
|
+
row[column] = column in record ? stringifyCell(record[column]) : ""
|
|
84
|
+
}
|
|
85
|
+
return row
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const collectKeysFromObjects = (items: Record<string, unknown>[]): string[] => {
|
|
89
|
+
const columns = new Set<string>()
|
|
90
|
+
for (const item of items) {
|
|
91
|
+
for (const key of Object.keys(item)) {
|
|
92
|
+
columns.add(key)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return sortedKeys(columns)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Items inside one cache value that share the same object shape (array or object map). */
|
|
99
|
+
export const unwrapValueCollection = (
|
|
100
|
+
parsed: unknown | undefined,
|
|
101
|
+
raw?: string,
|
|
102
|
+
): Record<string, unknown>[] | null => {
|
|
103
|
+
const value = parsed ?? (raw !== undefined ? tryParseJson(raw) : undefined)
|
|
104
|
+
if (value === undefined) {
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
return value.every(isPlainObject) ? value : null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!isPlainObject(value)) {
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const objectKeys = Object.keys(value)
|
|
117
|
+
const objectValues = Object.values(value)
|
|
118
|
+
if (objectValues.length > 0 && objectValues.every(isPlainObject)) {
|
|
119
|
+
if (objectKeys.length > 0 && objectKeys.every(isMapEntryKey)) {
|
|
120
|
+
return Object.entries(value).map(([entryKey, entryValue]) => ({
|
|
121
|
+
...(entryValue as Record<string, unknown>),
|
|
122
|
+
[MAP_ENTRY_KEY_COLUMN]: entryKey,
|
|
123
|
+
}))
|
|
124
|
+
}
|
|
125
|
+
return objectValues
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (
|
|
129
|
+
objectKeys.length > 0 &&
|
|
130
|
+
objectKeys.every(isMapEntryKey) &&
|
|
131
|
+
objectValues.every(isScalarValue)
|
|
132
|
+
) {
|
|
133
|
+
return Object.entries(value).map(([entryKey, entryValue]) => ({
|
|
134
|
+
[MAP_ENTRY_KEY_COLUMN]: entryKey,
|
|
135
|
+
[SCALAR_COLUMN]: entryValue,
|
|
136
|
+
}))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (objectKeys.length === 1) {
|
|
140
|
+
const nested = value[objectKeys[0]]
|
|
141
|
+
if (Array.isArray(nested) && nested.every(isPlainObject)) {
|
|
142
|
+
return nested
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const tableFromCollection = (
|
|
150
|
+
items: Record<string, unknown>[],
|
|
151
|
+
cacheKey?: string,
|
|
152
|
+
): { rows: ValueTableRow[]; headers: ValueTableHeader[] } => {
|
|
153
|
+
const columns = collectKeysFromObjects(items)
|
|
154
|
+
if (columns.length === 0) {
|
|
155
|
+
return { rows: [], headers: [] }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const attributeHeaders = headersFromKeys(columns)
|
|
159
|
+
const headers = cacheKey
|
|
160
|
+
? [
|
|
161
|
+
{ title: "Key", key: CACHE_KEY_COLUMN },
|
|
162
|
+
...attributeHeaders,
|
|
163
|
+
]
|
|
164
|
+
: attributeHeaders
|
|
165
|
+
|
|
166
|
+
const rows = items.map((item, index) => {
|
|
167
|
+
const row: ValueTableRow = {
|
|
168
|
+
...rowFromRecord(item, columns),
|
|
169
|
+
}
|
|
170
|
+
if (cacheKey) {
|
|
171
|
+
row[CACHE_KEY_COLUMN] = cacheKey
|
|
172
|
+
}
|
|
173
|
+
row._id = cacheKey ? `${cacheKey}:${index}` : String(index)
|
|
174
|
+
return row
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
return { rows, headers }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const scalarTable = (text: string): { rows: ValueTableRow[]; headers: ValueTableHeader[] } => ({
|
|
181
|
+
rows: [{ _id: "0", [SCALAR_COLUMN]: text }],
|
|
182
|
+
headers: [{ title: "(value)", key: SCALAR_COLUMN }],
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const rawFallback = (value: unknown): { rows: ValueTableRow[]; headers: ValueTableHeader[] } => ({
|
|
186
|
+
rows: [{ _id: "0", [RAW_COLUMN]: JSON.stringify(value, null, 2) }],
|
|
187
|
+
headers: [{ title: "Raw", key: RAW_COLUMN }],
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const parsedValueObject = (
|
|
191
|
+
entry: CacheEntryValueSource,
|
|
192
|
+
): Record<string, unknown> | null => {
|
|
193
|
+
const parsed = entry.valueParsed ?? tryParseJson(entry.value)
|
|
194
|
+
return isPlainObject(parsed) ? parsed : null
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Build a table from loaded cache entries: attribute keys become columns,
|
|
199
|
+
* each entry's parsed value object becomes one row.
|
|
200
|
+
*/
|
|
201
|
+
export const cacheEntriesValueTableRows = (
|
|
202
|
+
entries: CacheEntryValueSource[],
|
|
203
|
+
schemaEntry?: CacheEntryValueSource,
|
|
204
|
+
): { rows: ValueTableRow[]; headers: ValueTableHeader[] } => {
|
|
205
|
+
if (entries.length === 0) {
|
|
206
|
+
return { rows: [], headers: [] }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const focus = schemaEntry ?? entries[0]
|
|
210
|
+
const innerItems = unwrapValueCollection(focus.valueParsed, focus.value)
|
|
211
|
+
if (innerItems && innerItems.length > 0) {
|
|
212
|
+
const cacheKeyForCollection = entries.length > 1 ? focus.key : undefined
|
|
213
|
+
return tableFromCollection(innerItems, cacheKeyForCollection)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const schemaObject = schemaEntry ? parsedValueObject(schemaEntry) : null
|
|
217
|
+
if (schemaObject && !isHomogeneousObjectMap(schemaObject)) {
|
|
218
|
+
const fallback = schemaEntry ?? entries[0]
|
|
219
|
+
return cacheValueTableRows(fallback.valueParsed, fallback.value)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const rowObjects = entries
|
|
223
|
+
.map((entry) => ({ entry, data: parsedValueObject(entry) }))
|
|
224
|
+
.filter(
|
|
225
|
+
(row): row is { entry: CacheEntryValueSource; data: Record<string, unknown> } =>
|
|
226
|
+
row.data !== null,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if (rowObjects.length === 0) {
|
|
230
|
+
const fallback = schemaEntry ?? entries[0]
|
|
231
|
+
return cacheValueTableRows(fallback.valueParsed, fallback.value)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const columns = schemaObject
|
|
235
|
+
? sortedKeys(Object.keys(schemaObject))
|
|
236
|
+
: collectKeysFromObjects(rowObjects.map((row) => row.data))
|
|
237
|
+
|
|
238
|
+
if (columns.length === 0) {
|
|
239
|
+
return { rows: [], headers: [] }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const headers: ValueTableHeader[] = [
|
|
243
|
+
{ title: "Key", key: CACHE_KEY_COLUMN },
|
|
244
|
+
...headersFromKeys(columns),
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
const rows = rowObjects.map(({ entry, data }) => ({
|
|
248
|
+
_id: entry.key,
|
|
249
|
+
[CACHE_KEY_COLUMN]: entry.key,
|
|
250
|
+
...rowFromRecord(data, columns),
|
|
251
|
+
}))
|
|
252
|
+
|
|
253
|
+
return { rows, headers }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Map one cache value into rows (collection items or single-object pivot). */
|
|
257
|
+
export const cacheValueTableRows = (
|
|
258
|
+
parsed: unknown | undefined,
|
|
259
|
+
raw?: string,
|
|
260
|
+
cacheKey?: string,
|
|
261
|
+
): { rows: ValueTableRow[]; headers: ValueTableHeader[] } => {
|
|
262
|
+
const collection = unwrapValueCollection(parsed, raw)
|
|
263
|
+
if (collection && collection.length > 0) {
|
|
264
|
+
return tableFromCollection(collection, cacheKey)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (parsed === undefined) {
|
|
268
|
+
return scalarTable(raw ?? "")
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (parsed === null) {
|
|
272
|
+
return scalarTable("null")
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (
|
|
276
|
+
typeof parsed === "string" ||
|
|
277
|
+
typeof parsed === "number" ||
|
|
278
|
+
typeof parsed === "boolean"
|
|
279
|
+
) {
|
|
280
|
+
return scalarTable(String(parsed))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (isPlainObject(parsed)) {
|
|
284
|
+
if (!isHomogeneousObjectMap(parsed)) {
|
|
285
|
+
return rawFallback(parsed)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const columns = sortedKeys(Object.keys(parsed))
|
|
289
|
+
if (columns.length === 0) {
|
|
290
|
+
return { rows: [], headers: [] }
|
|
291
|
+
}
|
|
292
|
+
const row: ValueTableRow = {
|
|
293
|
+
_id: cacheKey ?? "0",
|
|
294
|
+
...rowFromRecord(parsed, columns),
|
|
295
|
+
}
|
|
296
|
+
if (cacheKey) {
|
|
297
|
+
row[CACHE_KEY_COLUMN] = cacheKey
|
|
298
|
+
return {
|
|
299
|
+
rows: [row],
|
|
300
|
+
headers: [
|
|
301
|
+
{ title: "Key", key: CACHE_KEY_COLUMN },
|
|
302
|
+
...headersFromKeys(columns),
|
|
303
|
+
],
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
rows: [row],
|
|
308
|
+
headers: headersFromKeys(columns),
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (Array.isArray(parsed)) {
|
|
313
|
+
return rawFallback(parsed)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return rawFallback(parsed)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** True when tabular layout is not meaningful; use pretty-print instead. */
|
|
320
|
+
export const isScalarFallbackResult = (
|
|
321
|
+
rows: ValueTableRow[],
|
|
322
|
+
headers: ValueTableHeader[],
|
|
323
|
+
entry: CacheEntryValueSource,
|
|
324
|
+
): boolean => {
|
|
325
|
+
if (rows.length === 0) {
|
|
326
|
+
return true
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (rows.length === 1 && headers.length === 1) {
|
|
330
|
+
const columnKey = headers[0].key
|
|
331
|
+
if (columnKey === RAW_COLUMN) {
|
|
332
|
+
return true
|
|
333
|
+
}
|
|
334
|
+
if (columnKey === SCALAR_COLUMN && entry.valueParsed === undefined) {
|
|
335
|
+
return true
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return false
|
|
340
|
+
}
|