@ocelescope/discovery 0.1.2 → 0.6.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.
Files changed (2) hide show
  1. package/dist/index.js +145 -136
  2. package/package.json +18 -24
package/dist/index.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import { defineModule, defineModuleRoute, useCurrentOcel } from "@ocelescope/core";
2
2
  import { BookmarkIcon, ChartNetwork, SettingsIcon } from "lucide-react";
3
- import { ActionIcon, Alert, Box, Card, Center, Divider, Drawer, LoadingOverlay, MultiSelect, NumberInput, Select, Slider, Stack, Switch, Text, TextInput, Tooltip } from "@mantine/core";
3
+ import { ActionIcon, Alert, Box, Card, Center, Divider, Drawer, Input, LoadingOverlay, MultiSelect, NumberInput, Select, Slider, Stack, Text, Tooltip } from "@mantine/core";
4
4
  import { useLocalStorage } from "@mantine/hooks";
5
5
  import { useCreateDiscoveryTask, useDiscoveryTask, useEventCounts, useListDiscoveryFilters, useListDiscoveryMethods, useObjectCounts, useSaveDiscovery } from "@ocelescope/api-base";
6
6
  import { Visualization } from "@ocelescope/resources";
7
- import { useEffect, useMemo, useState } from "react";
7
+ import { memo, useEffect, useMemo, useState } from "react";
8
8
  import { create } from "zustand";
9
9
  import { persist } from "zustand/middleware";
10
+ import validator from "@rjsf/validator-ajv8";
11
+ import dynamic from "next/dynamic.js";
10
12
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
13
  //#region src/stores/discoveryStore.ts
