@ocelescope/plugin 0.1.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/dist/index.js ADDED
@@ -0,0 +1,833 @@
1
+ import { BarList, FileDropzone, OcelSelect, defineModule, defineModuleRoute, env, getModuleRoute } from "@ocelescope/core";
2
+ import { CheckIcon, Columns2Icon, DatabaseIcon, DownloadIcon, EllipsisVertical, PuzzleIcon, Rows2Icon, Trash2Icon, UploadIcon } from "lucide-react";
3
+ import { useRouter } from "next/router.js";
4
+ import { ActionIcon, Anchor, Badge, Box, Breadcrumbs, Button, Card, Center, Container, Flex, Group, Loader, LoadingOverlay, Menu, Modal, MultiSelect, ScrollArea, Select, SimpleGrid, Splitter, Stack, Text, ThemeIcon, Title, Tooltip } from "@mantine/core";
5
+ import { useDeletePlugin, useDownloadPluginResults, useEventAttributes, useEventCounts, useEventIds, useGetComputedValues, useGetExtensionMeta, useGetPlugin, useGetPluginMethod, useGetResourceMeta, useObjectAttributes, useObjectCounts, useObjectIds, usePluginResult, usePlugins, useRunPlugin, useSavePluginResults, useUpload } from "@ocelescope/api-base";
6
+ import { memo, useCallback, useEffect, useMemo, useState } from "react";
7
+ import { ResourceSelect, Visualization } from "@ocelescope/resources";
8
+ import { Controller, useForm, useWatch } from "react-hook-form";
9
+ import RefParser from "@apidevtools/json-schema-ref-parser";
10
+ import validator from "@rjsf/validator-ajv8";
11
+ import { unflatten } from "flat";
12
+ import traverse from "json-schema-traverse";
13
+ import { keepPreviousData } from "@tanstack/react-query";
14
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
+ import { useDebouncedValue } from "@mantine/hooks";
16
+ import dynamic from "next/dynamic.js";
17
+ import Link from "next/link.js";
18
+ import { generateColor } from "@marko19907/string-to-color";
19
+ //#region src/components/Form/Fields/custom.tsx
20
+ const useSelectOptions = ({ pluginId, methodName, provider, control }) => {
21
+ const formValues = useWatch({ control });
22
+ const { data } = useGetComputedValues(pluginId, methodName, provider, {
23
+ input: formValues.input,
24
+ input_ocels: Object.fromEntries(Object.entries(formValues.input_ocels ?? {}).filter(([_, v]) => !!v && v.length >= 0)),
25
+ input_resources: Object.fromEntries(Object.entries(formValues.input_resources ?? {}).filter(([_, v]) => !!v && v.length >= 0))
26
+ }, { query: {
27
+ placeholderData: keepPreviousData,
28
+ refetchOnWindowFocus: false,
29
+ refetchOnReconnect: false
30
+ } });
31
+ return { options: data };
32
+ };
33
+ const getComputedSelect = ({ pluginId, methodName, control }) => ({ computed_select: memo(({ schema, required, formData, onChange, fieldPathId: { path } }) => {
34
+ const meta = schema?.["x-ui-meta"];
35
+ const SelectComponent = schema.type === "array" ? MultiSelect : Select;
36
+ const { options } = useSelectOptions({
37
+ pluginId,
38
+ methodName,
39
+ control,
40
+ provider: meta.provider
41
+ });
42
+ return /* @__PURE__ */ jsx(SelectComponent, {
43
+ label: schema.title,
44
+ description: schema.description,
45
+ required,
46
+ value: formData ?? (schema.type === "array" ? [] : null),
47
+ onChange: (value) => onChange(value, path),
48
+ data: options
49
+ });
50
+ }) });
51
+ //#endregion
52
+ //#region src/components/Form/Fields/ocel.tsx
53
+ const AttributeSelector = (query) => ({ isMulti, ocelId, onChange, value, label, required, description }) => {
54
+ const { data: attributes = [] } = query(ocelId);
55
+ const attributeNames = new Set(attributes.map(({ name }) => name));
56
+ return /* @__PURE__ */ jsx(isMulti ? MultiSelect : Select, {
57
+ value: value ?? (isMulti ? [] : null),
58
+ label,
59
+ onChange,
60
+ required,
61
+ description,
62
+ clearable: true,
63
+ data: [...attributeNames]
64
+ });
65
+ };
66
+ const TypeSelector = (query) => ({ ocelId, onChange, required, value, isMulti, label, description }) => {
67
+ const { data = {} } = query(ocelId);
68
+ return /* @__PURE__ */ jsx(isMulti ? MultiSelect : Select, {
69
+ value: value ?? (isMulti ? [] : null),
70
+ label,
71
+ required,
72
+ clearable: true,
73
+ description,
74
+ onChange,
75
+ data: Object.keys(data)
76
+ });
77
+ };
78
+ const IdSelect = (query) => ({ ocelId, isMulti, value, ...rest }) => {
79
+ const [searchValue, setSearchValue] = useState();
80
+ const [debouncedSearch] = useDebouncedValue(searchValue, 300);
81
+ const { data: ids } = query(ocelId, { search: debouncedSearch });
82
+ return /* @__PURE__ */ jsx(isMulti ? MultiSelect : Select, {
83
+ searchable: true,
84
+ searchValue,
85
+ onSearchChange: (newSearchValue) => setSearchValue(newSearchValue),
86
+ data: ids?.response,
87
+ value: value ?? (isMulti ? [] : null),
88
+ ...rest
89
+ });
90
+ };
91
+ const ocelFieldMap = {
92
+ event_type: TypeSelector(useEventCounts),
93
+ object_type: TypeSelector(useObjectCounts),
94
+ event_attribute: AttributeSelector(useEventAttributes),
95
+ object_attribute: AttributeSelector(useObjectAttributes),
96
+ event_id: IdSelect(useEventIds),
97
+ object_id: IdSelect(useObjectIds)
98
+ };
99
+ const wrapFieldsWithContext = (control) => {
100
+ const wrapped = {};
101
+ for (const [name, Field] of Object.entries(ocelFieldMap)) {
102
+ const Comp = ({ schema, required, formData, fieldPathId: { path }, onChange }) => {
103
+ const ocelRef = schema?.["x-ui-meta"]?.ocel_id;
104
+ const isMulti = schema?.type === "array";
105
+ const ocelId = useWatch({
106
+ control,
107
+ name: `input_ocels.${ocelRef}`
108
+ });
109
+ return /* @__PURE__ */ jsx(Field, {
110
+ label: schema?.title,
111
+ required,
112
+ description: schema?.description,
113
+ isMulti,
114
+ onChange: (data) => onChange(data, path),
115
+ value: formData,
116
+ ocelId
117
+ });
118
+ };
119
+ wrapped[name] = memo(Comp);
120
+ }
121
+ return wrapped;
122
+ };
123
+ //#endregion
124
+ //#region src/components/Form/MantineForm.tsx
125
+ const Form = dynamic(() => import("@rjsf/mantine").then((m) => m.Form), { ssr: false });
126
+ //#endregion
127
+ //#region src/components/Form/PluginForm.tsx
128
+ const buildUiSchemaV2 = async ({ schema }) => {
129
+ const deref = await RefParser.dereference(schema, { dereference: { circular: "ignore" } });
130
+ const uiSchema = {};
131
+ traverse(deref, {
132
+ allKeys: true,
133
+ cb: (subschema, pointer) => {
134
+ const pointerComponents = pointer.split("/").slice(1);
135
+ if (pointerComponents[0] !== "properties") return;
136
+ if (pointerComponents[0] === "properties" && pointerComponents.at(-1) === "x-ui-meta") {
137
+ const path = pointerComponents.filter((pathComponent) => !["x-ui-meta", "properties"].includes(pathComponent)).join(".");
138
+ uiSchema[path] = { "ui:field": subschema.field_type ?? subschema.type };
139
+ }
140
+ }
141
+ });
142
+ return unflatten(uiSchema);
143
+ };
144
+ const PluginForm = ({ schema, control, pluginId, methodName, onSubmit }) => {
145
+ const [uiSchema, setUiSchema] = useState(void 0);
146
+ useEffect(() => {
147
+ let cancelled = false;
148
+ (async () => {
149
+ const ui = await buildUiSchemaV2({ schema });
150
+ if (!cancelled) setUiSchema(ui);
151
+ })();
152
+ return () => {
153
+ cancelled = true;
154
+ };
155
+ }, [schema]);
156
+ const fields = useMemo(() => wrapFieldsWithContext(control), [control]);
157
+ const computedFields = useMemo(() => getComputedSelect({
158
+ methodName,
159
+ pluginId,
160
+ control
161
+ }), [
162
+ methodName,
163
+ pluginId,
164
+ control
165
+ ]);
166
+ return /* @__PURE__ */ jsx(Controller, {
167
+ control,
168
+ name: "input",
169
+ render: ({ field }) => /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Form, {
170
+ schema: {
171
+ ...schema,
172
+ title: ""
173
+ },
174
+ formData: field.value,
175
+ validator,
176
+ uiSchema,
177
+ fields: {
178
+ ...fields,
179
+ ...computedFields
180
+ },
181
+ onChange: (data) => {
182
+ field.onChange(data.formData);
183
+ },
184
+ onSubmit
185
+ }) })
186
+ });
187
+ };
188
+ //#endregion
189
+ //#region src/components/Form/index.tsx
190
+ const PluginInput = ({ pluginId, method, onSuccess }) => {
191
+ const { mutate: runPlugin } = useRunPlugin({ mutation: { onSuccess } });
192
+ const { control, handleSubmit } = useForm({ defaultValues: {
193
+ input_ocels: Object.fromEntries(Object.keys(method.input_ocels ?? {}).map((name) => [name, void 0])),
194
+ input_resources: Object.fromEntries(Object.keys(method.input_resources ?? {}).map((name) => [name, void 0])),
195
+ formData: {}
196
+ } });
197
+ const onSubmit = useCallback(() => handleSubmit((data) => runPlugin({
198
+ data,
199
+ methodName: method.name,
200
+ pluginId
201
+ }))(), [
202
+ handleSubmit,
203
+ pluginId,
204
+ method,
205
+ runPlugin
206
+ ]);
207
+ return /* @__PURE__ */ jsxs(Stack, {
208
+ gap: "md",
209
+ children: [
210
+ Object.entries(method.input_ocels ?? {}).map(([name, { label, description, extension }]) => /* @__PURE__ */ jsx(Controller, {
211
+ control,
212
+ name: `input_ocels.${name}`,
213
+ rules: { required: "Please select a value" },
214
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsx(OcelSelect, {
215
+ label,
216
+ required: true,
217
+ extension: extension ?? void 0,
218
+ description,
219
+ error: fieldState.error?.message,
220
+ onChange: field.onChange,
221
+ value: field.value
222
+ })
223
+ }, name)),
224
+ Object.entries(method.input_resources ?? {}).map(([name, [resource_type, { label, description }]]) => /* @__PURE__ */ jsx(Controller, {
225
+ control,
226
+ name: `input_resources.${name}`,
227
+ rules: { required: "Please select a value" },
228
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsx(ResourceSelect, {
229
+ label,
230
+ required: true,
231
+ type: resource_type,
232
+ description,
233
+ onChange: field.onChange,
234
+ error: fieldState.error?.message,
235
+ value: field.value
236
+ })
237
+ }, name)),
238
+ method.input_schema ? /* @__PURE__ */ jsx(PluginForm, {
239
+ pluginId,
240
+ methodName: method.name,
241
+ schema: method.input_schema,
242
+ control,
243
+ onSubmit
244
+ }) : /* @__PURE__ */ jsx(Button, {
245
+ onClick: onSubmit,
246
+ children: "Submit"
247
+ })
248
+ ]
249
+ });
250
+ };
251
+ //#endregion
252
+ //#region src/components/PluginBreadcrumbs/PluginBreadcrumbs.tsx
253
+ const PluginBreadcrumbs = () => {
254
+ const { query } = useRouter();
255
+ const { pluginId, methodName } = query;
256
+ const { data: plugin } = useGetPlugin(pluginId ?? "", { query: { enabled: !!pluginId } });
257
+ const pluginMethod = useMemo(() => plugin?.methods.find(({ name }) => name === methodName), [plugin, methodName]);
258
+ return /* @__PURE__ */ jsxs(Breadcrumbs, { children: [
259
+ /* @__PURE__ */ jsx(Anchor, {
260
+ component: Link,
261
+ href: { query: {
262
+ ...query,
263
+ pluginId: void 0,
264
+ methodName: void 0
265
+ } },
266
+ children: "Plugins"
267
+ }),
268
+ plugin && /* @__PURE__ */ jsx(Anchor, {
269
+ component: Link,
270
+ href: { query: {
271
+ ...query,
272
+ pluginId: plugin?.id,
273
+ methodName: void 0
274
+ } },
275
+ children: plugin?.meta.label
276
+ }),
277
+ pluginMethod && /* @__PURE__ */ jsx(Anchor, {
278
+ component: Link,
279
+ href: { query: {
280
+ ...query,
281
+ pluginId: plugin?.id,
282
+ methodName: pluginMethod.name
283
+ } },
284
+ children: pluginMethod.label ?? pluginMethod.name
285
+ })
286
+ ] });
287
+ };
288
+ //#endregion
289
+ //#region src/components/ResultSection/ResultSection.tsx
290
+ const ResourceView = ({ resource }) => {
291
+ return /* @__PURE__ */ jsx(Visualization, { visualization: resource.visualization });
292
+ };
293
+ const OCELView = ({ ocel }) => {
294
+ return /* @__PURE__ */ jsxs(Stack, {
295
+ gap: "md",
296
+ children: [/* @__PURE__ */ jsx(BarList, {
297
+ data: ocel.activity_count,
298
+ labelHeader: "Activity"
299
+ }), /* @__PURE__ */ jsx(BarList, {
300
+ data: ocel.object_count,
301
+ labelHeader: "Object type"
302
+ })]
303
+ });
304
+ };
305
+ const entityTypeOf = (output) => output.type === "ocel" ? "ocel" : output.resource_type || "resource";
306
+ const ResultLabel = ({ label, entityType, bold }) => /* @__PURE__ */ jsxs(Group, {
307
+ gap: "xs",
308
+ wrap: "nowrap",
309
+ children: [/* @__PURE__ */ jsx(Text, {
310
+ fw: bold ? 600 : void 0,
311
+ truncate: true,
312
+ children: label
313
+ }), /* @__PURE__ */ jsx(Badge, {
314
+ size: "sm",
315
+ color: generateColor(entityType),
316
+ children: entityType
317
+ })]
318
+ });
319
+ const ResultSection = ({ pluginId, methodName, taskId }) => {
320
+ const { data: pluginSummary } = usePluginResult(pluginId, methodName, taskId, { query: { refetchInterval: ({ state }) => {
321
+ if (state.data == null) return 1e3;
322
+ return false;
323
+ } } });
324
+ const [selected, setSelected] = useState([0]);
325
+ const [orientation, setOrientation] = useState("vertical");
326
+ const { mutate: saveResults, isPending: isSaving, isSuccess: isSaved, reset: resetSave } = useSavePluginResults();
327
+ const { mutate: downloadResults, isPending: isDownloading } = useDownloadPluginResults({ request: { responseType: "blob" } });
328
+ const options = useMemo(() => (pluginSummary ?? []).map((output) => {
329
+ return {
330
+ value: output.result_index,
331
+ label: entityTypeOf(output),
332
+ entityType: output.type
333
+ };
334
+ }), [pluginSummary]);
335
+ if (!pluginSummary) return /* @__PURE__ */ jsx(Center, {
336
+ h: "100%",
337
+ children: /* @__PURE__ */ jsxs(Stack, {
338
+ align: "center",
339
+ gap: "sm",
340
+ children: [/* @__PURE__ */ jsx(Loader, {}), /* @__PURE__ */ jsx(Text, {
341
+ c: "dimmed",
342
+ children: "Running…"
343
+ })]
344
+ })
345
+ });
346
+ if (pluginSummary.length === 0) return /* @__PURE__ */ jsx(Center, {
347
+ h: "100%",
348
+ children: /* @__PURE__ */ jsx(Text, {
349
+ c: "dimmed",
350
+ children: "This run produced no results."
351
+ })
352
+ });
353
+ const handleSelectionChange = (value) => {
354
+ setSelected(value);
355
+ resetSave();
356
+ };
357
+ const handleDownload = () => {
358
+ downloadResults({
359
+ pluginId,
360
+ methodName,
361
+ taskId,
362
+ data: { indices: selected }
363
+ }, { onSuccess: (data) => {
364
+ const url = URL.createObjectURL(data);
365
+ const anchor = document.createElement("a");
366
+ anchor.href = url;
367
+ anchor.download = `${methodName}_results.zip`;
368
+ anchor.click();
369
+ URL.revokeObjectURL(url);
370
+ } });
371
+ };
372
+ const handleSaveToSession = () => {
373
+ saveResults({
374
+ pluginId,
375
+ methodName,
376
+ taskId,
377
+ data: { indices: selected }
378
+ });
379
+ };
380
+ return /* @__PURE__ */ jsxs(Stack, {
381
+ gap: 0,
382
+ h: "100%",
383
+ children: [/* @__PURE__ */ jsxs(Group, {
384
+ px: "sm",
385
+ py: "xs",
386
+ wrap: "nowrap",
387
+ style: { borderBottom: "1px solid var(--mantine-color-default-border)" },
388
+ children: [options.length > 1 ? /* @__PURE__ */ jsx(MultiSelect, {
389
+ flex: 1,
390
+ data: options,
391
+ onChange: handleSelectionChange,
392
+ value: selected,
393
+ placeholder: "Select results to display",
394
+ searchable: true,
395
+ clearable: true,
396
+ comboboxProps: { withinPortal: true },
397
+ renderOption: ({ option }) => {
398
+ const output = pluginSummary[Number(option.value)];
399
+ return /* @__PURE__ */ jsx(ResultLabel, {
400
+ label: option.label,
401
+ entityType: output ? entityTypeOf(output) : option.label
402
+ });
403
+ }
404
+ }) : options[0] && /* @__PURE__ */ jsx(ResultLabel, {
405
+ label: options[0].label,
406
+ entityType: options[0].entityType,
407
+ bold: true
408
+ }), /* @__PURE__ */ jsxs(Group, {
409
+ gap: "xs",
410
+ ml: "auto",
411
+ wrap: "nowrap",
412
+ children: [
413
+ selected.length > 1 && /* @__PURE__ */ jsx(Tooltip, {
414
+ label: orientation === "vertical" ? "Show side by side" : "Stack vertically",
415
+ children: /* @__PURE__ */ jsx(ActionIcon, {
416
+ variant: "default",
417
+ size: "lg",
418
+ onClick: () => setOrientation((prev) => prev === "vertical" ? "horizontal" : "vertical"),
419
+ children: orientation === "vertical" ? /* @__PURE__ */ jsx(Columns2Icon, { size: 16 }) : /* @__PURE__ */ jsx(Rows2Icon, { size: 16 })
420
+ })
421
+ }),
422
+ /* @__PURE__ */ jsx(Button, {
423
+ variant: "default",
424
+ leftSection: /* @__PURE__ */ jsx(DownloadIcon, { size: 16 }),
425
+ onClick: handleDownload,
426
+ loading: isDownloading,
427
+ disabled: selected.length === 0,
428
+ children: "Download"
429
+ }),
430
+ /* @__PURE__ */ jsx(Button, {
431
+ variant: "light",
432
+ color: isSaved ? "green" : void 0,
433
+ leftSection: isSaved ? /* @__PURE__ */ jsx(CheckIcon, { size: 16 }) : /* @__PURE__ */ jsx(DatabaseIcon, { size: 16 }),
434
+ onClick: handleSaveToSession,
435
+ loading: isSaving,
436
+ disabled: selected.length === 0,
437
+ children: isSaved ? "Saved" : "Save to session"
438
+ })
439
+ ]
440
+ })]
441
+ }), selected.length === 0 ? /* @__PURE__ */ jsx(Center, {
442
+ style: { flex: 1 },
443
+ children: /* @__PURE__ */ jsx(Text, {
444
+ c: "dimmed",
445
+ children: "Select one or more results to display."
446
+ })
447
+ }) : /* @__PURE__ */ jsx(Splitter, {
448
+ orientation,
449
+ lineSize: 4,
450
+ handleColor: "var(--mantine-color-default-border)",
451
+ style: {
452
+ flex: 1,
453
+ minHeight: 0
454
+ },
455
+ children: pluginSummary?.filter(({ result_index }) => selected.includes(result_index)).map((output, index) => /* @__PURE__ */ jsx(Splitter.Pane, {
456
+ defaultSize: 100 / selected.length,
457
+ min: "15%",
458
+ children: /* @__PURE__ */ jsx(Box, {
459
+ h: "100%",
460
+ p: "xs",
461
+ pos: "relative",
462
+ children: output.type === "ocel" ? /* @__PURE__ */ jsx(OCELView, { ocel: output }) : /* @__PURE__ */ jsx(ResourceView, { resource: output })
463
+ })
464
+ }, index))
465
+ }, `${orientation}:${selected.join(",")}`)]
466
+ });
467
+ };
468
+ //#endregion
469
+ //#region src/pages/MethodPage.tsx
470
+ const COLLAPSED_SIZES = [0, 100];
471
+ const SPLIT_SIZES = [75, 25];
472
+ const MethodPage = ({ pluginId, methodName }) => {
473
+ const { data: pluginMethod } = useGetPluginMethod(pluginId, methodName);
474
+ const [currentTask, setCurrentTask] = useState();
475
+ const [sizes, setSizes] = useState(COLLAPSED_SIZES);
476
+ if (!pluginMethod) return /* @__PURE__ */ jsx(LoadingOverlay, { visible: true });
477
+ const handleSuccess = (taskId) => {
478
+ setCurrentTask(taskId);
479
+ setSizes((prev) => prev[0] === 0 ? SPLIT_SIZES : prev);
480
+ };
481
+ return /* @__PURE__ */ jsx(Flex, {
482
+ direction: "column",
483
+ h: "100%",
484
+ children: /* @__PURE__ */ jsxs(Splitter, {
485
+ sizes,
486
+ onSizeChange: setSizes,
487
+ lineSize: 4,
488
+ handleColor: "var(--mantine-color-default-border)",
489
+ px: "xs",
490
+ style: {
491
+ flex: 1,
492
+ minHeight: 0
493
+ },
494
+ children: [/* @__PURE__ */ jsx(Splitter.Pane, {
495
+ defaultSize: 0,
496
+ min: "20%",
497
+ collapsible: true,
498
+ children: /* @__PURE__ */ jsx(Box, {
499
+ h: "100%",
500
+ pos: "relative",
501
+ style: { overflow: "hidden" },
502
+ children: currentTask ? /* @__PURE__ */ jsx(ResultSection, {
503
+ pluginId,
504
+ methodName,
505
+ taskId: currentTask
506
+ }) : /* @__PURE__ */ jsx(Center, {
507
+ h: "100%",
508
+ p: "md",
509
+ children: /* @__PURE__ */ jsx(Text, {
510
+ c: "dimmed",
511
+ ta: "center",
512
+ children: "Run the method to see its results here."
513
+ })
514
+ })
515
+ })
516
+ }), /* @__PURE__ */ jsx(Splitter.Pane, {
517
+ defaultSize: 100,
518
+ min: "20%",
519
+ collapsible: true,
520
+ children: /* @__PURE__ */ jsx(ScrollArea, {
521
+ h: "100%",
522
+ type: "auto",
523
+ children: /* @__PURE__ */ jsxs(Stack, {
524
+ gap: "sm",
525
+ p: "md",
526
+ maw: 640,
527
+ mx: "auto",
528
+ w: "100%",
529
+ children: [
530
+ /* @__PURE__ */ jsx(Group, {
531
+ justify: "center",
532
+ children: /* @__PURE__ */ jsx(PluginBreadcrumbs, {})
533
+ }),
534
+ /* @__PURE__ */ jsx(Title, {
535
+ ta: "center",
536
+ children: pluginMethod.label ?? methodName
537
+ }),
538
+ pluginMethod.description && /* @__PURE__ */ jsx(Text, {
539
+ c: "dimmed",
540
+ ta: "center",
541
+ children: pluginMethod.description
542
+ }),
543
+ /* @__PURE__ */ jsx(PluginInput, {
544
+ onSuccess: handleSuccess,
545
+ pluginId,
546
+ method: pluginMethod
547
+ })
548
+ ]
549
+ })
550
+ })
551
+ })]
552
+ })
553
+ });
554
+ };
555
+ //#endregion
556
+ //#region src/components/PluginCard/GenericCard.tsx
557
+ const GenericCard = ({ menuItems, title, description, link, tags, version }) => {
558
+ return /* @__PURE__ */ jsxs(Card, {
559
+ shadow: "sm",
560
+ padding: "sm",
561
+ radius: "md",
562
+ withBorder: true,
563
+ h: "100%",
564
+ children: [
565
+ /* @__PURE__ */ jsxs(Group, {
566
+ justify: "space-between",
567
+ align: "start",
568
+ wrap: "nowrap",
569
+ children: [/* @__PURE__ */ jsxs(Stack, {
570
+ gap: "xs",
571
+ mb: "xs",
572
+ children: [tags && /* @__PURE__ */ jsx(Group, {
573
+ gap: "xs",
574
+ children: tags.map((tag) => /* @__PURE__ */ jsx(Badge, {
575
+ size: "sm",
576
+ color: generateColor(tag),
577
+ children: tag
578
+ }, tag))
579
+ }), /* @__PURE__ */ jsxs(Text, {
580
+ fw: 500,
581
+ children: [title, version && /* @__PURE__ */ jsx(Text, {
582
+ ml: "xs",
583
+ c: "dimmed",
584
+ component: "span",
585
+ children: `v${version}`
586
+ })]
587
+ })]
588
+ }), menuItems && /* @__PURE__ */ jsxs(Menu, {
589
+ width: 200,
590
+ position: "left",
591
+ children: [/* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(ActionIcon, {
592
+ variant: "transparent",
593
+ size: "sm",
594
+ children: /* @__PURE__ */ jsx(EllipsisVertical, {})
595
+ }) }), /* @__PURE__ */ jsx(Menu.Dropdown, { children: menuItems })]
596
+ })]
597
+ }),
598
+ /* @__PURE__ */ jsx(Text, {
599
+ size: "sm",
600
+ mb: "xs",
601
+ c: "dimmed",
602
+ children: description
603
+ }),
604
+ link && /* @__PURE__ */ jsx(Button, {
605
+ mt: "auto",
606
+ component: Link,
607
+ variant: "outline",
608
+ ...link
609
+ })
610
+ ]
611
+ });
612
+ };
613
+ //#endregion
614
+ //#region src/pages/PluginPage.tsx
615
+ const MethodCard = ({ method }) => {
616
+ const { query } = useRouter();
617
+ const { data: resourceMeta = {} } = useGetResourceMeta();
618
+ const { data: extensionMeta = {} } = useGetExtensionMeta();
619
+ const tags = useMemo(() => {
620
+ const extensions = Object.values(method.input_ocels ?? {}).map(({ extension }) => extension ? extensionMeta[extension]?.label : void 0);
621
+ const inputResources = Object.values(method.input_resources ?? {}).map(([resourceName]) => resourceMeta[resourceName]?.label ?? resourceName);
622
+ const resultNames = (method.results ?? []).filter((result) => result.type === "resource").map((result) => resourceMeta[result.resource_type]?.label ?? result.resource_type);
623
+ return Array.from(/* @__PURE__ */ new Set([
624
+ ...inputResources,
625
+ ...resultNames,
626
+ ...extensions
627
+ ])).filter((tag) => !!tag);
628
+ }, [
629
+ resourceMeta,
630
+ extensionMeta,
631
+ method
632
+ ]);
633
+ return /* @__PURE__ */ jsx(GenericCard, {
634
+ title: method.label ?? method.name,
635
+ description: method.description ?? "",
636
+ tags,
637
+ link: {
638
+ href: { query: {
639
+ ...query,
640
+ methodName: method.name
641
+ } },
642
+ children: "Run Method"
643
+ }
644
+ });
645
+ };
646
+ const PluginPage = ({ pluginId }) => {
647
+ const { data: plugin } = useGetPlugin(pluginId);
648
+ return /* @__PURE__ */ jsx(Container, {
649
+ fluid: true,
650
+ children: /* @__PURE__ */ jsxs(Stack, { children: [/* @__PURE__ */ jsxs(Stack, {
651
+ gap: 0,
652
+ align: "center",
653
+ children: [
654
+ /* @__PURE__ */ jsx(PluginBreadcrumbs, {}),
655
+ /* @__PURE__ */ jsxs(Title, {
656
+ mt: "xs",
657
+ children: [" ", plugin?.meta.label]
658
+ }),
659
+ /* @__PURE__ */ jsx(Text, {
660
+ c: "dimmed",
661
+ children: plugin?.meta.description
662
+ })
663
+ ]
664
+ }), /* @__PURE__ */ jsx(SimpleGrid, {
665
+ cols: {
666
+ base: 1,
667
+ sm: 2,
668
+ lg: 4
669
+ },
670
+ spacing: {
671
+ base: 10,
672
+ sm: "xl"
673
+ },
674
+ verticalSpacing: {
675
+ base: "md",
676
+ sm: "xl"
677
+ },
678
+ children: plugin?.methods.map((method) => /* @__PURE__ */ jsx(MethodCard, {
679
+ method,
680
+ pluginId: plugin.id
681
+ }, method.name))
682
+ })] })
683
+ });
684
+ };
685
+ //#endregion
686
+ //#region src/components/PluginUploadSection/PluginUploadSection.tsx
687
+ const PluginUploadSection = ({ onSuccess }) => {
688
+ const { mutateAsync: upload } = useUpload({ mutation: { onSuccess } });
689
+ return /* @__PURE__ */ jsx(FileDropzone, {
690
+ onUpload: async (data) => await upload({ data: { files: data } }),
691
+ content: { description: "Drag'n'drop your Plugins to upload." },
692
+ accept: ["application/x-zip-compressed", "application/zip"]
693
+ });
694
+ };
695
+ //#endregion
696
+ //#region src/components/PluginCard/PluginCard.tsx
697
+ const PluginCard = ({ plugin }) => {
698
+ const { description, label, version } = plugin.meta;
699
+ const { mutate: deletePlugin } = useDeletePlugin();
700
+ return /* @__PURE__ */ jsx(GenericCard, {
701
+ title: label,
702
+ description: description ?? "",
703
+ version,
704
+ menuItems: /* @__PURE__ */ jsx(Menu.Item, {
705
+ leftSection: /* @__PURE__ */ jsx(Trash2Icon, {}),
706
+ color: "red",
707
+ fw: "bold",
708
+ onClick: () => {
709
+ deletePlugin({ pluginId: plugin.id });
710
+ },
711
+ children: "Delete"
712
+ }),
713
+ link: {
714
+ href: getModuleRoute({
715
+ routeName: "plugins",
716
+ moduleName: "plugins",
717
+ query: { pluginId: plugin.id }
718
+ }),
719
+ children: "View Plugin"
720
+ }
721
+ });
722
+ };
723
+ const UploadPluginCard = () => {
724
+ const [isModalOpen, setIsModalOpen] = useState(false);
725
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Modal, {
726
+ title: /* @__PURE__ */ jsx(Text, {
727
+ size: "h3",
728
+ children: "Upload"
729
+ }),
730
+ opened: isModalOpen,
731
+ onClose: () => setIsModalOpen(false),
732
+ size: "xl",
733
+ children: /* @__PURE__ */ jsx(PluginUploadSection, { onSuccess: () => setIsModalOpen(false) })
734
+ }), /* @__PURE__ */ jsx(Card, {
735
+ shadow: "sm",
736
+ padding: "lg",
737
+ radius: "md",
738
+ withBorder: true,
739
+ h: "100%",
740
+ onClick: () => setIsModalOpen(true),
741
+ style: { cursor: "pointer" },
742
+ children: /* @__PURE__ */ jsxs(Stack, {
743
+ align: "center",
744
+ justify: "center",
745
+ h: "100%",
746
+ children: [/* @__PURE__ */ jsx(ThemeIcon, {
747
+ size: "xl",
748
+ variant: "transparent",
749
+ children: /* @__PURE__ */ jsx(UploadIcon, { size: 50 })
750
+ }), /* @__PURE__ */ jsx(Text, {
751
+ fw: "bold",
752
+ c: "blue.6",
753
+ children: "Upload Plugin"
754
+ })]
755
+ })
756
+ })] });
757
+ };
758
+ //#endregion
759
+ //#region src/pages/PluginsOverview.tsx
760
+ const PluginsOverview = () => {
761
+ const { data: plugins } = usePlugins();
762
+ return /* @__PURE__ */ jsxs(Stack, {
763
+ gap: "md",
764
+ children: [/* @__PURE__ */ jsxs("div", {
765
+ style: { maxWidth: 720 },
766
+ children: [/* @__PURE__ */ jsx(Title, {
767
+ order: 2,
768
+ children: "Plugin Overview"
769
+ }), /* @__PURE__ */ jsxs(Text, {
770
+ c: "dimmed",
771
+ size: "sm",
772
+ mt: 4,
773
+ children: [
774
+ "Upload plugins to extend Ocelescope with custom process mining functionality. Plugins are packaged as .zip files. Documentation for writing your own plugins is available on the",
775
+ " ",
776
+ /* @__PURE__ */ jsx(Anchor, {
777
+ href: env.projectPage,
778
+ target: "_blank",
779
+ rel: "noopener noreferrer",
780
+ children: "project page"
781
+ }),
782
+ "."
783
+ ]
784
+ })]
785
+ }), plugins && plugins.length === 0 ? /* @__PURE__ */ jsx(PluginUploadSection, {}) : /* @__PURE__ */ jsxs(SimpleGrid, {
786
+ cols: {
787
+ base: 1,
788
+ sm: 2,
789
+ md: 3,
790
+ lg: 3
791
+ },
792
+ spacing: {
793
+ base: 10,
794
+ sm: "xl"
795
+ },
796
+ verticalSpacing: {
797
+ base: "md",
798
+ sm: "xl"
799
+ },
800
+ children: [plugins?.map((plugin) => /* @__PURE__ */ jsx(PluginCard, { plugin }, plugin.id)), /* @__PURE__ */ jsx(UploadPluginCard, {})]
801
+ })]
802
+ });
803
+ };
804
+ //#endregion
805
+ //#region src/pages/index.tsx
806
+ const PluginRoute = () => {
807
+ const { query } = useRouter();
808
+ const { pluginId, methodName } = query;
809
+ if (pluginId) {
810
+ if (methodName) return /* @__PURE__ */ jsx(MethodPage, {
811
+ methodName,
812
+ pluginId
813
+ });
814
+ return /* @__PURE__ */ jsx(PluginPage, { pluginId });
815
+ }
816
+ return /* @__PURE__ */ jsx(PluginsOverview, {});
817
+ };
818
+ //#endregion
819
+ //#region src/index.ts
820
+ var src_default = defineModule({
821
+ name: "plugins",
822
+ description: "Run process mining plugins inside ocelescope",
823
+ label: "Plugins",
824
+ authors: [{ name: "Öztürk, Görkem-Emre" }],
825
+ routes: [defineModuleRoute({
826
+ name: "plugins",
827
+ label: "Plugins",
828
+ component: PluginRoute
829
+ })],
830
+ icon: PuzzleIcon
831
+ });
832
+ //#endregion
833
+ export { src_default as default };