12
14
  const EMPTY = {
@@ -26,8 +28,9 @@ const useDiscoveryStore = create()(persist((set) => ({
26
28
  byOcel: {},
27
29
  setSelectedMethodId: (ocelId, methodId) => set((s) => updateOcel(s, ocelId, { selectedMethodId: methodId })),
28
30
  setFormData: (ocelId, methodId, data) => set((s) => {
31
+ const current = s.byOcel[ocelId] ?? EMPTY;
29
32
  return updateOcel(s, ocelId, { formDataByMethod: {
30
- ...(s.byOcel[ocelId] ?? EMPTY).formDataByMethod,
33
+ ...current.formDataByMethod,
31
34
  [methodId]: data
32
35
  } });
33
36
  }),
@@ -60,115 +63,115 @@ const normalizeFormData = (formData) => Object.fromEntries(Object.entries(formDa
60
63
  return true;
61
64
  }));
62
65
  //#endregion
63
- //#region src/components/DiscoveryField.tsx
64
- const DiscoveryField = ({ name, property, value, eventTypeOptions, objectTypeOptions, onChange }) => {
65
- const label = property.title ?? name;
66
- const description = property.description;
67
- const fieldType = property.fieldType;
68
- if (property.type === "array" && fieldType === "event_type") return /* @__PURE__ */ jsx(MultiSelect, {
69
- label,
70
- description,
71
- value: value ?? [],
72
- onChange,
73
- data: eventTypeOptions,
74
- clearable: true,
75
- searchable: true
76
- });
77
- if (property.type === "array" && fieldType === "object_type") return /* @__PURE__ */ jsx(MultiSelect, {
78
- label,
79
- description,
80
- value: value ?? [],
81
- onChange,
82
- data: objectTypeOptions,
83
- clearable: true,
84
- searchable: true
85
- });
86
- if (property.type === "string" && fieldType === "object_type") return /* @__PURE__ */ jsx(Select, {
87
- label,
88
- description,
89
- value: value ?? null,
90
- onChange,
91
- data: objectTypeOptions,
92
- searchable: true,
93
- allowDeselect: false
94
- });
95
- if (property.type === "string" && fieldType === "event_type") return /* @__PURE__ */ jsx(Select, {
96
- label,
97
- description,
98
- value: value ?? null,
99
- onChange,
100
- data: eventTypeOptions,
101
- searchable: true,
102
- allowDeselect: false
103
- });
104
- if (property.type === "string" && property.enum) return /* @__PURE__ */ jsx(Select, {
105
- label,
106
- description,
107
- value: value ?? null,
108
- onChange,
109
- data: property.enum.map((item, index) => ({
110
- value: item,
111
- label: property.enumNames?.[index] ?? item
112
- })),
113
- allowDeselect: false
66
+ //#region src/components/DiscoveryForm.tsx
67
+ const Form = dynamic(() => import("@rjsf/mantine").then((m) => m.Form), { ssr: false });
68
+ const MantineSliderWidget = ({ value, onChange, schema, label, required }) => {
69
+ const min = schema.minimum ?? schema.exclusiveMinimum;
70
+ const max = schema.maximum ?? schema.exclusiveMaximum;
71
+ const description = schema.description;
72
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
73
+ /* @__PURE__ */ jsx(Input.Label, {
74
+ required,
75
+ children: label
76
+ }),
77
+ description && /* @__PURE__ */ jsx(Input.Description, { children: description }),
78
+ /* @__PURE__ */ jsx(NumberInput, {
79
+ mt: "5px",
80
+ min,
81
+ max,
82
+ step: 1e-4,
83
+ decimalScale: 4,
84
+ hideControls: true,
85
+ value: typeof value === "number" ? value : schema.default ?? min,
86
+ onChange: (v) => typeof v === "number" && onChange(v)
87
+ }),
88
+ /* @__PURE__ */ jsx(Slider, {
89
+ min,
90
+ max,
91
+ step: 1e-4,
92
+ label: (v) => v.toFixed(4),
93
+ value: typeof value === "number" ? value : schema.default ?? min,
94
+ onChange
95
+ })
96
+ ] });
97
+ };
98
+ const WIDGETS = { "mantine-slider": MantineSliderWidget };
99
+ const FieldTemplate = ({ children, errors, help, hidden }) => {
100
+ if (hidden) return /* @__PURE__ */ jsx("div", {
101
+ style: { display: "none" },
102
+ children
114
103
  });
115
- if ((property.type === "number" || property.type === "integer") && property.minimum !== void 0 && property.maximum !== void 0) {
116
- const currentValue = typeof value === "number" ? value : property.default;
117
- const isPercentage = property.maximum === 100 && property.minimum === 0;
118
- return /* @__PURE__ */ jsxs(Stack, {
119
- gap: 6,
120
- children: [
121
- /* @__PURE__ */ jsx(Text, {
122
- size: "sm",
123
- fw: 500,
124
- children: label
125
- }),
126
- description && /* @__PURE__ */ jsx(Text, {
127
- size: "xs",
128
- c: "dimmed",
129
- children: description
130
- }),
131
- /* @__PURE__ */ jsx(Slider, {
132
- min: property.minimum,
133
- max: property.maximum,
134
- value: currentValue ?? property.minimum,
135
- onChange,
136
- label: (val) => isPercentage ? `${val}%` : val
137
- })
138
- ]
104
+ return /* @__PURE__ */ jsxs("div", { children: [
105
+ children,
106
+ errors,
107
+ help
108
+ ] });
109
+ };
110
+ const DescriptionFieldTemplate = ({ id, description }) => description ? /* @__PURE__ */ jsx(Input.Description, {
111
+ id,
112
+ mt: -8,
113
+ mb: 8,
114
+ children: description
115
+ }) : null;
116
+ const TEMPLATES = {
117
+ DescriptionFieldTemplate,
118
+ FieldTemplate
119
+ };
120
+ const UI_SCHEMA_BASE = { "ui:submitButtonOptions": { norender: true } };
121
+ const buildUiSchema = (schema) => {
122
+ const uiSchema = { ...UI_SCHEMA_BASE };
123
+ for (const [key, prop] of Object.entries(schema.properties ?? {})) if (prop.fieldType) uiSchema[key] = { "ui:field": prop.fieldType };
124
+ else if ((prop.type === "number" || prop.type === "integer") && (prop.minimum !== void 0 || prop.exclusiveMinimum !== void 0) && (prop.maximum !== void 0 || prop.exclusiveMaximum !== void 0)) uiSchema[key] = { "ui:widget": "mantine-slider" };
125
+ return uiSchema;
126
+ };
127
+ const makeOcelFields = (eventTypeOptions, objectTypeOptions) => {
128
+ const OcelTypeField = (options) => memo(({ schema, formData, onChange, required, fieldPathId: { path } }) => {
129
+ return /* @__PURE__ */ jsx(schema.type === "array" ? MultiSelect : Select, {
130
+ label: schema.title,
131
+ description: schema.description,
132
+ required,
133
+ value: formData ?? (schema.type === "array" ? [] : null),
134
+ onChange: (v) => onChange(v, path),
135
+ data: options,
136
+ clearable: true,
137
+ searchable: true
139
138
  });
140
- }
141
- if (property.type === "integer" || property.type === "number") return /* @__PURE__ */ jsx(NumberInput, {
142
- label,
143
- description,
144
- value,
145
- onChange,
146
- min: property.minimum,
147
- max: property.maximum
148
- });
149
- if (property.type === "boolean") return /* @__PURE__ */ jsx(Switch, {
150
- label,
151
- description,
152
- checked: Boolean(value),
153
- onChange: (event) => onChange(event.currentTarget.checked)
154
139
  });
155
- return /* @__PURE__ */ jsx(TextInput, {
156
- label,
157
- description,
158
- value: value ?? "",
159
- onChange: (event) => onChange(event.currentTarget.value)
140
+ return {
141
+ event_type: OcelTypeField(eventTypeOptions),
142
+ object_type: OcelTypeField(objectTypeOptions)
143
+ };
144
+ };
145
+ const DiscoveryForm = ({ schema, formData, onChange, eventTypeOptions, objectTypeOptions }) => {
146
+ const uiSchema = useMemo(() => buildUiSchema(schema), [schema]);
147
+ const fields = useMemo(() => makeOcelFields(eventTypeOptions, objectTypeOptions), [eventTypeOptions, objectTypeOptions]);
148
+ return /* @__PURE__ */ jsx(Form, {
149
+ schema: {
150
+ ...schema,
151
+ title: ""
152
+ },
153
+ formData,
154
+ validator,
155
+ uiSchema,
156
+ fields,
157
+ widgets: WIDGETS,
158
+ templates: TEMPLATES,
159
+ onChange: (data) => onChange(data.formData ?? {})
160
160
  });
161
161
  };
162
162
  //#endregion
163
163
  //#region src/components/DiscoveryFiltersSection.tsx
164
164
  const DiscoveryFiltersSection = ({ availableFilters, filters, onFiltersChange, eventTypeOptions, objectTypeOptions }) => {
165
- const updateField = (filterName, fieldName, value) => onFiltersChange(filters.map((f) => f.name === filterName ? {
166
- ...f,
167
- payload: {
168
- ...f.payload,
169
- [fieldName]: value
170
- }
171
- } : f));
165
+ const handleChange = (filterName, data) => {
166
+ if (filters.some((f) => f.name === filterName)) onFiltersChange(filters.map((f) => f.name === filterName ? {
167
+ ...f,
168
+ payload: data
169
+ } : f));
170
+ else onFiltersChange([...filters, {
171
+ name: filterName,
172
+ payload: data
173
+ }]);
174
+ };
172
175
  return /* @__PURE__ */ jsxs(Stack, {
173
176
  gap: "xs",
174
177
  children: [/* @__PURE__ */ jsx(Text, {
@@ -186,16 +189,12 @@ const DiscoveryFiltersSection = ({ availableFilters, filters, onFiltersChange, e
186
189
  fw: 500,
187
190
  mb: "xs",
188
191
  children: filterSchema.name
189
- }), /* @__PURE__ */ jsx(Stack, {
190
- gap: "sm",
191
- children: Object.entries(schema.properties ?? {}).map(([fieldName, property]) => /* @__PURE__ */ jsx(DiscoveryField, {
192
- name: fieldName,
193
- property,
194
- value: payload[fieldName],
195
- eventTypeOptions,
196
- objectTypeOptions,
197
- onChange: (value) => updateField(filterSchema.name, fieldName, value)
198
- }, fieldName))
192
+ }), /* @__PURE__ */ jsx(DiscoveryForm, {
193
+ schema,
194
+ formData: payload,
195
+ onChange: (data) => handleChange(filterSchema.name, data),
196
+ eventTypeOptions,
197
+ objectTypeOptions
199
198
  })]
200
199
  }, filterSchema.name);
201
200
  })]
@@ -223,7 +222,12 @@ const DiscoverySettingsContent = ({ methods, selectedMethodId, setSelectedMethod
223
222
  })),
224
223
  allowDeselect: false
225
224
  }),
226
- selectedVariants.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Select, {
225
+ selectedVariants.find((v) => v.methodId === selectedMethodId)?.description && /* @__PURE__ */ jsx(Text, {
226
+ size: "sm",
227
+ c: "dimmed",
228
+ children: selectedVariants.find((v) => v.methodId === selectedMethodId)?.description
229
+ }),
230
+ selectedVariants.length > 1 && /* @__PURE__ */ jsx(Select, {
227
231
  label: "Output Format",
228
232
  value: selectedMethodId,
229
233
  onChange: setSelectedMethodId,
@@ -232,22 +236,14 @@ const DiscoverySettingsContent = ({ methods, selectedMethodId, setSelectedMethod
232
236
  label: v.resourceType
233
237
  })),
234
238
  allowDeselect: false
235
- }), /* @__PURE__ */ jsx(Text, {
236
- size: "sm",
237
- c: "dimmed",
238
- children: selectedVariants.find((v) => v.methodId === selectedMethodId)?.description
239
- })] }),
240
- Object.entries(selectedSchema.properties ?? {}).map(([name, property]) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(DiscoveryField, {
241
- name,
242
- property,
243
- value: activeFormData[name],
239
+ }),
240
+ /* @__PURE__ */ jsx(DiscoveryForm, {
241
+ schema: selectedSchema,
242
+ formData: activeFormData,
243
+ onChange: setActiveFormData,
244
244
  eventTypeOptions: Object.keys(eventCounts),
245
- objectTypeOptions: Object.keys(objectCounts),
246
- onChange: (value) => setActiveFormData({
247
- ...activeFormData,
248
- [name]: value
249
- })
250
- }) }, name))
245
+ objectTypeOptions: Object.keys(objectCounts)
246
+ })
251
247
  ]
252
248
  }),
253
249
  /* @__PURE__ */ jsx(Divider, {}),
@@ -282,7 +278,11 @@ const DiscoveryPageContent = ({ ocelId }) => {
282
278
  key: PANEL_OPEN_KEY,
283
279
  defaultValue: true
284
280
  });
285
- const { data: methods = [], isLoading: isMethodsLoading, error: methodsError } = useListDiscoveryMethods();
281
+ const { data: rawMethods = [], isLoading: isMethodsLoading, error: methodsError } = useListDiscoveryMethods();
282
+ const methods = useMemo(() => rawMethods.map((m) => ({
283
+ ...m,
284
+ variants: m.variants.filter((v) => v.enabled)
285
+ })).filter((m) => m.variants.length), [rawMethods]);
286
286
  const { data: availableFilters = [] } = useListDiscoveryFilters();
287
287
  const { data: eventCounts = {} } = useEventCounts(ocelId, void 0, { query: { enabled: true } });
288
288
  const { data: objectCounts = {} } = useObjectCounts(ocelId, void 0, { query: { enabled: true } });
@@ -298,6 +298,16 @@ const DiscoveryPageContent = ({ ocelId }) => {
298
298
  ocelId,
299
299
  initializeFilters
300
300
  ]);
301
+ useEffect(() => {
302
+ if (!selectedMethodId || isMethodsLoading) return;
303
+ if (!methods.flatMap((m) => m.variants.map((v) => v.methodId)).includes(selectedMethodId)) setSelectedMethodId(ocelId, null);
304
+ }, [
305
+ methods,
306
+ selectedMethodId,
307
+ isMethodsLoading,
308
+ ocelId,
309
+ setSelectedMethodId
310
+ ]);
301
311
  useEffect(() => {
302
312
  if (selectedMethodId || methods.length === 0) return;
303
313
  setSelectedMethodId(ocelId, methods.flatMap((m) => m.variants).find((v) => v.resourceType === "DirectlyFollowsGraph")?.methodId ?? methods[0]?.variants[0]?.methodId ?? null);
@@ -341,7 +351,6 @@ const DiscoveryPageContent = ({ ocelId }) => {
341
351
  const { mutate: saveDiscovery, isPending: isSaving } = useSaveDiscovery({ mutation: { onSuccess: (_data, { taskId: savedId }) => setSavedTaskId(savedId) } });
342
352
  const isSaved = !!taskId && savedTaskId === taskId;
343
353
  const requestPayload = useMemo(() => normalizeFormData(activeFormData), [activeFormData]);
344
- const activeFilters = useMemo(() => filters.filter((entry) => Object.values(entry.payload).every((v) => !Array.isArray(v) || v.length > 0)), [filters]);
345
354
  useEffect(() => {
346
355
  if (!selectedMethodId) return;
347
356
  const timeoutId = window.setTimeout(() => {
@@ -350,7 +359,7 @@ const DiscoveryPageContent = ({ ocelId }) => {
350
359
  data: {
351
360
  methodId: selectedMethodId,
352
361
  parameters: requestPayload,
353
- filters: activeFilters
362
+ filters
354
363
  }
355
364
  });
356
365
  }, 650);
@@ -362,16 +371,16 @@ const DiscoveryPageContent = ({ ocelId }) => {
362
371
  selectedMethodId,
363
372
  requestPayload,
364
373
  ocelId,
365
- filters: activeFilters
374
+ filters
366
375
  }), [
367
376
  ocelId,
368
377
  requestPayload,
369
378
  selectedMethodId,
370
- activeFilters
379
+ filters
371
380
  ]),
372
381
  requestPayload,
373
382
  selectedMethodId,
374
- activeFilters
383
+ filters
375
384
  ]);
376
385
  const isDiscovering = isSubmittingDiscovery || task?.state === "PENDING" || task?.state === "STARTED";
377
386
  const errorMessage = submitError || (methodsError instanceof Error ? methodsError.message : void 0) || (taskError instanceof Error ? taskError.message : void 0) || (task?.state === "FAILURE" ? "The backend discovery task failed." : void 0);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ocelescope/discovery",
3
3
  "type": "module",
4
- "version": "0.1.2",
4
+ "version": "0.6.0",
5
5
  "description": "A module for discovery of OCELs",
6
6
  "author": "Sebastian Menne <sebastian.menne@rwth-aachen.de>",
7
7
  "license": "MIT",
@@ -27,40 +27,34 @@
27
27
  "access": "public"
28
28
  },
29
29
  "peerDependencies": {
30
- "@apidevtools/json-schema-ref-parser": "^15.3.1",
31
- "@mantine/carousel": "^9.4.0",
32
- "@mantine/core": "^9.4.0",
33
- "@mantine/hooks": "^9.4.0",
34
- "@rjsf/core": "^6.6.2",
35
- "@rjsf/mantine": "^6.6.2",
36
- "@rjsf/utils": "^6.6.2",
37
- "@rjsf/validator-ajv8": "^6.6.2",
30
+ "@mantine/carousel": "^9.4.2",
31
+ "@mantine/core": "^9.4.2",
32
+ "@mantine/hooks": "^9.4.2",
33
+ "@rjsf/core": "^6.7.0",
34
+ "@rjsf/mantine": "^6.7.0",
35
+ "@rjsf/utils": "^6.7.0",
36
+ "@rjsf/validator-ajv8": "^6.7.0",
38
37
  "@tanstack/react-query": "^5.90.21",
39
38
  "embla-carousel": "^8.6.0",
40
39
  "embla-carousel-react": "^8.6.0",
41
- "flat": "^6.0.1",
42
- "json-schema-traverse": "^1.0.0",
43
40
  "lucide-react": "^1.0.0",
44
- "next": "^16.1.6",
45
- "react": "^19.2.0",
46
- "react-dom": "^19.2.0",
41
+ "next": "^16.2.10",
42
+ "react": "^19.2.7",
43
+ "react-dom": "^19.2.7",
47
44
  "react-hook-form": "^7.71.2",
48
45
  "uniqolor": "^1.1.1",
49
46
  "zustand": "^5.0.11",
50
- "@ocelescope/api-base": "^0.1.0",
51
- "@ocelescope/resources": "^0.1.2",
52
- "@ocelescope/core": "^0.1.2"
47
+ "@ocelescope/api-base": "^0.6.0",
48
+ "@ocelescope/resources": "^0.6.0",
49
+ "@ocelescope/core": "^0.6.0"
53
50
  },
54
51
  "devDependencies": {
55
- "@apidevtools/json-schema-ref-parser": "^15.3.1",
56
- "@rjsf/mantine": "^6.6.2",
52
+ "@rjsf/mantine": "^6.7.0",
57
53
  "@types/file-saver": "^2.0.7",
58
- "@types/node": "^25.0.3",
59
- "@types/react": "^19.2.7",
54
+ "@types/node": "^26.1.1",
55
+ "@types/react": "^19.2.17",
60
56
  "@types/react-dom": "^19.2.3",
61
- "flat": "^6.0.1",
62
- "json-schema-traverse": "^1.0.0",
63
- "tsdown": "^0.22.0",
57
+ "tsdown": "^0.22.12",
64
58
  "typescript": "^5.9.3"
65
59
  },
66
60
  "scripts": {