@ocelescope/filter 0.10.2 → 0.11.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.d.ts +0 -2
- package/dist/index.js +1792 -647
- package/package.json +28 -21
package/dist/index.js
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import { ActivityPicker, AttributesTable, ObjectTypePicker,
|
|
2
|
-
import { CheckIcon, FilterIcon, PlusIcon, RotateCcwIcon, Trash2Icon, XIcon } from "lucide-react";
|
|
3
|
-
import { ActionIcon,
|
|
1
|
+
import { ActivityPicker, AttributesTable, BarList, ObjectTypePicker, OptionPicker, defineModule, defineModuleRoute, useCurrentOcel, useInvalidate } from "@ocelescope/core";
|
|
2
|
+
import { ArrowLeftIcon, CheckIcon, FilterIcon, GripVerticalIcon, InfoIcon, PlusIcon, RotateCcwIcon, Trash2Icon, XIcon } from "lucide-react";
|
|
3
|
+
import { ActionIcon, Badge, Box, Button, Center, Divider, Grid, Group, InlineStyles, Loader, LoadingOverlay, Menu, NumberInput, Paper, Popover, RangeSlider, ScrollArea, SegmentedControl, Select, SimpleGrid, Slider, Stack, Switch, Text, TextInput, Title, Tooltip, UnstyledButton, useMantineTheme } from "@mantine/core";
|
|
4
4
|
import hash from "object-hash";
|
|
5
|
-
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
5
|
+
import { keepPreviousData, useMutation, useQuery } from "@tanstack/react-query";
|
|
6
6
|
import { customFetch } from "@ocelescope/api-client";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { DateTimePicker } from "@mantine/dates";
|
|
7
|
+
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
8
|
+
import { Controller, useFieldArray, useForm, useWatch } from "react-hook-form";
|
|
9
|
+
import { useAttributeDistribution, useAttributeValues, useE2o, useE2oCountDistribution, useEventAttributes, useEventCounts, useO2o, useO2oCountDistribution, useObjectAttributes, useObjectCounts, useTimeInfo } from "@ocelescope/api-base";
|
|
11
10
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
-
import { useTimeInfo } from "@ocelescope/api-base";
|
|
13
|
-
import EChartsReact from "echarts-for-react";
|
|
14
11
|
import dayjs from "dayjs";
|
|
15
12
|
import isBetween from "dayjs/plugin/isBetween.js";
|
|
16
|
-
|
|
13
|
+
import { DateTimePicker } from "@mantine/dates";
|
|
14
|
+
import EChartsReact from "echarts-for-react";
|
|
15
|
+
import { useDebouncedValue } from "@mantine/hooks";
|
|
16
|
+
import dynamic from "next/dynamic.js";
|
|
17
|
+
import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
|
|
18
|
+
import { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
19
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
20
|
+
// src/lib/fetcher.ts
|
|
17
21
|
const customFetch$1 = async (config, options) => customFetch(config, options);
|
|
18
|
-
|
|
19
|
-
//#region src/api/base.ts
|
|
22
|
+
// src/api/base.ts
|
|
20
23
|
/**
|
|
21
24
|
* Generated by orval 🍺
|
|
22
25
|
* Do not edit manually.
|
|
@@ -36,6 +39,47 @@ const withQueryKey = (query, queryKey) => {
|
|
|
36
39
|
return result;
|
|
37
40
|
};
|
|
38
41
|
/**
|
|
42
|
+
* @summary Register View
|
|
43
|
+
*/
|
|
44
|
+
const registerView = (ocelId, timeFrameFilterActivityFilterObjectTypeFilterEventAttributeFilterObjectAttributeFilterE2OCountFilterO2OCountFilter, params, options, signal) => {
|
|
45
|
+
return customFetch$1({
|
|
46
|
+
url: `/api/external/modules/filter/v1/${ocelId}/views`,
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: { "Content-Type": "application/json" },
|
|
49
|
+
data: timeFrameFilterActivityFilterObjectTypeFilterEventAttributeFilterObjectAttributeFilterE2OCountFilterO2OCountFilter,
|
|
50
|
+
params,
|
|
51
|
+
signal
|
|
52
|
+
}, options);
|
|
53
|
+
};
|
|
54
|
+
const getRegisterViewQueryKey = (ocelId, timeFrameFilterActivityFilterObjectTypeFilterEventAttributeFilterObjectAttributeFilterE2OCountFilterO2OCountFilter, params) => {
|
|
55
|
+
return [
|
|
56
|
+
"POST",
|
|
57
|
+
`/api/external/modules/filter/v1/${ocelId}/views`,
|
|
58
|
+
...params ? [params] : [],
|
|
59
|
+
timeFrameFilterActivityFilterObjectTypeFilterEventAttributeFilterObjectAttributeFilterE2OCountFilterO2OCountFilter
|
|
60
|
+
];
|
|
61
|
+
};
|
|
62
|
+
const getRegisterViewQueryOptions = (ocelId, timeFrameFilterActivityFilterObjectTypeFilterEventAttributeFilterObjectAttributeFilterE2OCountFilterO2OCountFilter, params, options) => {
|
|
63
|
+
const { query: queryOptions, request: requestOptions } = options ?? {};
|
|
64
|
+
const queryKey = queryOptions?.queryKey ?? getRegisterViewQueryKey(ocelId, timeFrameFilterActivityFilterObjectTypeFilterEventAttributeFilterObjectAttributeFilterE2OCountFilterO2OCountFilter, params);
|
|
65
|
+
const queryFn = ({ signal }) => registerView(ocelId, timeFrameFilterActivityFilterObjectTypeFilterEventAttributeFilterObjectAttributeFilterE2OCountFilterO2OCountFilter, params, requestOptions, signal);
|
|
66
|
+
return {
|
|
67
|
+
queryKey,
|
|
68
|
+
queryFn,
|
|
69
|
+
enabled: ocelId !== null && ocelId !== void 0,
|
|
70
|
+
staleTime: 3e5,
|
|
71
|
+
...queryOptions
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* @summary Register View
|
|
76
|
+
*/
|
|
77
|
+
function useRegisterView(ocelId, timeFrameFilterActivityFilterObjectTypeFilterEventAttributeFilterObjectAttributeFilterE2OCountFilterO2OCountFilter, params, options, queryClient) {
|
|
78
|
+
const queryOptions = getRegisterViewQueryOptions(ocelId, timeFrameFilterActivityFilterObjectTypeFilterEventAttributeFilterObjectAttributeFilterE2OCountFilterO2OCountFilter, params, options);
|
|
79
|
+
const query = useQuery(queryOptions, queryClient);
|
|
80
|
+
return withQueryKey(query, queryOptions.queryKey);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
39
83
|
* @summary Getfilter
|
|
40
84
|
*/
|
|
41
85
|
const getFilter = (ocelId, options, signal) => {
|
|
@@ -108,479 +152,1317 @@ const getSetFilterMutationOptions = (options) => {
|
|
|
108
152
|
const useSetFilter = (options, queryClient) => {
|
|
109
153
|
return useMutation(getSetFilterMutationOptions(options), queryClient);
|
|
110
154
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
155
|
+
// src/hooks/useAttributeFilterColumns.tsx
|
|
156
|
+
const getInitialCondition = (attribute) => {
|
|
157
|
+
switch (attribute.type) {
|
|
158
|
+
case "int": return { number_range: [Number.parseInt(`${attribute.min}`, 10), Number.parseInt(`${attribute.max}`, 10)] };
|
|
159
|
+
case "float": return { number_range: [Number.parseFloat(`${attribute.min}`), Number.parseFloat(`${attribute.max}`)] };
|
|
160
|
+
case "date": return { time_range: [String(attribute.min), String(attribute.max)] };
|
|
161
|
+
default: return {};
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
const getEntityType = (attribute) => {
|
|
165
|
+
if (!("entity_type_names" in attribute)) return attribute.entity_type;
|
|
166
|
+
return attribute.entity_type_names.length === 1 ? attribute.entity_type_names[0] : void 0;
|
|
167
|
+
};
|
|
168
|
+
const useAttributeFilter = ({ filter, onChange }) => {
|
|
169
|
+
return { filterColumns: useMemo(() => [{
|
|
170
|
+
accessor: "action",
|
|
171
|
+
title: "",
|
|
172
|
+
width: "100px",
|
|
173
|
+
render: (record) => /* @__PURE__ */ jsx(Button, {
|
|
174
|
+
size: "compact-sm",
|
|
175
|
+
variant: "default",
|
|
176
|
+
onClick: (event) => {
|
|
177
|
+
event.stopPropagation();
|
|
178
|
+
onChange({
|
|
179
|
+
type: filter.type,
|
|
180
|
+
attribute: record.name,
|
|
181
|
+
target_type: getEntityType(record),
|
|
182
|
+
...getInitialCondition(record)
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
children: "Use"
|
|
186
|
+
})
|
|
187
|
+
}], [filter.type, onChange]) };
|
|
188
|
+
};
|
|
189
|
+
// src/hooks/useView.ts
|
|
190
|
+
const ORIGINAL = { ocel_version: "original" };
|
|
191
|
+
const useView = (ocelId, steps) => {
|
|
192
|
+
const { data } = useRegisterView(ocelId, steps, void 0, { query: {
|
|
193
|
+
placeholderData: keepPreviousData,
|
|
194
|
+
staleTime: Infinity
|
|
195
|
+
} });
|
|
196
|
+
if (data === void 0) return;
|
|
197
|
+
return data === null ? ORIGINAL : { view_id: data };
|
|
198
|
+
};
|
|
199
|
+
const viewProps = (view) => "view_id" in view ? { viewId: view.view_id } : { ocelVersion: "original" };
|
|
200
|
+
// src/util/dayjs.ts
|
|
201
|
+
dayjs.extend(isBetween);
|
|
202
|
+
var dayjs_default = dayjs;
|
|
203
|
+
// src/components/Previewed.tsx
|
|
204
|
+
/** Renders ``children`` with the preview data once it is there. */
|
|
205
|
+
const Previewed = ({ query, children }) => {
|
|
206
|
+
if (query.data !== void 0) return children(query.data);
|
|
207
|
+
if (query.error) return /* @__PURE__ */ jsx(Text, {
|
|
208
|
+
c: "red",
|
|
209
|
+
size: "sm",
|
|
210
|
+
children: "Could not load the log as the steps before leave it."
|
|
211
|
+
});
|
|
212
|
+
return /* @__PURE__ */ jsx(Center, {
|
|
213
|
+
flex: 1,
|
|
214
|
+
mih: 120,
|
|
215
|
+
children: /* @__PURE__ */ jsx(Loader, { size: "sm" })
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
// src/components/FilterViews/AttributeFilter/util.ts
|
|
219
|
+
const DATE_FORMAT$1 = "YYYY-MM-DD HH:mm";
|
|
220
|
+
const formatValue = (value, type) => {
|
|
221
|
+
if (type === "date") return dayjs_default(value).format(DATE_FORMAT$1);
|
|
222
|
+
if (type === "float" && typeof value === "number") return value.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
223
|
+
return `${value}`;
|
|
224
|
+
};
|
|
225
|
+
// src/components/RangeView.tsx
|
|
226
|
+
const Histogram = ({ bins, value: [low, high], format, onSelect, exclude }) => {
|
|
227
|
+
const theme = useMantineTheme();
|
|
228
|
+
const option = useMemo(() => ({
|
|
229
|
+
tooltip: {
|
|
230
|
+
trigger: "axis",
|
|
231
|
+
axisPointer: { type: "shadow" },
|
|
232
|
+
formatter: (params) => {
|
|
233
|
+
const [param] = Array.isArray(params) ? params : [params];
|
|
234
|
+
const bin = bins[param?.dataIndex ?? 0];
|
|
235
|
+
if (!bin) return "";
|
|
236
|
+
return `${bin.start === bin.end ? format(bin.start) : `${format(bin.start)} – ${format(bin.end)}`}<br/><b>${bin.count.toLocaleString()}</b> values`;
|
|
130
237
|
}
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
238
|
+
},
|
|
239
|
+
grid: {
|
|
240
|
+
left: 8,
|
|
241
|
+
right: 8,
|
|
242
|
+
top: 8,
|
|
243
|
+
bottom: 24,
|
|
244
|
+
containLabel: true
|
|
245
|
+
},
|
|
246
|
+
xAxis: {
|
|
247
|
+
type: "category",
|
|
248
|
+
data: bins.map(({ start }) => format(start)),
|
|
249
|
+
axisTick: { show: false },
|
|
250
|
+
axisLabel: { hideOverlap: true }
|
|
251
|
+
},
|
|
252
|
+
yAxis: {
|
|
253
|
+
type: "value",
|
|
254
|
+
splitNumber: 3
|
|
255
|
+
},
|
|
256
|
+
series: [{
|
|
257
|
+
type: "bar",
|
|
258
|
+
barCategoryGap: "8%",
|
|
259
|
+
data: bins.map(({ start, end, count }) => {
|
|
260
|
+
const isKept = exclude ? start < low || end > high : end >= low && start <= high;
|
|
261
|
+
return {
|
|
262
|
+
value: count,
|
|
263
|
+
itemStyle: {
|
|
264
|
+
color: theme.colors.blue[6],
|
|
265
|
+
opacity: isKept ? 1 : .25
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
})
|
|
269
|
+
}]
|
|
270
|
+
}), [
|
|
271
|
+
bins,
|
|
272
|
+
low,
|
|
273
|
+
high,
|
|
274
|
+
format,
|
|
275
|
+
exclude,
|
|
276
|
+
theme
|
|
277
|
+
]);
|
|
278
|
+
return /* @__PURE__ */ jsx(EChartsReact, {
|
|
279
|
+
option,
|
|
280
|
+
style: { height: 260 },
|
|
281
|
+
onEvents: { click: ({ dataIndex }) => {
|
|
282
|
+
const bin = bins[dataIndex];
|
|
283
|
+
if (bin) onSelect([bin.start, bin.end]);
|
|
284
|
+
} }
|
|
285
|
+
});
|
|
286
|
+
};
|
|
287
|
+
const STEPS = 1e3;
|
|
288
|
+
const RangeSelector = ({ min, max, value: [low, high], onChange, format, snap = (value) => value, exclude }) => {
|
|
289
|
+
const span = max - min;
|
|
290
|
+
const toPosition = (value) => Math.round((value - min) / span * STEPS);
|
|
291
|
+
const toValue = (position) => {
|
|
292
|
+
if (position <= 0) return min;
|
|
293
|
+
if (position >= STEPS) return max;
|
|
294
|
+
return snap(min + position / STEPS * span);
|
|
136
295
|
};
|
|
296
|
+
return /* @__PURE__ */ jsx(RangeSlider, {
|
|
297
|
+
px: "xs",
|
|
298
|
+
min: 0,
|
|
299
|
+
max: STEPS,
|
|
300
|
+
minRange: 0,
|
|
301
|
+
inverted: exclude,
|
|
302
|
+
value: [toPosition(low), toPosition(high)],
|
|
303
|
+
label: (position) => format(toValue(position)),
|
|
304
|
+
onChange: ([start, end]) => onChange([toValue(start), toValue(end)])
|
|
305
|
+
});
|
|
137
306
|
};
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
307
|
+
const RangeView = ({ bins, min, max, value, onChange, format, snap, exclude = false, inputs }) => {
|
|
308
|
+
const [low, high] = value;
|
|
309
|
+
return /* @__PURE__ */ jsxs(Stack, {
|
|
310
|
+
gap: "sm",
|
|
311
|
+
children: [
|
|
312
|
+
/* @__PURE__ */ jsx(Histogram, {
|
|
313
|
+
bins,
|
|
314
|
+
value,
|
|
315
|
+
format,
|
|
316
|
+
onSelect: onChange,
|
|
317
|
+
exclude
|
|
318
|
+
}),
|
|
319
|
+
max > min && /* @__PURE__ */ jsx(RangeSelector, {
|
|
320
|
+
min,
|
|
321
|
+
max,
|
|
322
|
+
value,
|
|
323
|
+
onChange,
|
|
324
|
+
format,
|
|
325
|
+
snap,
|
|
326
|
+
exclude
|
|
327
|
+
}),
|
|
328
|
+
/* @__PURE__ */ jsxs(Group, {
|
|
329
|
+
align: "end",
|
|
330
|
+
wrap: "nowrap",
|
|
331
|
+
children: [/* @__PURE__ */ jsx(Box, {
|
|
332
|
+
flex: 1,
|
|
333
|
+
children: inputs
|
|
334
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
335
|
+
variant: "default",
|
|
336
|
+
onClick: () => onChange([min, max]),
|
|
337
|
+
disabled: low <= min && high >= max,
|
|
338
|
+
children: "Reset"
|
|
339
|
+
})]
|
|
340
|
+
}),
|
|
341
|
+
/* @__PURE__ */ jsx(Text, {
|
|
342
|
+
size: "xs",
|
|
343
|
+
c: "dimmed",
|
|
344
|
+
children: "Click a bar to set the range to it."
|
|
345
|
+
})
|
|
346
|
+
]
|
|
347
|
+
});
|
|
348
|
+
};
|
|
349
|
+
// src/components/FilterViews/AttributeFilter/views/RangeView/DateRangeView.tsx
|
|
350
|
+
const PICKER_FORMAT = "YYYY-MM-DD HH:mm:ss";
|
|
351
|
+
const toTime = (value) => dayjs_default(value).valueOf();
|
|
352
|
+
const toDate = (value) => dayjs_default(value).format(PICKER_FORMAT);
|
|
353
|
+
const formatTime = (value) => dayjs_default(value).format("YYYY-MM-DD HH:mm");
|
|
354
|
+
const DateRangeView = ({ data, filter, onChange }) => {
|
|
355
|
+
const min = toTime(data.min);
|
|
356
|
+
const max = toTime(data.max);
|
|
357
|
+
const bins = useMemo(() => (data.bins ?? []).map(({ start, end, count }) => ({
|
|
358
|
+
start: toTime(start),
|
|
359
|
+
end: toTime(end),
|
|
360
|
+
count
|
|
361
|
+
})), [data.bins]);
|
|
362
|
+
const [start, end] = filter.time_range ?? [null, null];
|
|
363
|
+
const value = [start != null ? toTime(start) : min, end != null ? toTime(end) : max];
|
|
364
|
+
const setRange = ([low, high]) => onChange({
|
|
365
|
+
...filter,
|
|
366
|
+
time_range: [toDate(low), toDate(high)]
|
|
367
|
+
});
|
|
368
|
+
return /* @__PURE__ */ jsx(RangeView, {
|
|
369
|
+
bins,
|
|
370
|
+
min,
|
|
371
|
+
max,
|
|
372
|
+
value,
|
|
373
|
+
onChange: setRange,
|
|
374
|
+
exclude: filter.mode === "exclude",
|
|
375
|
+
format: formatTime,
|
|
376
|
+
inputs: /* @__PURE__ */ jsxs(Group, {
|
|
377
|
+
wrap: "nowrap",
|
|
378
|
+
align: "end",
|
|
379
|
+
gap: "xs",
|
|
380
|
+
children: [
|
|
381
|
+
/* @__PURE__ */ jsx(DateTimePicker, {
|
|
382
|
+
flex: 1,
|
|
383
|
+
withSeconds: true,
|
|
384
|
+
value: toDate(value[0]),
|
|
385
|
+
minDate: toDate(min),
|
|
386
|
+
maxDate: toDate(value[1]),
|
|
387
|
+
onChange: (next) => setRange([next ? toTime(next) : min, value[1]])
|
|
388
|
+
}),
|
|
389
|
+
/* @__PURE__ */ jsx(Text, {
|
|
390
|
+
c: "dimmed",
|
|
391
|
+
size: "sm",
|
|
392
|
+
pb: 8,
|
|
393
|
+
children: "-"
|
|
394
|
+
}),
|
|
395
|
+
/* @__PURE__ */ jsx(DateTimePicker, {
|
|
396
|
+
flex: 1,
|
|
397
|
+
withSeconds: true,
|
|
398
|
+
value: toDate(value[1]),
|
|
399
|
+
minDate: toDate(value[0]),
|
|
400
|
+
maxDate: toDate(max),
|
|
401
|
+
onChange: (next) => setRange([value[0], next ? toTime(next) : max])
|
|
402
|
+
})
|
|
403
|
+
]
|
|
148
404
|
})
|
|
149
405
|
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
406
|
+
};
|
|
407
|
+
// src/components/FilterViews/AttributeFilter/views/RangeView/NumberRangeView.tsx
|
|
408
|
+
const formatInt = (value) => value.toLocaleString();
|
|
409
|
+
const formatFloat = (value) => value.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
410
|
+
const NumberRangeView = ({ data, filter, onChange }) => {
|
|
411
|
+
const isInt = data.type === "int";
|
|
412
|
+
const min = Number(data.min);
|
|
413
|
+
const max = Number(data.max);
|
|
414
|
+
const bins = useMemo(() => (data.bins ?? []).map(({ start, end, count }, index, all) => ({
|
|
415
|
+
start: Number(start),
|
|
416
|
+
end: isInt && index < all.length - 1 ? Number(end) - 1 : Number(end),
|
|
417
|
+
count
|
|
418
|
+
})), [data.bins, isInt]);
|
|
419
|
+
const [low, high] = filter.number_range ?? [null, null];
|
|
420
|
+
const value = [low ?? min, high ?? max];
|
|
421
|
+
const setRange = (number_range) => onChange({
|
|
422
|
+
...filter,
|
|
423
|
+
number_range
|
|
424
|
+
});
|
|
425
|
+
const toNumber = (next) => typeof next === "number" ? next : void 0;
|
|
426
|
+
return /* @__PURE__ */ jsx(RangeView, {
|
|
427
|
+
bins,
|
|
428
|
+
min,
|
|
429
|
+
max,
|
|
430
|
+
value,
|
|
431
|
+
onChange: setRange,
|
|
432
|
+
exclude: filter.mode === "exclude",
|
|
433
|
+
format: isInt ? formatInt : formatFloat,
|
|
434
|
+
snap: isInt ? Math.round : void 0,
|
|
435
|
+
inputs: /* @__PURE__ */ jsxs(Group, {
|
|
436
|
+
wrap: "nowrap",
|
|
437
|
+
align: "end",
|
|
438
|
+
gap: "xs",
|
|
439
|
+
children: [
|
|
440
|
+
/* @__PURE__ */ jsx(NumberInput, {
|
|
441
|
+
flex: 1,
|
|
442
|
+
value: value[0],
|
|
443
|
+
min,
|
|
444
|
+
max: value[1],
|
|
445
|
+
allowDecimal: !isInt,
|
|
446
|
+
onChange: (next) => setRange([toNumber(next) ?? min, value[1]])
|
|
447
|
+
}),
|
|
448
|
+
/* @__PURE__ */ jsx(Text, {
|
|
449
|
+
c: "dimmed",
|
|
450
|
+
size: "sm",
|
|
451
|
+
pb: 8,
|
|
452
|
+
children: "-"
|
|
453
|
+
}),
|
|
454
|
+
/* @__PURE__ */ jsx(NumberInput, {
|
|
455
|
+
flex: 1,
|
|
456
|
+
value: value[1],
|
|
457
|
+
min: value[0],
|
|
458
|
+
max,
|
|
459
|
+
allowDecimal: !isInt,
|
|
460
|
+
onChange: (next) => setRange([value[0], toNumber(next) ?? max])
|
|
461
|
+
})
|
|
462
|
+
]
|
|
463
|
+
})
|
|
464
|
+
});
|
|
465
|
+
};
|
|
466
|
+
// src/components/FilterViews/AttributeFilter/views/ValuesView/PatternView.tsx
|
|
467
|
+
const PatternView = ({ data, filter, onChange, ocelId, entityType, before }) => {
|
|
468
|
+
const [pattern] = useDebouncedValue(filter.regex ?? "", 300);
|
|
469
|
+
const exclude = filter.mode === "exclude";
|
|
470
|
+
const view = useView(ocelId, before);
|
|
471
|
+
const matches = useAttributeValues(ocelId, {
|
|
472
|
+
...view,
|
|
473
|
+
attribute: filter.attribute,
|
|
474
|
+
regex: pattern,
|
|
475
|
+
entity_type: entityType,
|
|
476
|
+
entity_names: filter.target_type ? [filter.target_type] : void 0
|
|
477
|
+
}, { query: {
|
|
478
|
+
enabled: view !== void 0 && pattern !== "",
|
|
479
|
+
placeholderData: keepPreviousData,
|
|
480
|
+
retry: false
|
|
481
|
+
} });
|
|
482
|
+
const isEmpty = pattern === "";
|
|
483
|
+
const matched = isEmpty ? data.count : matches.data?.count ?? 0;
|
|
484
|
+
const values = isEmpty ? data.values ?? [] : matches.data?.values ?? [];
|
|
485
|
+
const distinct = isEmpty ? data.distinct_values : matches.data?.distinct_values ?? 0;
|
|
486
|
+
const kept = exclude ? data.count - matched : matched;
|
|
487
|
+
return /* @__PURE__ */ jsxs(Stack, {
|
|
488
|
+
gap: "sm",
|
|
489
|
+
flex: 1,
|
|
490
|
+
mih: 0,
|
|
491
|
+
children: [
|
|
492
|
+
/* @__PURE__ */ jsx(TextInput, {
|
|
493
|
+
label: "Regular expression",
|
|
494
|
+
placeholder: "e.g. ^Warehouse",
|
|
495
|
+
value: filter.regex ?? "",
|
|
496
|
+
error: !isEmpty && matches.error ? "Invalid pattern" : void 0,
|
|
497
|
+
rightSection: matches.isFetching ? /* @__PURE__ */ jsx(Loader, { size: "xs" }) : void 0,
|
|
498
|
+
onChange: (event) => onChange({
|
|
499
|
+
...filter,
|
|
500
|
+
regex: event.currentTarget.value
|
|
501
|
+
})
|
|
502
|
+
}),
|
|
503
|
+
/* @__PURE__ */ jsxs(Group, {
|
|
504
|
+
justify: "space-between",
|
|
505
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
506
|
+
size: "sm",
|
|
507
|
+
fw: 500,
|
|
508
|
+
children: isEmpty ? "Most frequent values" : `${matched.toLocaleString()} values match (${distinct.toLocaleString()} distinct)`
|
|
509
|
+
}), /* @__PURE__ */ jsxs(Text, {
|
|
510
|
+
size: "sm",
|
|
511
|
+
c: "dimmed",
|
|
512
|
+
children: [
|
|
513
|
+
"Keeping ",
|
|
514
|
+
kept.toLocaleString(),
|
|
515
|
+
" of ",
|
|
516
|
+
data.count.toLocaleString(),
|
|
517
|
+
" ",
|
|
518
|
+
"values"
|
|
519
|
+
]
|
|
520
|
+
})]
|
|
521
|
+
}),
|
|
522
|
+
values.length > 0 ? /* @__PURE__ */ jsx(BarList, {
|
|
523
|
+
data: Object.fromEntries(values.map(({ value, count }) => [value, count])),
|
|
524
|
+
labelHeader: isEmpty ? "Value" : "Match",
|
|
525
|
+
maxVisibleItems: values.length,
|
|
526
|
+
style: {
|
|
527
|
+
flex: 1,
|
|
528
|
+
minHeight: 0
|
|
529
|
+
}
|
|
530
|
+
}) : !isEmpty && !matches.isFetching && /* @__PURE__ */ jsx(Text, {
|
|
531
|
+
size: "sm",
|
|
532
|
+
c: "dimmed",
|
|
533
|
+
ta: "center",
|
|
534
|
+
py: "md",
|
|
535
|
+
children: "No value matches the pattern."
|
|
536
|
+
}),
|
|
537
|
+
values.length < distinct && /* @__PURE__ */ jsxs(Text, {
|
|
538
|
+
size: "xs",
|
|
539
|
+
c: "dimmed",
|
|
162
540
|
children: [
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
},
|
|
170
|
-
allowDecimal: allowDecimals,
|
|
171
|
-
style: { flex: 1 }
|
|
172
|
-
}),
|
|
173
|
-
/* @__PURE__ */ jsx(Text, {
|
|
174
|
-
c: "dimmed",
|
|
175
|
-
size: "sm",
|
|
176
|
-
pb: 8,
|
|
177
|
-
children: "-"
|
|
178
|
-
}),
|
|
179
|
-
/* @__PURE__ */ jsx(NumberInput, {
|
|
180
|
-
value: currentMax,
|
|
181
|
-
min: currentMin ?? min,
|
|
182
|
-
max,
|
|
183
|
-
allowDecimal: allowDecimals,
|
|
184
|
-
onChange: (nextMax) => {
|
|
185
|
-
field.onChange([currentMin, nextMax]);
|
|
186
|
-
},
|
|
187
|
-
style: { flex: 1 }
|
|
188
|
-
})
|
|
541
|
+
"Showing the ",
|
|
542
|
+
values.length,
|
|
543
|
+
" most frequent of",
|
|
544
|
+
" ",
|
|
545
|
+
distinct.toLocaleString(),
|
|
546
|
+
" distinct values."
|
|
189
547
|
]
|
|
190
|
-
})
|
|
191
|
-
|
|
548
|
+
})
|
|
549
|
+
]
|
|
550
|
+
});
|
|
551
|
+
};
|
|
552
|
+
// src/components/FilterViews/AttributeFilter/views/ValuesView/ValuePicker.tsx
|
|
553
|
+
const ValuePicker = ({ data, filter, onChange, ocelId, entityType, before }) => {
|
|
554
|
+
const theme = useMantineTheme();
|
|
555
|
+
const [search, setSearch] = useState("");
|
|
556
|
+
const [debounced] = useDebouncedValue(search.trim(), 300);
|
|
557
|
+
const view = useView(ocelId, before);
|
|
558
|
+
const found = useAttributeValues(ocelId, {
|
|
559
|
+
...view,
|
|
560
|
+
attribute: filter.attribute,
|
|
561
|
+
search: debounced,
|
|
562
|
+
entity_type: entityType,
|
|
563
|
+
entity_names: filter.target_type ? [filter.target_type] : void 0
|
|
564
|
+
}, { query: {
|
|
565
|
+
enabled: view !== void 0 && debounced !== "",
|
|
566
|
+
placeholderData: keepPreviousData
|
|
567
|
+
} });
|
|
568
|
+
const isSearching = debounced !== "";
|
|
569
|
+
const values = isSearching ? found.data?.values ?? [] : data.values ?? [];
|
|
570
|
+
const total = isSearching ? found.data?.distinct_values ?? 0 : data.distinct_values;
|
|
571
|
+
const options = useMemo(() => values.map(({ value, count }) => ({
|
|
572
|
+
value,
|
|
573
|
+
label: value,
|
|
574
|
+
count,
|
|
575
|
+
color: theme.colors.blue[6]
|
|
576
|
+
})), [values, theme]);
|
|
577
|
+
const selected = (filter.values ?? []).map(String);
|
|
578
|
+
const exclude = filter.mode === "exclude";
|
|
579
|
+
const matched = useAttributeValues(ocelId, {
|
|
580
|
+
...view,
|
|
581
|
+
attribute: filter.attribute,
|
|
582
|
+
values: selected,
|
|
583
|
+
entity_type: entityType,
|
|
584
|
+
entity_names: filter.target_type ? [filter.target_type] : void 0
|
|
585
|
+
}, { query: {
|
|
586
|
+
enabled: view !== void 0 && selected.length > 0,
|
|
587
|
+
placeholderData: keepPreviousData
|
|
588
|
+
} }).data?.count ?? 0;
|
|
589
|
+
const kept = selected.length === 0 ? exclude ? 0 : data.count : exclude ? data.count - matched : matched;
|
|
590
|
+
return /* @__PURE__ */ jsxs(Stack, {
|
|
591
|
+
gap: "sm",
|
|
592
|
+
children: [
|
|
593
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
594
|
+
size: "sm",
|
|
595
|
+
c: "dimmed",
|
|
596
|
+
children: [
|
|
597
|
+
"Keeping ",
|
|
598
|
+
kept.toLocaleString(),
|
|
599
|
+
" of ",
|
|
600
|
+
data.count.toLocaleString(),
|
|
601
|
+
" values"
|
|
602
|
+
]
|
|
603
|
+
}),
|
|
604
|
+
/* @__PURE__ */ jsx(OptionPicker, {
|
|
605
|
+
variant: "list",
|
|
606
|
+
multiple: true,
|
|
607
|
+
counts: true,
|
|
608
|
+
bars: true,
|
|
609
|
+
cutoff: true,
|
|
610
|
+
sort: "count",
|
|
611
|
+
options,
|
|
612
|
+
loading: found.isFetching,
|
|
613
|
+
search,
|
|
614
|
+
onSearch: setSearch,
|
|
615
|
+
total,
|
|
616
|
+
value: selected,
|
|
617
|
+
onChange: (next) => onChange({
|
|
618
|
+
...filter,
|
|
619
|
+
values: next.length ? next : null
|
|
620
|
+
})
|
|
621
|
+
}),
|
|
622
|
+
values.length < total && /* @__PURE__ */ jsxs(Text, {
|
|
623
|
+
size: "xs",
|
|
624
|
+
c: "dimmed",
|
|
625
|
+
children: [
|
|
626
|
+
"Showing the ",
|
|
627
|
+
values.length,
|
|
628
|
+
" most frequent of ",
|
|
629
|
+
total.toLocaleString(),
|
|
630
|
+
" ",
|
|
631
|
+
"values. Search to find the others."
|
|
632
|
+
]
|
|
633
|
+
}),
|
|
634
|
+
selected.length === 0 && /* @__PURE__ */ jsxs(Text, {
|
|
635
|
+
size: "xs",
|
|
636
|
+
c: "dimmed",
|
|
637
|
+
children: [
|
|
638
|
+
"No value picked: every value is ",
|
|
639
|
+
exclude ? "removed" : "kept",
|
|
640
|
+
"."
|
|
641
|
+
]
|
|
642
|
+
})
|
|
643
|
+
]
|
|
192
644
|
});
|
|
645
|
+
};
|
|
646
|
+
// src/components/FilterViews/AttributeFilter/views/ValuesView/ValuesView.tsx
|
|
647
|
+
const PICKER_OPTIONS = [{
|
|
648
|
+
label: "Pick values",
|
|
649
|
+
value: "values"
|
|
650
|
+
}, {
|
|
651
|
+
label: "Pattern",
|
|
652
|
+
value: "pattern"
|
|
653
|
+
}];
|
|
654
|
+
const ValuesView = (props) => {
|
|
655
|
+
const { filter, onChange } = props;
|
|
656
|
+
const picker = filter.regex != null ? "pattern" : "values";
|
|
657
|
+
return /* @__PURE__ */ jsxs(Stack, {
|
|
658
|
+
gap: "sm",
|
|
659
|
+
flex: 1,
|
|
660
|
+
mih: 0,
|
|
661
|
+
children: [/* @__PURE__ */ jsx(SegmentedControl, {
|
|
662
|
+
size: "xs",
|
|
663
|
+
w: "fit-content",
|
|
664
|
+
value: picker,
|
|
665
|
+
data: PICKER_OPTIONS,
|
|
666
|
+
onChange: (next) => onChange({
|
|
667
|
+
...filter,
|
|
668
|
+
values: null,
|
|
669
|
+
regex: next === "pattern" ? "" : null
|
|
670
|
+
})
|
|
671
|
+
}), picker === "pattern" ? /* @__PURE__ */ jsx(PatternView, { ...props }) : /* @__PURE__ */ jsx(ValuePicker, { ...props })]
|
|
672
|
+
});
|
|
673
|
+
};
|
|
674
|
+
// src/components/FilterViews/AttributeFilter/AttributeDetail.tsx
|
|
675
|
+
const ALL_TYPES = "__all__";
|
|
676
|
+
const MODE_OPTIONS$1 = [{
|
|
677
|
+
label: "Include",
|
|
678
|
+
value: "include"
|
|
679
|
+
}, {
|
|
680
|
+
label: "Exclude",
|
|
681
|
+
value: "exclude"
|
|
682
|
+
}];
|
|
683
|
+
const BINS$1 = 40;
|
|
684
|
+
const Stat$1 = ({ label, value }) => /* @__PURE__ */ jsxs(Stack, {
|
|
685
|
+
gap: 0,
|
|
686
|
+
miw: 0,
|
|
687
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
688
|
+
size: "xs",
|
|
689
|
+
c: "dimmed",
|
|
690
|
+
tt: "uppercase",
|
|
691
|
+
fw: 600,
|
|
692
|
+
children: label
|
|
693
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
694
|
+
size: "sm",
|
|
695
|
+
fw: 500,
|
|
696
|
+
truncate: true,
|
|
697
|
+
children: typeof value === "number" ? value.toLocaleString() : value
|
|
698
|
+
})]
|
|
193
699
|
});
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
700
|
+
const AttributeDetail = memo(({ ocelId, entityType, filter, onChange, before, scopes, onClear }) => {
|
|
701
|
+
const view = useView(ocelId, before);
|
|
702
|
+
const distribution = useAttributeDistribution(ocelId, {
|
|
703
|
+
...view,
|
|
704
|
+
attribute: filter.attribute,
|
|
705
|
+
entity_type: entityType,
|
|
706
|
+
entity_names: filter.target_type ? [filter.target_type] : void 0,
|
|
707
|
+
bins: BINS$1
|
|
708
|
+
}, { query: {
|
|
709
|
+
enabled: view !== void 0,
|
|
710
|
+
placeholderData: keepPreviousData
|
|
711
|
+
} });
|
|
712
|
+
const typeNoun = entityType === "events" ? "activities" : "object types";
|
|
713
|
+
return /* @__PURE__ */ jsxs(Stack, {
|
|
714
|
+
gap: "md",
|
|
715
|
+
flex: 1,
|
|
716
|
+
mih: 0,
|
|
717
|
+
children: [/* @__PURE__ */ jsxs(Group, {
|
|
718
|
+
justify: "space-between",
|
|
719
|
+
wrap: "nowrap",
|
|
720
|
+
children: [/* @__PURE__ */ jsxs(Group, {
|
|
721
|
+
gap: "sm",
|
|
200
722
|
wrap: "nowrap",
|
|
201
|
-
|
|
202
|
-
gap: "xs",
|
|
723
|
+
miw: 0,
|
|
203
724
|
children: [
|
|
204
|
-
/* @__PURE__ */ jsx(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
field.onChange([nextMin, field.value?.[1] ?? max]);
|
|
211
|
-
},
|
|
212
|
-
style: { flex: 1 }
|
|
725
|
+
/* @__PURE__ */ jsx(Button, {
|
|
726
|
+
variant: "subtle",
|
|
727
|
+
size: "compact-sm",
|
|
728
|
+
leftSection: /* @__PURE__ */ jsx(ArrowLeftIcon, { size: 14 }),
|
|
729
|
+
onClick: onClear,
|
|
730
|
+
children: "Change attribute"
|
|
213
731
|
}),
|
|
214
|
-
/* @__PURE__ */ jsx(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
children: "-"
|
|
732
|
+
/* @__PURE__ */ jsx(Title, {
|
|
733
|
+
order: 5,
|
|
734
|
+
style: { overflowWrap: "anywhere" },
|
|
735
|
+
children: filter.attribute
|
|
219
736
|
}),
|
|
220
|
-
/* @__PURE__ */ jsx(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
maxDate: max,
|
|
224
|
-
withSeconds: true,
|
|
225
|
-
onChange: (nextMax) => {
|
|
226
|
-
field.onChange([field.value?.[0] ?? min, nextMax]);
|
|
227
|
-
},
|
|
228
|
-
style: { flex: 1 }
|
|
737
|
+
distribution.data && /* @__PURE__ */ jsx(Badge, {
|
|
738
|
+
variant: "light",
|
|
739
|
+
children: distribution.data.type
|
|
229
740
|
})
|
|
230
741
|
]
|
|
231
|
-
})
|
|
232
|
-
|
|
742
|
+
}), /* @__PURE__ */ jsxs(Group, {
|
|
743
|
+
gap: "sm",
|
|
744
|
+
wrap: "nowrap",
|
|
745
|
+
children: [/* @__PURE__ */ jsx(SegmentedControl, {
|
|
746
|
+
size: "xs",
|
|
747
|
+
data: MODE_OPTIONS$1,
|
|
748
|
+
value: filter.mode ?? "include",
|
|
749
|
+
onChange: (next) => onChange({
|
|
750
|
+
...filter,
|
|
751
|
+
mode: next
|
|
752
|
+
})
|
|
753
|
+
}), scopes.length > 1 ? /* @__PURE__ */ jsx(Select, {
|
|
754
|
+
size: "xs",
|
|
755
|
+
w: 220,
|
|
756
|
+
allowDeselect: false,
|
|
757
|
+
value: filter.target_type ?? ALL_TYPES,
|
|
758
|
+
data: [{
|
|
759
|
+
value: ALL_TYPES,
|
|
760
|
+
label: `All ${typeNoun}`
|
|
761
|
+
}, ...scopes.map((scope) => ({
|
|
762
|
+
value: scope,
|
|
763
|
+
label: scope
|
|
764
|
+
}))],
|
|
765
|
+
onChange: (next) => onChange({
|
|
766
|
+
...filter,
|
|
767
|
+
target_type: next === ALL_TYPES ? null : next
|
|
768
|
+
})
|
|
769
|
+
}) : scopes[0] && /* @__PURE__ */ jsx(Badge, {
|
|
770
|
+
variant: "outline",
|
|
771
|
+
color: "gray",
|
|
772
|
+
children: scopes[0]
|
|
773
|
+
})]
|
|
774
|
+
})]
|
|
775
|
+
}), /* @__PURE__ */ jsx(Previewed, {
|
|
776
|
+
query: distribution,
|
|
777
|
+
children: (data) => data ? /* @__PURE__ */ jsxs(Stack, {
|
|
778
|
+
gap: "md",
|
|
779
|
+
flex: 1,
|
|
780
|
+
mih: 0,
|
|
781
|
+
children: [/* @__PURE__ */ jsxs(SimpleGrid, {
|
|
782
|
+
cols: 4,
|
|
783
|
+
children: [
|
|
784
|
+
/* @__PURE__ */ jsx(Stat$1, {
|
|
785
|
+
label: "Values",
|
|
786
|
+
value: data.count
|
|
787
|
+
}),
|
|
788
|
+
/* @__PURE__ */ jsx(Stat$1, {
|
|
789
|
+
label: "Distinct",
|
|
790
|
+
value: data.distinct_values
|
|
791
|
+
}),
|
|
792
|
+
/* @__PURE__ */ jsx(Stat$1, {
|
|
793
|
+
label: "Min",
|
|
794
|
+
value: formatValue(data.min, data.type)
|
|
795
|
+
}),
|
|
796
|
+
/* @__PURE__ */ jsx(Stat$1, {
|
|
797
|
+
label: "Max",
|
|
798
|
+
value: formatValue(data.max, data.type)
|
|
799
|
+
})
|
|
800
|
+
]
|
|
801
|
+
}), data.type === "int" || data.type === "float" ? /* @__PURE__ */ jsx(NumberRangeView, {
|
|
802
|
+
data,
|
|
803
|
+
filter,
|
|
804
|
+
onChange
|
|
805
|
+
}) : data.type === "date" ? /* @__PURE__ */ jsx(DateRangeView, {
|
|
806
|
+
data,
|
|
807
|
+
filter,
|
|
808
|
+
onChange
|
|
809
|
+
}) : /* @__PURE__ */ jsx(ValuesView, {
|
|
810
|
+
data,
|
|
811
|
+
filter,
|
|
812
|
+
onChange,
|
|
813
|
+
ocelId,
|
|
814
|
+
entityType,
|
|
815
|
+
before
|
|
816
|
+
})]
|
|
817
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
818
|
+
size: "sm",
|
|
819
|
+
c: "dimmed",
|
|
820
|
+
children: "The attribute has no values in the log as the steps before leave it."
|
|
821
|
+
})
|
|
822
|
+
})]
|
|
233
823
|
});
|
|
234
824
|
});
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
control,
|
|
243
|
-
name: `${path}.number_range`,
|
|
244
|
-
min: Number.parseInt(`${record.min}`, 10),
|
|
245
|
-
max: Number.parseInt(`${record.max}`, 10),
|
|
246
|
-
allowDecimals: false
|
|
825
|
+
// src/components/FilterViews/AttributeFilter/AttributeFilter.tsx
|
|
826
|
+
const AttributePanel = (entityType) => {
|
|
827
|
+
const useTypedAttributes = entityType === "events" ? useEventAttributes : useObjectAttributes;
|
|
828
|
+
return ({ ocelId, filter, onChange, before }) => {
|
|
829
|
+
const { filterColumns } = useAttributeFilter({
|
|
830
|
+
filter,
|
|
831
|
+
onChange
|
|
247
832
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
833
|
+
const view = useView(ocelId, before);
|
|
834
|
+
const typed = useTypedAttributes(ocelId, view, { query: {
|
|
835
|
+
enabled: view !== void 0,
|
|
836
|
+
placeholderData: keepPreviousData
|
|
837
|
+
} });
|
|
838
|
+
const scopes = useMemo(() => (typed.data ?? []).filter(({ name }) => name === filter.attribute).map(({ entity_type }) => entity_type), [typed.data, filter.attribute]);
|
|
839
|
+
if (filter.attribute) return /* @__PURE__ */ jsx(AttributeDetail, {
|
|
840
|
+
ocelId,
|
|
841
|
+
entityType,
|
|
842
|
+
filter,
|
|
843
|
+
onChange,
|
|
844
|
+
before,
|
|
845
|
+
scopes,
|
|
846
|
+
onClear: () => onChange({
|
|
847
|
+
type: filter.type,
|
|
848
|
+
attribute: ""
|
|
849
|
+
})
|
|
259
850
|
});
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
851
|
+
return /* @__PURE__ */ jsx(Previewed, {
|
|
852
|
+
query: {
|
|
853
|
+
data: view,
|
|
854
|
+
isPending: true,
|
|
855
|
+
error: null
|
|
856
|
+
},
|
|
857
|
+
children: (view) => /* @__PURE__ */ jsx(AttributesTable, {
|
|
858
|
+
ocelId,
|
|
859
|
+
entityType,
|
|
860
|
+
...viewProps(view),
|
|
861
|
+
extraColumns: filterColumns,
|
|
862
|
+
subTableExtraColumns: filterColumns,
|
|
863
|
+
hideRange: true,
|
|
864
|
+
hideValues: true
|
|
865
|
+
})
|
|
263
866
|
});
|
|
867
|
+
};
|
|
868
|
+
};
|
|
869
|
+
const condition = (filter) => {
|
|
870
|
+
if (filter.regex) return `matches /${filter.regex}/`;
|
|
871
|
+
if (filter.number_range) {
|
|
872
|
+
const [low, high] = filter.number_range;
|
|
873
|
+
return `${low ?? "-∞"} – ${high ?? "∞"}`;
|
|
264
874
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
switch (attribute.type) {
|
|
270
|
-
case "string": return { regex: "" };
|
|
271
|
-
case "int": return { number_range: [Number.parseInt(`${attribute.min}`, 10), Number.parseInt(`${attribute.max}`, 10)] };
|
|
272
|
-
case "float": return { number_range: [Number.parseFloat(`${attribute.min}`), Number.parseFloat(`${attribute.max}`)] };
|
|
273
|
-
case "date": return { time_range: [String(attribute.min), String(attribute.max)] };
|
|
274
|
-
default: return {};
|
|
875
|
+
if (filter.time_range) {
|
|
876
|
+
const [start, end] = filter.time_range;
|
|
877
|
+
const format = (value, open) => value ? dayjs_default(value).format("YYYY-MM-DD") : open;
|
|
878
|
+
return `${format(start, "start")} – ${format(end, "end")}`;
|
|
275
879
|
}
|
|
880
|
+
if (filter.values) {
|
|
881
|
+
const shown = filter.values.slice(0, 3).join(", ");
|
|
882
|
+
const more = filter.values.length - 3;
|
|
883
|
+
return `in ${shown}${more > 0 ? ` +${more} more` : ""}`;
|
|
884
|
+
}
|
|
885
|
+
return "any value";
|
|
276
886
|
};
|
|
277
|
-
const
|
|
278
|
-
if (!
|
|
279
|
-
|
|
887
|
+
const summary$1 = (filter) => {
|
|
888
|
+
if (!filter.attribute) return "No attribute selected";
|
|
889
|
+
const scope = filter.target_type ? ` (${filter.target_type})` : "";
|
|
890
|
+
const negation = filter.mode === "exclude" ? "not " : "";
|
|
891
|
+
return `${filter.attribute}${scope}: ${negation}${condition(filter)}`;
|
|
892
|
+
};
|
|
893
|
+
const EventAttributeFilter = {
|
|
894
|
+
title: "Event Attribute",
|
|
895
|
+
description: "Keeps the events whose value of one attribute satisfies a condition, such as a value range or a pattern. Pick the attribute with Use, then set the condition on its value distribution: a range for numbers and dates, chosen values or a pattern otherwise. With Exclude, the matching events are removed instead. Activities without the attribute are left untouched.",
|
|
896
|
+
Panel: AttributePanel("events"),
|
|
897
|
+
create: () => ({
|
|
898
|
+
type: "event_attribute",
|
|
899
|
+
attribute: ""
|
|
900
|
+
}),
|
|
901
|
+
summary: summary$1,
|
|
902
|
+
isComplete: (filter) => filter.attribute !== ""
|
|
280
903
|
};
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
904
|
+
const ObjectAttributeFilter = {
|
|
905
|
+
title: "Object Attribute",
|
|
906
|
+
description: "Keeps the objects whose value of one attribute satisfies a condition, such as a value range or a pattern. Pick the attribute with Use, then set the condition on its value distribution: a range for numbers and dates, chosen values or a pattern otherwise. With Exclude, the matching objects are removed instead. Object types without the attribute are left untouched.",
|
|
907
|
+
Panel: AttributePanel("objects"),
|
|
908
|
+
create: () => ({
|
|
909
|
+
type: "object_attribute",
|
|
910
|
+
attribute: ""
|
|
911
|
+
}),
|
|
912
|
+
summary: summary$1,
|
|
913
|
+
isComplete: (filter) => filter.attribute !== ""
|
|
284
914
|
};
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
915
|
+
// src/components/FilterViews/RelationCountFilter/RelationDetail.tsx
|
|
916
|
+
const ANY_QUALIFIER = "__any__";
|
|
917
|
+
const BINS = 40;
|
|
918
|
+
const formatCount = (value) => value.toLocaleString();
|
|
919
|
+
const Stat = ({ label, value }) => /* @__PURE__ */ jsxs(Stack, {
|
|
920
|
+
gap: 0,
|
|
921
|
+
miw: 0,
|
|
922
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
923
|
+
size: "xs",
|
|
924
|
+
c: "dimmed",
|
|
925
|
+
tt: "uppercase",
|
|
926
|
+
fw: 600,
|
|
927
|
+
children: label
|
|
928
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
929
|
+
size: "sm",
|
|
930
|
+
fw: 500,
|
|
931
|
+
children: value.toLocaleString()
|
|
932
|
+
})]
|
|
933
|
+
});
|
|
934
|
+
const RelationDetail = ({ ocelId, kind, hooks, filter, onChange, before, onClear }) => {
|
|
935
|
+
const direction = filter.direction ?? "source";
|
|
936
|
+
const view = useView(ocelId, before);
|
|
937
|
+
const options = { query: {
|
|
938
|
+
enabled: view !== void 0,
|
|
939
|
+
placeholderData: keepPreviousData
|
|
940
|
+
} };
|
|
941
|
+
const qualifiers = hooks.useRelations(ocelId, {
|
|
942
|
+
...view,
|
|
943
|
+
with_qualifier: false,
|
|
944
|
+
source_types: [filter.source],
|
|
945
|
+
target_types: [filter.target]
|
|
946
|
+
}, options).data?.response[0]?.qualifiers ?? [];
|
|
947
|
+
const distribution = hooks.useDistribution(ocelId, {
|
|
948
|
+
...view,
|
|
949
|
+
source: filter.source,
|
|
950
|
+
target: filter.target,
|
|
951
|
+
direction,
|
|
952
|
+
qualifier: filter.qualifier ?? void 0,
|
|
953
|
+
bins: BINS
|
|
954
|
+
}, options);
|
|
955
|
+
const sides = kind === "e2o" ? {
|
|
956
|
+
source: "event",
|
|
957
|
+
target: "object"
|
|
958
|
+
} : {
|
|
959
|
+
source: filter.source,
|
|
960
|
+
target: filter.target
|
|
961
|
+
};
|
|
962
|
+
const isSource = direction === "source";
|
|
963
|
+
const countedNoun = kind === "e2o" && isSource ? "event" : "object";
|
|
964
|
+
const relatedNoun = kind === "e2o" && !isSource ? "events" : "objects";
|
|
965
|
+
const caption = `How many ${isSource ? filter.target : filter.source} ${relatedNoun} each ${isSource ? filter.source : filter.target} ${countedNoun} is related to`;
|
|
966
|
+
return /* @__PURE__ */ jsxs(Stack, {
|
|
967
|
+
gap: "md",
|
|
968
|
+
flex: 1,
|
|
969
|
+
mih: 0,
|
|
970
|
+
children: [/* @__PURE__ */ jsxs(Group, {
|
|
971
|
+
justify: "space-between",
|
|
972
|
+
wrap: "nowrap",
|
|
973
|
+
children: [/* @__PURE__ */ jsxs(Group, {
|
|
974
|
+
gap: "sm",
|
|
975
|
+
wrap: "nowrap",
|
|
976
|
+
miw: 0,
|
|
977
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
978
|
+
variant: "subtle",
|
|
979
|
+
size: "compact-sm",
|
|
980
|
+
leftSection: /* @__PURE__ */ jsx(ArrowLeftIcon, { size: 14 }),
|
|
981
|
+
onClick: onClear,
|
|
982
|
+
children: "Change relation"
|
|
983
|
+
}), /* @__PURE__ */ jsxs(Title, {
|
|
984
|
+
order: 5,
|
|
985
|
+
style: { overflowWrap: "anywhere" },
|
|
986
|
+
children: [
|
|
987
|
+
filter.source,
|
|
988
|
+
" → ",
|
|
989
|
+
filter.target
|
|
990
|
+
]
|
|
991
|
+
})]
|
|
992
|
+
}), /* @__PURE__ */ jsxs(Group, {
|
|
993
|
+
gap: "sm",
|
|
994
|
+
wrap: "nowrap",
|
|
995
|
+
children: [/* @__PURE__ */ jsx(SegmentedControl, {
|
|
996
|
+
size: "xs",
|
|
997
|
+
value: direction,
|
|
998
|
+
data: [{
|
|
999
|
+
label: `Per ${sides.source}`,
|
|
1000
|
+
value: "source"
|
|
1001
|
+
}, {
|
|
1002
|
+
label: `Per ${sides.target}`,
|
|
1003
|
+
value: "target"
|
|
1004
|
+
}],
|
|
1005
|
+
onChange: (next) => onChange({
|
|
1006
|
+
...filter,
|
|
1007
|
+
direction: next,
|
|
1008
|
+
range: [null, null]
|
|
1009
|
+
})
|
|
1010
|
+
}), qualifiers.length > 0 && /* @__PURE__ */ jsx(Select, {
|
|
1011
|
+
size: "xs",
|
|
1012
|
+
w: 200,
|
|
1013
|
+
allowDeselect: false,
|
|
1014
|
+
value: filter.qualifier ?? ANY_QUALIFIER,
|
|
1015
|
+
data: [{
|
|
1016
|
+
value: ANY_QUALIFIER,
|
|
1017
|
+
label: "Any qualifier"
|
|
1018
|
+
}, ...qualifiers.map((qualifier) => ({
|
|
1019
|
+
value: qualifier,
|
|
1020
|
+
label: qualifier
|
|
1021
|
+
}))],
|
|
1022
|
+
onChange: (next) => onChange({
|
|
1023
|
+
...filter,
|
|
1024
|
+
qualifier: next === ANY_QUALIFIER ? null : next
|
|
1025
|
+
})
|
|
1026
|
+
})]
|
|
1027
|
+
})]
|
|
1028
|
+
}), /* @__PURE__ */ jsx(Previewed, {
|
|
1029
|
+
query: distribution,
|
|
1030
|
+
children: (data) => /* @__PURE__ */ jsx(CountRange, {
|
|
1031
|
+
data,
|
|
1032
|
+
filter,
|
|
1033
|
+
onChange,
|
|
1034
|
+
countedLabel: countedNoun === "event" ? "Events" : "Objects",
|
|
1035
|
+
caption
|
|
1036
|
+
})
|
|
1037
|
+
})]
|
|
289
1038
|
});
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}, [
|
|
297
|
-
const [
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
1039
|
+
};
|
|
1040
|
+
const CountRange = ({ data, filter, onChange, countedLabel, caption }) => {
|
|
1041
|
+
const bins = useMemo(() => data.bins.map(({ start, end, count }, index, all) => ({
|
|
1042
|
+
start: Number(start),
|
|
1043
|
+
end: index < all.length - 1 ? Number(end) - 1 : Number(end),
|
|
1044
|
+
count
|
|
1045
|
+
})), [data.bins]);
|
|
1046
|
+
const [low, high] = filter.range;
|
|
1047
|
+
const value = [low ?? data.min, high ?? data.max];
|
|
1048
|
+
const setRange = (range) => onChange({
|
|
1049
|
+
...filter,
|
|
1050
|
+
range
|
|
1051
|
+
});
|
|
1052
|
+
return /* @__PURE__ */ jsxs(Stack, {
|
|
1053
|
+
gap: "md",
|
|
1054
|
+
children: [
|
|
1055
|
+
/* @__PURE__ */ jsxs(SimpleGrid, {
|
|
1056
|
+
cols: 4,
|
|
1057
|
+
children: [
|
|
1058
|
+
/* @__PURE__ */ jsx(Stat, {
|
|
1059
|
+
label: countedLabel,
|
|
1060
|
+
value: data.entities
|
|
1061
|
+
}),
|
|
1062
|
+
/* @__PURE__ */ jsx(Stat, {
|
|
1063
|
+
label: "Relations",
|
|
1064
|
+
value: data.relations
|
|
1065
|
+
}),
|
|
1066
|
+
/* @__PURE__ */ jsx(Stat, {
|
|
1067
|
+
label: "Min",
|
|
1068
|
+
value: data.min
|
|
1069
|
+
}),
|
|
1070
|
+
/* @__PURE__ */ jsx(Stat, {
|
|
1071
|
+
label: "Max",
|
|
1072
|
+
value: data.max
|
|
1073
|
+
})
|
|
1074
|
+
]
|
|
307
1075
|
}),
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
1076
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1077
|
+
size: "sm",
|
|
1078
|
+
c: "dimmed",
|
|
1079
|
+
children: caption
|
|
1080
|
+
}),
|
|
1081
|
+
/* @__PURE__ */ jsx(RangeView, {
|
|
1082
|
+
bins,
|
|
1083
|
+
min: data.min,
|
|
1084
|
+
max: data.max,
|
|
1085
|
+
value,
|
|
1086
|
+
onChange: setRange,
|
|
1087
|
+
format: formatCount,
|
|
1088
|
+
snap: Math.round,
|
|
1089
|
+
inputs: /* @__PURE__ */ jsxs(Group, {
|
|
1090
|
+
wrap: "nowrap",
|
|
1091
|
+
align: "end",
|
|
1092
|
+
gap: "xs",
|
|
314
1093
|
children: [
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
1094
|
+
/* @__PURE__ */ jsx(NumberInput, {
|
|
1095
|
+
flex: 1,
|
|
1096
|
+
value: value[0],
|
|
1097
|
+
min: data.min,
|
|
1098
|
+
max: value[1],
|
|
1099
|
+
allowDecimal: false,
|
|
1100
|
+
onChange: (next) => setRange([typeof next === "number" ? next : data.min, value[1]])
|
|
1101
|
+
}),
|
|
1102
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1103
|
+
c: "dimmed",
|
|
1104
|
+
size: "sm",
|
|
1105
|
+
pb: 8,
|
|
1106
|
+
children: "-"
|
|
1107
|
+
}),
|
|
1108
|
+
/* @__PURE__ */ jsx(NumberInput, {
|
|
1109
|
+
flex: 1,
|
|
1110
|
+
value: value[1],
|
|
1111
|
+
min: value[0],
|
|
1112
|
+
max: data.max,
|
|
1113
|
+
allowDecimal: false,
|
|
1114
|
+
onChange: (next) => setRange([value[0], typeof next === "number" ? next : data.max])
|
|
1115
|
+
})
|
|
318
1116
|
]
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
1117
|
+
})
|
|
1118
|
+
})
|
|
1119
|
+
]
|
|
1120
|
+
});
|
|
1121
|
+
};
|
|
1122
|
+
// src/components/FilterViews/RelationCountFilter/RelationGraph.tsx
|
|
1123
|
+
const OcelTypeGraph = dynamic(() => import("@r4pm/components").then((m) => m.OcelTypeGraph), { ssr: false });
|
|
1124
|
+
const eventId = (activity) => `event:${activity}`;
|
|
1125
|
+
const objectId = (objectType) => `object:${objectType}`;
|
|
1126
|
+
const EDGE_LIMIT = 10;
|
|
1127
|
+
const countOptions = (counts) => Object.entries(counts).map(([name, count]) => ({
|
|
1128
|
+
value: name,
|
|
1129
|
+
label: name,
|
|
1130
|
+
count
|
|
1131
|
+
}));
|
|
1132
|
+
const MIN_WIDTH = 1;
|
|
1133
|
+
const ScopePicker = ({ label, count, total, children }) => /* @__PURE__ */ jsxs(Group, {
|
|
1134
|
+
gap: 4,
|
|
1135
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
1136
|
+
size: "xs",
|
|
1137
|
+
c: "dimmed",
|
|
1138
|
+
children: [
|
|
1139
|
+
label,
|
|
1140
|
+
": ",
|
|
1141
|
+
count,
|
|
1142
|
+
" / ",
|
|
1143
|
+
total
|
|
1144
|
+
]
|
|
1145
|
+
}), /* @__PURE__ */ jsxs(Popover, {
|
|
1146
|
+
width: 260,
|
|
1147
|
+
position: "bottom",
|
|
1148
|
+
withArrow: true,
|
|
1149
|
+
shadow: "md",
|
|
1150
|
+
children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx(ActionIcon, {
|
|
1151
|
+
size: "md",
|
|
1152
|
+
variant: "transparent",
|
|
1153
|
+
children: /* @__PURE__ */ jsx(FilterIcon, { size: 16 })
|
|
1154
|
+
}) }), /* @__PURE__ */ jsx(Popover.Dropdown, { children })]
|
|
1155
|
+
})]
|
|
1156
|
+
});
|
|
1157
|
+
const RelationGraph = ({ ocelId, kind, hooks, filter, onChange, before }) => {
|
|
1158
|
+
const theme = useMantineTheme();
|
|
1159
|
+
const [showQualifiers, setShowQualifiers] = useState(true);
|
|
1160
|
+
const [pickedActivities, setActivities] = useState(null);
|
|
1161
|
+
const [pickedObjectTypes, setObjectTypes] = useState(null);
|
|
1162
|
+
const [pending, setPending] = useState(null);
|
|
1163
|
+
const [edgeLimit, setEdgeLimit] = useState(EDGE_LIMIT);
|
|
1164
|
+
const view = useView(ocelId, before);
|
|
1165
|
+
const options = { query: {
|
|
1166
|
+
enabled: view !== void 0,
|
|
1167
|
+
placeholderData: keepPreviousData
|
|
1168
|
+
} };
|
|
1169
|
+
const relations = hooks.useRelations(ocelId, {
|
|
1170
|
+
...view,
|
|
1171
|
+
with_qualifier: false
|
|
1172
|
+
}, options);
|
|
1173
|
+
const qualified = hooks.useRelations(ocelId, {
|
|
1174
|
+
...view,
|
|
1175
|
+
with_qualifier: true
|
|
1176
|
+
}, options);
|
|
1177
|
+
const events = useEventCounts(ocelId, view, { query: {
|
|
1178
|
+
enabled: view !== void 0,
|
|
1179
|
+
placeholderData: keepPreviousData
|
|
1180
|
+
} });
|
|
1181
|
+
const objects = useObjectCounts(ocelId, view, { query: {
|
|
1182
|
+
enabled: view !== void 0,
|
|
1183
|
+
placeholderData: keepPreviousData
|
|
1184
|
+
} });
|
|
1185
|
+
const query = {
|
|
1186
|
+
data: useMemo(() => {
|
|
1187
|
+
if (!relations.data || !qualified.data || !events.data || !objects.data) return;
|
|
1188
|
+
const summaries = relations.data.response;
|
|
1189
|
+
const sourceId = kind === "e2o" ? eventId : objectId;
|
|
1190
|
+
const edges = summaries.flatMap(({ source, target, sum: pairSum, qualifiers }) => {
|
|
1191
|
+
return (showQualifiers && (qualifiers?.length ?? 0) > 0 ? qualified.data.response.filter((row) => row.source === source && row.target === target) : [{
|
|
1192
|
+
qualifier: null,
|
|
1193
|
+
sum: pairSum
|
|
1194
|
+
}]).map(({ qualifier, sum }) => ({
|
|
1195
|
+
id: `${kind}:${source}->${target}:${qualifier ?? ""}`,
|
|
1196
|
+
source: sourceId(source),
|
|
1197
|
+
target: objectId(target),
|
|
1198
|
+
qualifier: qualifier ?? void 0,
|
|
1199
|
+
kind,
|
|
1200
|
+
relation: {
|
|
1201
|
+
source,
|
|
1202
|
+
target,
|
|
1203
|
+
qualifier
|
|
351
1204
|
},
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
1205
|
+
sum,
|
|
1206
|
+
pair: `${source}->${target}`,
|
|
1207
|
+
pairSum
|
|
1208
|
+
}));
|
|
1209
|
+
});
|
|
1210
|
+
const activityCounts = Object.fromEntries([...new Set(kind === "e2o" ? summaries.map(({ source }) => source) : [])].map((activity) => [activity, events.data[activity] ?? 0]));
|
|
1211
|
+
const objectTypeCounts = Object.fromEntries([...new Set(summaries.flatMap(({ source, target }) => kind === "e2o" ? [target] : [source, target]))].map((objectType) => [objectType, objects.data[objectType] ?? 0]));
|
|
1212
|
+
return {
|
|
1213
|
+
nodes: [...Object.entries(activityCounts).map(([activity, count]) => ({
|
|
1214
|
+
id: eventId(activity),
|
|
1215
|
+
label: activity,
|
|
1216
|
+
kind: "event",
|
|
1217
|
+
count
|
|
1218
|
+
})), ...Object.entries(objectTypeCounts).map(([objectType, count]) => ({
|
|
1219
|
+
id: objectId(objectType),
|
|
1220
|
+
label: objectType,
|
|
1221
|
+
kind: "object",
|
|
1222
|
+
count
|
|
1223
|
+
}))],
|
|
1224
|
+
edges,
|
|
1225
|
+
activityCounts,
|
|
1226
|
+
objectTypeCounts
|
|
1227
|
+
};
|
|
1228
|
+
}, [
|
|
1229
|
+
relations.data,
|
|
1230
|
+
qualified.data,
|
|
1231
|
+
events.data,
|
|
1232
|
+
objects.data,
|
|
1233
|
+
kind,
|
|
1234
|
+
showQualifiers
|
|
362
1235
|
]),
|
|
363
|
-
|
|
1236
|
+
isPending: relations.isPending || qualified.isPending || events.isPending || objects.isPending,
|
|
1237
|
+
error: relations.error ?? qualified.error ?? events.error ?? objects.error
|
|
364
1238
|
};
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
control,
|
|
371
|
-
path: entityType === "events" ? "event_attribute" : "object_attribute"
|
|
372
|
-
});
|
|
373
|
-
return /* @__PURE__ */ jsx(AttributesTable, {
|
|
374
|
-
ocelId,
|
|
375
|
-
entityType,
|
|
376
|
-
ocelVersion: "original",
|
|
377
|
-
extraColumns: filterColumns,
|
|
378
|
-
subTableExtraColumns: filterColumns,
|
|
379
|
-
visibleAttributes,
|
|
380
|
-
hideRange: true,
|
|
381
|
-
hideValues: true
|
|
382
|
-
});
|
|
383
|
-
};
|
|
384
|
-
const EventAttributeFilter = {
|
|
385
|
-
title: "Event Attribute",
|
|
386
|
-
description: "Filters events by the values of their attributes. For each attribute you can define a condition (e.g. a value range or an allowed set of values), and only events whose attribute values satisfy every condition are kept. Attributes without a condition are ignored.",
|
|
387
|
-
ViewComponent: AttributeFilter("events"),
|
|
388
|
-
generateDefault: () => []
|
|
389
|
-
};
|
|
390
|
-
const ObjectAttributeFilter = {
|
|
391
|
-
title: "Object Attribute",
|
|
392
|
-
description: "Filters objects by the values of their attributes. For each attribute you can define a condition (e.g. a value range or an allowed set of values), and only objects whose attribute values satisfy every condition are kept. Attributes without a condition are ignored.",
|
|
393
|
-
ViewComponent: AttributeFilter("objects"),
|
|
394
|
-
generateDefault: () => []
|
|
395
|
-
};
|
|
396
|
-
//#endregion
|
|
397
|
-
//#region src/hooks/useRelationFilterColumns.tsx
|
|
398
|
-
const filterKey = ({ source, target, qualifier }) => `${source}-${qualifier ?? ""}-${target}`;
|
|
399
|
-
const resolveQualifier = (record) => record.qualifiers?.length === 1 ? record.qualifiers[0] : void 0;
|
|
400
|
-
const useRelationCountFilter = ({ path, control }) => {
|
|
401
|
-
const { fields, append, remove } = useFieldArray({
|
|
402
|
-
name: path,
|
|
403
|
-
control
|
|
1239
|
+
const select = (relation) => onChange({
|
|
1240
|
+
...filter,
|
|
1241
|
+
...relation,
|
|
1242
|
+
direction: "source",
|
|
1243
|
+
range: [null, null]
|
|
404
1244
|
});
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const {
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}),
|
|
438
|
-
filtering: showOnlyFiltered,
|
|
439
|
-
render: (record) => {
|
|
440
|
-
const qualifier = resolveQualifier(record);
|
|
441
|
-
const filterIndex = filterIndexMap.get(filterKey({
|
|
442
|
-
...record,
|
|
443
|
-
qualifier
|
|
444
|
-
})) ?? -1;
|
|
445
|
-
if (filterIndex < 0) return /* @__PURE__ */ jsx(Text, {
|
|
446
|
-
size: "sm",
|
|
447
|
-
c: "dimmed",
|
|
448
|
-
children: "No filter applied"
|
|
1245
|
+
return /* @__PURE__ */ jsx(Previewed, {
|
|
1246
|
+
query,
|
|
1247
|
+
children: ({ nodes, edges: allEdges, activityCounts, objectTypeCounts }) => {
|
|
1248
|
+
const activities = pickedActivities ?? Object.keys(activityCounts);
|
|
1249
|
+
const objectTypes = pickedObjectTypes ?? Object.keys(objectTypeCounts);
|
|
1250
|
+
const picked = /* @__PURE__ */ new Set([...activities.map(eventId), ...objectTypes.map(objectId)]);
|
|
1251
|
+
const candidates = allEdges.filter(({ source, target }) => picked.has(source) && picked.has(target));
|
|
1252
|
+
const pairs = new Map(candidates.map(({ pair, pairSum }) => [pair, pairSum]));
|
|
1253
|
+
const shownPairs = new Set([...pairs].sort(([, a], [, b]) => b - a).slice(0, edgeLimit).map(([pair]) => pair));
|
|
1254
|
+
const edges = candidates.filter(({ pair }) => shownPairs.has(pair));
|
|
1255
|
+
const visibleNodeIds = new Set(edges.flatMap(({ source, target }) => [source, target]));
|
|
1256
|
+
const shownNodes = nodes.filter(({ id }) => visibleNodeIds.has(id));
|
|
1257
|
+
const structure = edges.map(({ id }) => id).sort().join("|");
|
|
1258
|
+
const largest = Math.max(1, ...edges.map(({ sum }) => sum));
|
|
1259
|
+
const byId = new Map(edges.map((edge) => [edge.id, edge]));
|
|
1260
|
+
const neighbours = (id) => new Set(edges.flatMap(({ source, target }) => source === id ? [target] : target === id ? [source] : []));
|
|
1261
|
+
const connected = pending ? neighbours(pending) : void 0;
|
|
1262
|
+
const clickNode = (id) => {
|
|
1263
|
+
if (!pending || pending === id) {
|
|
1264
|
+
setPending(pending === id ? null : id);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
const candidates = edges.filter(({ source, target }) => source === pending && target === id || source === id && target === pending);
|
|
1268
|
+
const [first] = candidates;
|
|
1269
|
+
if (!first) {
|
|
1270
|
+
setPending(id);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
setPending(null);
|
|
1274
|
+
select({
|
|
1275
|
+
...first.relation,
|
|
1276
|
+
qualifier: candidates.length === 1 ? first.relation.qualifier : null
|
|
449
1277
|
});
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
})
|
|
472
|
-
|
|
1278
|
+
};
|
|
1279
|
+
return /* @__PURE__ */ jsxs(Stack, {
|
|
1280
|
+
gap: "xs",
|
|
1281
|
+
flex: 1,
|
|
1282
|
+
mih: 0,
|
|
1283
|
+
children: [/* @__PURE__ */ jsxs(Group, {
|
|
1284
|
+
justify: "space-between",
|
|
1285
|
+
children: [/* @__PURE__ */ jsxs(Group, {
|
|
1286
|
+
gap: "md",
|
|
1287
|
+
children: [
|
|
1288
|
+
kind === "e2o" && /* @__PURE__ */ jsx(ScopePicker, {
|
|
1289
|
+
label: "Activities",
|
|
1290
|
+
count: activities.length,
|
|
1291
|
+
total: Object.keys(activityCounts).length,
|
|
1292
|
+
children: /* @__PURE__ */ jsx(OptionPicker, {
|
|
1293
|
+
options: countOptions(activityCounts),
|
|
1294
|
+
variant: "list",
|
|
1295
|
+
multiple: true,
|
|
1296
|
+
cutoff: true,
|
|
1297
|
+
value: activities,
|
|
1298
|
+
onChange: (next) => setActivities([...next])
|
|
1299
|
+
})
|
|
1300
|
+
}),
|
|
1301
|
+
/* @__PURE__ */ jsx(ScopePicker, {
|
|
1302
|
+
label: "Object types",
|
|
1303
|
+
count: objectTypes.length,
|
|
1304
|
+
total: Object.keys(objectTypeCounts).length,
|
|
1305
|
+
children: /* @__PURE__ */ jsx(OptionPicker, {
|
|
1306
|
+
options: countOptions(objectTypeCounts),
|
|
1307
|
+
variant: "list",
|
|
1308
|
+
multiple: true,
|
|
1309
|
+
cutoff: true,
|
|
1310
|
+
value: objectTypes,
|
|
1311
|
+
onChange: (next) => setObjectTypes([...next])
|
|
1312
|
+
})
|
|
1313
|
+
}),
|
|
1314
|
+
/* @__PURE__ */ jsxs(Group, {
|
|
1315
|
+
gap: "xs",
|
|
1316
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
1317
|
+
size: "xs",
|
|
473
1318
|
c: "dimmed",
|
|
1319
|
+
children: [
|
|
1320
|
+
"Relations: top ",
|
|
1321
|
+
Math.min(edgeLimit, pairs.size),
|
|
1322
|
+
" of",
|
|
1323
|
+
" ",
|
|
1324
|
+
pairs.size
|
|
1325
|
+
]
|
|
1326
|
+
}), /* @__PURE__ */ jsx(Slider, {
|
|
1327
|
+
w: 140,
|
|
474
1328
|
size: "sm",
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
onClick: (event) => {
|
|
522
|
-
event.stopPropagation();
|
|
523
|
-
remove(filterIndex);
|
|
524
|
-
},
|
|
525
|
-
children: /* @__PURE__ */ jsx(XIcon, { size: 16 })
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
}], [
|
|
529
|
-
append,
|
|
530
|
-
remove,
|
|
531
|
-
filterIndexMap,
|
|
532
|
-
path,
|
|
533
|
-
control,
|
|
534
|
-
showOnlyFiltered
|
|
535
|
-
]),
|
|
536
|
-
visibleRelations: showOnlyFiltered ? visibleRelations : void 0
|
|
537
|
-
};
|
|
538
|
-
};
|
|
539
|
-
//#endregion
|
|
540
|
-
//#region src/components/FilterViews/RelationCountFilter.tsx
|
|
541
|
-
const RelationCountFilter = (relationType) => ({ ocelId, control }) => {
|
|
542
|
-
const { filterColumns, visibleRelations } = useRelationCountFilter({
|
|
543
|
-
control,
|
|
544
|
-
path: relationType === "e2o" ? "e2o_count" : "o2o_count"
|
|
1329
|
+
min: 1,
|
|
1330
|
+
max: Math.max(1, pairs.size),
|
|
1331
|
+
value: Math.min(edgeLimit, Math.max(1, pairs.size)),
|
|
1332
|
+
onChange: setEdgeLimit
|
|
1333
|
+
})]
|
|
1334
|
+
}),
|
|
1335
|
+
/* @__PURE__ */ jsx(Switch, {
|
|
1336
|
+
size: "xs",
|
|
1337
|
+
label: "Qualifiers",
|
|
1338
|
+
checked: showQualifiers,
|
|
1339
|
+
onChange: (event) => setShowQualifiers(event.currentTarget.checked)
|
|
1340
|
+
})
|
|
1341
|
+
]
|
|
1342
|
+
}), pending ? /* @__PURE__ */ jsxs(Button, {
|
|
1343
|
+
size: "compact-xs",
|
|
1344
|
+
variant: "light",
|
|
1345
|
+
rightSection: /* @__PURE__ */ jsx(XIcon, { size: 12 }),
|
|
1346
|
+
onClick: () => setPending(null),
|
|
1347
|
+
children: [pending.slice(pending.indexOf(":") + 1), ": pick the second node"]
|
|
1348
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
1349
|
+
size: "xs",
|
|
1350
|
+
c: "dimmed",
|
|
1351
|
+
children: "Click an edge, or two nodes, to pick a relation"
|
|
1352
|
+
})]
|
|
1353
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
1354
|
+
flex: 1,
|
|
1355
|
+
mih: 420,
|
|
1356
|
+
pos: "relative",
|
|
1357
|
+
children: /* @__PURE__ */ jsx(OcelTypeGraph, {
|
|
1358
|
+
nodes: shownNodes,
|
|
1359
|
+
edges,
|
|
1360
|
+
nodeRingColor: (id) => id === pending ? theme.colors.blue[6] : void 0,
|
|
1361
|
+
nodeDimmed: (id) => connected !== void 0 && id !== pending && !connected.has(id),
|
|
1362
|
+
edgeStyle: (id) => {
|
|
1363
|
+
const edge = byId.get(id);
|
|
1364
|
+
return edge ? { width: MIN_WIDTH + 5 * (edge.sum / largest) } : void 0;
|
|
1365
|
+
},
|
|
1366
|
+
onNodeClick: clickNode,
|
|
1367
|
+
onEdgeClick: (id) => {
|
|
1368
|
+
const edge = byId.get(id);
|
|
1369
|
+
if (edge) select(edge.relation);
|
|
1370
|
+
}
|
|
1371
|
+
}, structure)
|
|
1372
|
+
})]
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
545
1375
|
});
|
|
546
|
-
|
|
1376
|
+
};
|
|
1377
|
+
// src/components/FilterViews/RelationCountFilter/util.ts
|
|
1378
|
+
const RELATION_HOOKS = {
|
|
1379
|
+
e2o: {
|
|
1380
|
+
useRelations: useE2o,
|
|
1381
|
+
useDistribution: useE2oCountDistribution
|
|
1382
|
+
},
|
|
1383
|
+
o2o: {
|
|
1384
|
+
useRelations: useO2o,
|
|
1385
|
+
useDistribution: useO2oCountDistribution
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
// src/components/FilterViews/RelationCountFilter/RelationCountFilter.tsx
|
|
1389
|
+
const RelationCountPanel = (kind) => {
|
|
1390
|
+
const hooks = RELATION_HOOKS[kind];
|
|
1391
|
+
return ({ ocelId, filter, onChange, before }) => filter.source ? /* @__PURE__ */ jsx(RelationDetail, {
|
|
547
1392
|
ocelId,
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
1393
|
+
kind,
|
|
1394
|
+
hooks,
|
|
1395
|
+
filter,
|
|
1396
|
+
onChange,
|
|
1397
|
+
before,
|
|
1398
|
+
onClear: () => onChange({
|
|
1399
|
+
type: filter.type,
|
|
1400
|
+
source: "",
|
|
1401
|
+
target: "",
|
|
1402
|
+
range: [null, null]
|
|
1403
|
+
})
|
|
1404
|
+
}) : /* @__PURE__ */ jsx(RelationGraph, {
|
|
1405
|
+
ocelId,
|
|
1406
|
+
kind,
|
|
1407
|
+
hooks,
|
|
1408
|
+
filter,
|
|
1409
|
+
onChange,
|
|
1410
|
+
before
|
|
555
1411
|
});
|
|
556
1412
|
};
|
|
1413
|
+
const summary = (filter) => {
|
|
1414
|
+
if (!filter.source) return "No relation selected";
|
|
1415
|
+
const [low, high] = filter.range;
|
|
1416
|
+
const qualifier = filter.qualifier ? ` (${filter.qualifier})` : "";
|
|
1417
|
+
return `${filter.source} → ${filter.target}${qualifier}: ${low ?? 0} – ${high ?? "∞"}`;
|
|
1418
|
+
};
|
|
557
1419
|
const E2OCountFilter = {
|
|
558
1420
|
title: "E2O Count",
|
|
559
|
-
description: "
|
|
560
|
-
|
|
561
|
-
|
|
1421
|
+
description: "Keeps the events of the source activity whose number of related objects of the target type (event-to-object relations) falls within a range. Pick the relation by clicking its edge in the graph, then set a minimum and/or maximum count on its distribution. Events of other activities are left untouched.",
|
|
1422
|
+
Panel: RelationCountPanel("e2o"),
|
|
1423
|
+
create: () => ({
|
|
1424
|
+
type: "e2o_count",
|
|
1425
|
+
source: "",
|
|
1426
|
+
target: "",
|
|
1427
|
+
range: [null, null]
|
|
1428
|
+
}),
|
|
1429
|
+
summary,
|
|
1430
|
+
isComplete: (filter) => filter.source !== "" && filter.target !== ""
|
|
562
1431
|
};
|
|
563
1432
|
const O2OCountFilter = {
|
|
564
1433
|
title: "O2O Count",
|
|
565
|
-
description: "
|
|
566
|
-
|
|
567
|
-
|
|
1434
|
+
description: "Keeps the objects of the source type whose number of related objects of the target type (object-to-object relations) falls within a range. Pick the relation by clicking its edge in the graph, then set a minimum and/or maximum count on its distribution. Objects of other types are left untouched.",
|
|
1435
|
+
Panel: RelationCountPanel("o2o"),
|
|
1436
|
+
create: () => ({
|
|
1437
|
+
type: "o2o_count",
|
|
1438
|
+
source: "",
|
|
1439
|
+
target: "",
|
|
1440
|
+
range: [null, null]
|
|
1441
|
+
}),
|
|
1442
|
+
summary,
|
|
1443
|
+
isComplete: (filter) => filter.source !== "" && filter.target !== ""
|
|
568
1444
|
};
|
|
569
|
-
|
|
570
|
-
//#region src/util/dayjs.ts
|
|
571
|
-
dayjs.extend(isBetween);
|
|
572
|
-
var dayjs_default = dayjs;
|
|
573
|
-
//#endregion
|
|
574
|
-
//#region src/components/FilterViews/TimeFrameFilter.tsx
|
|
1445
|
+
// src/components/FilterViews/TimeFrameFilter.tsx
|
|
575
1446
|
const DATE_FORMAT = "YYYY-MM-DD HH:mm";
|
|
576
|
-
const
|
|
1447
|
+
const MODE_OPTIONS = [{
|
|
1448
|
+
label: "Include",
|
|
1449
|
+
value: "include"
|
|
1450
|
+
}, {
|
|
1451
|
+
label: "Exclude",
|
|
1452
|
+
value: "exclude"
|
|
1453
|
+
}];
|
|
1454
|
+
const TimeGraph = memo(({ timeInfo, startDate, endDate, mode }) => {
|
|
577
1455
|
const theme = useMantineTheme();
|
|
578
1456
|
const option = useMemo(() => {
|
|
579
|
-
const buckets = timeInfo.date_distribution.map(({ start_timestamp, end_timestamp, entity_count }) =>
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
1457
|
+
const buckets = timeInfo.date_distribution.map(({ start_timestamp, end_timestamp, entity_count }) => {
|
|
1458
|
+
const overlapsRange = (!startDate || dayjs_default(end_timestamp).isAfter(dayjs_default(startDate))) && (!endDate || dayjs_default(start_timestamp).isBefore(dayjs_default(endDate)));
|
|
1459
|
+
const insideRange = (!startDate || !dayjs_default(start_timestamp).isBefore(dayjs_default(startDate))) && (!endDate || !dayjs_default(end_timestamp).isAfter(dayjs_default(endDate)));
|
|
1460
|
+
return {
|
|
1461
|
+
label: `${dayjs_default(start_timestamp).format(DATE_FORMAT)} - ${dayjs_default(end_timestamp).format(DATE_FORMAT)}`,
|
|
1462
|
+
count: Object.values(entity_count).reduce((acc, curr) => acc + curr, 0),
|
|
1463
|
+
isKept: mode === "exclude" ? !insideRange : overlapsRange
|
|
1464
|
+
};
|
|
1465
|
+
});
|
|
584
1466
|
return {
|
|
585
1467
|
tooltip: {
|
|
586
1468
|
trigger: "axis",
|
|
@@ -605,11 +1487,11 @@ const TimeGraph = memo(({ timeInfo, startDate, endDate }) => {
|
|
|
605
1487
|
type: "bar",
|
|
606
1488
|
name: "count",
|
|
607
1489
|
barCategoryGap: 0,
|
|
608
|
-
data: buckets.map(({ count,
|
|
1490
|
+
data: buckets.map(({ count, isKept }) => ({
|
|
609
1491
|
value: count,
|
|
610
1492
|
itemStyle: {
|
|
611
1493
|
color: theme.colors.blue[6],
|
|
612
|
-
opacity:
|
|
1494
|
+
opacity: isKept ? 1 : .25
|
|
613
1495
|
}
|
|
614
1496
|
}))
|
|
615
1497
|
}]
|
|
@@ -618,6 +1500,7 @@ const TimeGraph = memo(({ timeInfo, startDate, endDate }) => {
|
|
|
618
1500
|
timeInfo,
|
|
619
1501
|
startDate,
|
|
620
1502
|
endDate,
|
|
1503
|
+
mode,
|
|
621
1504
|
theme
|
|
622
1505
|
]);
|
|
623
1506
|
return /* @__PURE__ */ jsx(EChartsReact, {
|
|
@@ -625,7 +1508,7 @@ const TimeGraph = memo(({ timeInfo, startDate, endDate }) => {
|
|
|
625
1508
|
style: { height: 300 }
|
|
626
1509
|
});
|
|
627
1510
|
});
|
|
628
|
-
const TimeFrameSlider = ({ timeInfo, startTime, endTime, onChange }) => {
|
|
1511
|
+
const TimeFrameSlider = ({ timeInfo, startTime, endTime, mode, onChange }) => {
|
|
629
1512
|
const distribution = timeInfo.date_distribution;
|
|
630
1513
|
const lastIndex = distribution.length - 1;
|
|
631
1514
|
const startIndex = useMemo(() => {
|
|
@@ -645,6 +1528,7 @@ const TimeFrameSlider = ({ timeInfo, startTime, endTime, onChange }) => {
|
|
|
645
1528
|
min: 0,
|
|
646
1529
|
max: lastIndex,
|
|
647
1530
|
minRange: 0,
|
|
1531
|
+
inverted: mode === "exclude",
|
|
648
1532
|
value: [startIndex, endIndex],
|
|
649
1533
|
label: (value) => {
|
|
650
1534
|
const bucket = distribution[value];
|
|
@@ -655,84 +1539,99 @@ const TimeFrameSlider = ({ timeInfo, startTime, endTime, onChange }) => {
|
|
|
655
1539
|
}
|
|
656
1540
|
});
|
|
657
1541
|
};
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
1542
|
+
const TimeFramePanel = memo(({ ocelId, filter, onChange, before }) => {
|
|
1543
|
+
const view = useView(ocelId, before);
|
|
1544
|
+
const { data: timeInfo, isLoading } = useTimeInfo(ocelId, {
|
|
1545
|
+
...view,
|
|
1546
|
+
periods: 100
|
|
1547
|
+
}, { query: {
|
|
1548
|
+
enabled: view !== void 0,
|
|
1549
|
+
placeholderData: keepPreviousData
|
|
1550
|
+
} });
|
|
1551
|
+
const [start, end] = filter.time_range;
|
|
1552
|
+
const mode = filter.mode ?? "include";
|
|
1553
|
+
const setRange = (time_range) => onChange({
|
|
1554
|
+
...filter,
|
|
1555
|
+
time_range
|
|
1556
|
+
});
|
|
1557
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1558
|
+
pos: "relative",
|
|
1559
|
+
w: "100%",
|
|
1560
|
+
h: "100%",
|
|
1561
|
+
children: [/* @__PURE__ */ jsx(LoadingOverlay, { visible: isLoading }), timeInfo && /* @__PURE__ */ jsxs(Grid, {
|
|
1562
|
+
justify: "center",
|
|
1563
|
+
align: "center",
|
|
1564
|
+
children: [
|
|
1565
|
+
/* @__PURE__ */ jsx(Grid.Col, {
|
|
674
1566
|
span: 12,
|
|
675
|
-
children: /* @__PURE__ */ jsx(
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}), /* @__PURE__ */ jsx(Controller, {
|
|
685
|
-
control,
|
|
686
|
-
name: "time_frame.0.time_range",
|
|
687
|
-
defaultValue: [timeInfo.start_time, timeInfo.end_time],
|
|
688
|
-
render: ({ field }) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
689
|
-
/* @__PURE__ */ jsx(Grid.Col, {
|
|
690
|
-
span: 3,
|
|
691
|
-
children: /* @__PURE__ */ jsx(DateTimePicker, {
|
|
692
|
-
minDate: timeInfo.start_time,
|
|
693
|
-
maxDate: field.value?.[1] ?? timeInfo.end_time,
|
|
694
|
-
onChange: (newStart) => {
|
|
695
|
-
field.onChange([newStart, field.value?.[1]]);
|
|
696
|
-
},
|
|
697
|
-
value: field.value?.[0] ?? timeInfo.start_time
|
|
698
|
-
})
|
|
699
|
-
}),
|
|
700
|
-
/* @__PURE__ */ jsx(Grid.Col, {
|
|
701
|
-
span: 6,
|
|
702
|
-
children: /* @__PURE__ */ jsx(TimeFrameSlider, {
|
|
703
|
-
onChange: field.onChange,
|
|
704
|
-
timeInfo,
|
|
705
|
-
startTime: field.value?.[0] ?? timeInfo.start_time,
|
|
706
|
-
endTime: field.value?.[1] ?? timeInfo.end_time
|
|
707
|
-
})
|
|
708
|
-
}),
|
|
709
|
-
/* @__PURE__ */ jsx(Grid.Col, {
|
|
710
|
-
span: 3,
|
|
711
|
-
children: /* @__PURE__ */ jsx(DateTimePicker, {
|
|
712
|
-
minDate: field.value?.[0] ?? timeInfo.start_time,
|
|
713
|
-
maxDate: timeInfo.end_time,
|
|
714
|
-
onChange: (newEnd) => {
|
|
715
|
-
field.onChange([field.value?.[0], newEnd]);
|
|
716
|
-
},
|
|
717
|
-
value: field.value?.[1] ?? timeInfo.end_time
|
|
1567
|
+
children: /* @__PURE__ */ jsx(Group, {
|
|
1568
|
+
justify: "flex-end",
|
|
1569
|
+
children: /* @__PURE__ */ jsx(SegmentedControl, {
|
|
1570
|
+
size: "xs",
|
|
1571
|
+
data: MODE_OPTIONS,
|
|
1572
|
+
value: mode,
|
|
1573
|
+
onChange: (next) => onChange({
|
|
1574
|
+
...filter,
|
|
1575
|
+
mode: next
|
|
718
1576
|
})
|
|
719
1577
|
})
|
|
720
|
-
|
|
721
|
-
})
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1578
|
+
})
|
|
1579
|
+
}),
|
|
1580
|
+
/* @__PURE__ */ jsx(Grid.Col, {
|
|
1581
|
+
span: 12,
|
|
1582
|
+
children: /* @__PURE__ */ jsx(TimeGraph, {
|
|
1583
|
+
timeInfo,
|
|
1584
|
+
startDate: start ?? void 0,
|
|
1585
|
+
endDate: end ?? void 0,
|
|
1586
|
+
mode
|
|
1587
|
+
})
|
|
1588
|
+
}),
|
|
1589
|
+
/* @__PURE__ */ jsx(Grid.Col, {
|
|
1590
|
+
span: 3,
|
|
1591
|
+
children: /* @__PURE__ */ jsx(DateTimePicker, {
|
|
1592
|
+
minDate: timeInfo.start_time,
|
|
1593
|
+
maxDate: end ?? timeInfo.end_time,
|
|
1594
|
+
onChange: (newStart) => setRange([newStart, end]),
|
|
1595
|
+
value: start ?? timeInfo.start_time
|
|
1596
|
+
})
|
|
1597
|
+
}),
|
|
1598
|
+
/* @__PURE__ */ jsx(Grid.Col, {
|
|
1599
|
+
span: 6,
|
|
1600
|
+
children: /* @__PURE__ */ jsx(TimeFrameSlider, {
|
|
1601
|
+
onChange: setRange,
|
|
1602
|
+
timeInfo,
|
|
1603
|
+
startTime: start ?? timeInfo.start_time,
|
|
1604
|
+
endTime: end ?? timeInfo.end_time,
|
|
1605
|
+
mode
|
|
1606
|
+
})
|
|
1607
|
+
}),
|
|
1608
|
+
/* @__PURE__ */ jsx(Grid.Col, {
|
|
1609
|
+
span: 3,
|
|
1610
|
+
children: /* @__PURE__ */ jsx(DateTimePicker, {
|
|
1611
|
+
minDate: start ?? timeInfo.start_time,
|
|
1612
|
+
maxDate: timeInfo.end_time,
|
|
1613
|
+
onChange: (newEnd) => setRange([start, newEnd]),
|
|
1614
|
+
value: end ?? timeInfo.end_time
|
|
1615
|
+
})
|
|
1616
|
+
})
|
|
1617
|
+
]
|
|
1618
|
+
})]
|
|
1619
|
+
});
|
|
1620
|
+
});
|
|
1621
|
+
const bound = (value, open) => value ? dayjs_default(value).format(DATE_FORMAT) : open;
|
|
1622
|
+
const TimeFrameFilter = {
|
|
1623
|
+
title: "Timeframe",
|
|
1624
|
+
description: "Keeps only the events that occur within the selected time range, or with Exclude, removes them and keeps everything outside it. Use the date pickers or drag the range slider to set the start and end; the bar chart shows the event distribution over time, with kept events highlighted and removed events dimmed.",
|
|
1625
|
+
Panel: TimeFramePanel,
|
|
1626
|
+
create: () => ({
|
|
726
1627
|
type: "time_frame",
|
|
727
|
-
time_range: [null, null]
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
}
|
|
1628
|
+
time_range: [null, null],
|
|
1629
|
+
mode: "include"
|
|
1630
|
+
}),
|
|
1631
|
+
summary: ({ time_range: [start, end], mode }) => start || end ? `${mode === "exclude" ? "Excluding " : ""}${bound(start, "start")} – ${bound(end, "end")}` : "No range set",
|
|
1632
|
+
isComplete: ({ time_range: [start, end] }) => start != null || end != null
|
|
733
1633
|
};
|
|
734
|
-
|
|
735
|
-
//#region src/components/FilterViews/TypeForm.tsx
|
|
1634
|
+
// src/components/FilterViews/TypeForm.tsx
|
|
736
1635
|
const PICKER_CLASS = "ocelescope-type-filter-picker";
|
|
737
1636
|
const FillHeightStyles = () => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(InlineStyles, {
|
|
738
1637
|
deduplicate: true,
|
|
@@ -750,71 +1649,92 @@ const FillHeightStyles = () => /* @__PURE__ */ jsxs(Fragment, { children: [/* @_
|
|
|
750
1649
|
maxHeight: "none !important"
|
|
751
1650
|
}
|
|
752
1651
|
})] });
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
const
|
|
756
|
-
return
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1652
|
+
const listed = (names, noun) => {
|
|
1653
|
+
if (names.length === 0) return `No ${noun} selected`;
|
|
1654
|
+
const shown = names.slice(0, 2).join(", ");
|
|
1655
|
+
return names.length > 2 ? `${shown} +${names.length - 2} more` : shown;
|
|
1656
|
+
};
|
|
1657
|
+
const ActivityPanel = ({ ocelId, filter, onChange, before }) => {
|
|
1658
|
+
const view = useView(ocelId, before);
|
|
1659
|
+
return /* @__PURE__ */ jsx(Previewed, {
|
|
1660
|
+
query: {
|
|
1661
|
+
data: view,
|
|
1662
|
+
isPending: true,
|
|
1663
|
+
error: null
|
|
1664
|
+
},
|
|
1665
|
+
children: (view) => /* @__PURE__ */ jsxs(Stack, {
|
|
1666
|
+
gap: 4,
|
|
1667
|
+
flex: 1,
|
|
1668
|
+
mih: 0,
|
|
1669
|
+
className: PICKER_CLASS,
|
|
1670
|
+
children: [/* @__PURE__ */ jsx(FillHeightStyles, {}), /* @__PURE__ */ jsx(ActivityPicker, {
|
|
1671
|
+
variant: "list",
|
|
1672
|
+
multiple: true,
|
|
1673
|
+
cutoff: true,
|
|
1674
|
+
ocelId,
|
|
1675
|
+
...viewProps(view),
|
|
1676
|
+
value: filter.event_types,
|
|
1677
|
+
onChange: (next) => onChange({
|
|
1678
|
+
...filter,
|
|
1679
|
+
event_types: next
|
|
1680
|
+
})
|
|
1681
|
+
})]
|
|
1682
|
+
})
|
|
1683
|
+
});
|
|
1684
|
+
};
|
|
1685
|
+
const ObjectTypePanel = ({ ocelId, filter, onChange, before }) => {
|
|
1686
|
+
const view = useView(ocelId, before);
|
|
1687
|
+
return /* @__PURE__ */ jsx(Previewed, {
|
|
1688
|
+
query: {
|
|
1689
|
+
data: view,
|
|
1690
|
+
isPending: true,
|
|
1691
|
+
error: null
|
|
1692
|
+
},
|
|
1693
|
+
children: (view) => /* @__PURE__ */ jsxs(Stack, {
|
|
1694
|
+
gap: 4,
|
|
1695
|
+
flex: 1,
|
|
1696
|
+
mih: 0,
|
|
1697
|
+
className: PICKER_CLASS,
|
|
1698
|
+
children: [/* @__PURE__ */ jsx(FillHeightStyles, {}), /* @__PURE__ */ jsx(ObjectTypePicker, {
|
|
1699
|
+
variant: "list",
|
|
1700
|
+
multiple: true,
|
|
1701
|
+
cutoff: true,
|
|
1702
|
+
ocelId,
|
|
1703
|
+
...viewProps(view),
|
|
1704
|
+
value: filter.object_types,
|
|
1705
|
+
onChange: (next) => onChange({
|
|
1706
|
+
...filter,
|
|
1707
|
+
object_types: next
|
|
1708
|
+
})
|
|
1709
|
+
})]
|
|
1710
|
+
})
|
|
786
1711
|
});
|
|
787
1712
|
};
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const FILTER_MAP = {
|
|
1713
|
+
// src/components/FilterViews/index.tsx
|
|
1714
|
+
const FILTER_KINDS = {
|
|
791
1715
|
activity: {
|
|
792
1716
|
title: "Activity",
|
|
793
|
-
description: "
|
|
794
|
-
|
|
795
|
-
|
|
1717
|
+
description: "Keeps only the events of the selected activities (the event types). The list shows how many events exist per activity to help you decide what to keep.",
|
|
1718
|
+
Panel: ActivityPanel,
|
|
1719
|
+
create: () => ({
|
|
796
1720
|
type: "activity",
|
|
797
1721
|
event_types: [],
|
|
798
1722
|
mode: "include"
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
return [filter[0]];
|
|
803
|
-
}
|
|
1723
|
+
}),
|
|
1724
|
+
summary: (filter) => listed(filter.event_types, "activities"),
|
|
1725
|
+
isComplete: (filter) => filter.event_types.length > 0
|
|
804
1726
|
},
|
|
805
1727
|
object_type: {
|
|
806
1728
|
title: "Object Type",
|
|
807
|
-
description: "
|
|
808
|
-
|
|
809
|
-
|
|
1729
|
+
description: "Keeps only the objects of the selected types. The list shows how many objects exist per type to help you decide what to keep.",
|
|
1730
|
+
Panel: ObjectTypePanel,
|
|
1731
|
+
create: () => ({
|
|
810
1732
|
type: "object_type",
|
|
811
1733
|
object_types: [],
|
|
812
1734
|
mode: "include"
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
return [filter[0]];
|
|
817
|
-
}
|
|
1735
|
+
}),
|
|
1736
|
+
summary: (filter) => listed(filter.object_types, "object types"),
|
|
1737
|
+
isComplete: (filter) => filter.object_types.length > 0
|
|
818
1738
|
},
|
|
819
1739
|
time_frame: TimeFrameFilter,
|
|
820
1740
|
event_attribute: EventAttributeFilter,
|
|
@@ -822,100 +1742,327 @@ const FILTER_MAP = {
|
|
|
822
1742
|
e2o_count: E2OCountFilter,
|
|
823
1743
|
o2o_count: O2OCountFilter
|
|
824
1744
|
};
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
return filters;
|
|
838
|
-
};
|
|
839
|
-
const cleanUpFilterByKey = (key, filters) => {
|
|
840
|
-
return (FILTER_MAP[key].cleanUpFilters ?? defaultFilter)(filters);
|
|
841
|
-
};
|
|
842
|
-
const cleanUpFilters = (filter) => {
|
|
843
|
-
return Object.keys(filter).flatMap((key) => {
|
|
844
|
-
const filters = filter[key];
|
|
845
|
-
if (!filters) return [];
|
|
846
|
-
return cleanUpFilterByKey(key, filters);
|
|
847
|
-
}).filter((filter) => !!filter);
|
|
848
|
-
};
|
|
849
|
-
//#endregion
|
|
850
|
-
//#region src/components/FilterForm.tsx
|
|
851
|
-
const FilterForm = ({ ocelId, currentFilter, onSubmit }) => {
|
|
852
|
-
const { control, handleSubmit, reset, formState } = useForm({ defaultValues: generateDefaultFilter(currentFilter) });
|
|
853
|
-
const { currentTab, setCurrentTab } = useCurrentFilterTab({
|
|
854
|
-
tabs: Object.keys(FILTER_MAP),
|
|
855
|
-
defaultTab: "activity",
|
|
856
|
-
queryKey: "tab"
|
|
857
|
-
});
|
|
858
|
-
return /* @__PURE__ */ jsxs(Tabs, {
|
|
859
|
-
value: currentTab,
|
|
860
|
-
onChange: (newTab) => setCurrentTab(newTab),
|
|
861
|
-
keepMounted: false,
|
|
1745
|
+
const kindOf = (filter) => FILTER_KINDS[filter.type];
|
|
1746
|
+
const completeSteps = (steps) => steps.filter((step) => kindOf(step).isComplete(step));
|
|
1747
|
+
// src/components/Pipeline/PipelineStep.tsx
|
|
1748
|
+
const PipelineStep = ({ id, index, step, selected, onSelect, onRemove }) => {
|
|
1749
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
|
|
1750
|
+
const kind = kindOf(step);
|
|
1751
|
+
const complete = kind.isComplete(step);
|
|
1752
|
+
return /* @__PURE__ */ jsx(Paper, {
|
|
1753
|
+
ref: setNodeRef,
|
|
1754
|
+
withBorder: true,
|
|
1755
|
+
onClick: onSelect,
|
|
1756
|
+
bg: selected ? "var(--mantine-primary-color-light)" : void 0,
|
|
862
1757
|
style: {
|
|
863
|
-
|
|
864
|
-
|
|
1758
|
+
cursor: "pointer",
|
|
1759
|
+
overflow: "hidden",
|
|
1760
|
+
transform: CSS.Transform.toString(transform),
|
|
1761
|
+
transition,
|
|
1762
|
+
opacity: isDragging ? .6 : 1,
|
|
1763
|
+
zIndex: isDragging ? 1 : void 0,
|
|
1764
|
+
position: "relative"
|
|
865
1765
|
},
|
|
866
|
-
|
|
867
|
-
children: [/* @__PURE__ */ jsxs(Group, {
|
|
868
|
-
py: "xs",
|
|
1766
|
+
children: /* @__PURE__ */ jsxs(Group, {
|
|
869
1767
|
gap: 0,
|
|
870
1768
|
wrap: "nowrap",
|
|
871
|
-
|
|
1769
|
+
align: "stretch",
|
|
1770
|
+
children: [/* @__PURE__ */ jsx(UnstyledButton, {
|
|
1771
|
+
"aria-label": "Drag to reorder",
|
|
1772
|
+
w: 24,
|
|
1773
|
+
display: "flex",
|
|
1774
|
+
bg: "var(--mantine-color-default-hover)",
|
|
1775
|
+
c: "dimmed",
|
|
1776
|
+
style: {
|
|
1777
|
+
alignItems: "center",
|
|
1778
|
+
justifyContent: "center",
|
|
1779
|
+
cursor: isDragging ? "grabbing" : "grab",
|
|
1780
|
+
touchAction: "none",
|
|
1781
|
+
borderRight: "1px solid var(--mantine-color-default-border)"
|
|
1782
|
+
},
|
|
1783
|
+
onClick: (event) => event.stopPropagation(),
|
|
1784
|
+
...attributes,
|
|
1785
|
+
...listeners,
|
|
1786
|
+
children: /* @__PURE__ */ jsx(GripVerticalIcon, { size: 14 })
|
|
1787
|
+
}), /* @__PURE__ */ jsxs(Group, {
|
|
1788
|
+
gap: "xs",
|
|
1789
|
+
wrap: "nowrap",
|
|
1790
|
+
align: "start",
|
|
872
1791
|
flex: 1,
|
|
873
1792
|
miw: 0,
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1793
|
+
p: "xs",
|
|
1794
|
+
children: [/* @__PURE__ */ jsxs(Stack, {
|
|
1795
|
+
gap: 2,
|
|
1796
|
+
flex: 1,
|
|
1797
|
+
miw: 0,
|
|
1798
|
+
children: [/* @__PURE__ */ jsxs(Group, {
|
|
1799
|
+
gap: 6,
|
|
1800
|
+
wrap: "nowrap",
|
|
1801
|
+
children: [
|
|
1802
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1803
|
+
size: "xs",
|
|
1804
|
+
c: "dimmed",
|
|
1805
|
+
children: index + 1
|
|
1806
|
+
}),
|
|
1807
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1808
|
+
size: "sm",
|
|
1809
|
+
fw: 500,
|
|
1810
|
+
truncate: true,
|
|
1811
|
+
children: kind.title
|
|
1812
|
+
}),
|
|
1813
|
+
!complete && /* @__PURE__ */ jsx(Badge, {
|
|
1814
|
+
size: "xs",
|
|
1815
|
+
color: "yellow",
|
|
1816
|
+
variant: "light",
|
|
1817
|
+
children: "incomplete"
|
|
1818
|
+
})
|
|
1819
|
+
]
|
|
1820
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1821
|
+
size: "xs",
|
|
1822
|
+
c: "dimmed",
|
|
1823
|
+
truncate: true,
|
|
1824
|
+
children: kind.summary(step)
|
|
1825
|
+
})]
|
|
1826
|
+
}), /* @__PURE__ */ jsx(ActionIcon, {
|
|
1827
|
+
variant: "subtle",
|
|
1828
|
+
color: "red",
|
|
1829
|
+
size: "sm",
|
|
1830
|
+
"aria-label": "Remove step",
|
|
1831
|
+
onClick: (event) => {
|
|
1832
|
+
event.stopPropagation();
|
|
1833
|
+
onRemove();
|
|
1834
|
+
},
|
|
1835
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: 14 })
|
|
1836
|
+
})]
|
|
1837
|
+
})]
|
|
1838
|
+
})
|
|
1839
|
+
});
|
|
1840
|
+
};
|
|
1841
|
+
// src/components/Pipeline/PipelineList.tsx
|
|
1842
|
+
const KEYS = Object.keys(FILTER_KINDS);
|
|
1843
|
+
const PipelineList = ({ ids, steps, selectedId, onSelect, onRemove, onMove, onAdd, ...stackProps }) => {
|
|
1844
|
+
const [search, setSearch] = useState("");
|
|
1845
|
+
const shown = KEYS.filter((key) => FILTER_KINDS[key].title.toLowerCase().includes(search.trim().toLowerCase()));
|
|
1846
|
+
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
1847
|
+
const onDragEnd = ({ active, over }) => {
|
|
1848
|
+
if (!over || active.id === over.id) return;
|
|
1849
|
+
onMove(ids.indexOf(String(active.id)), ids.indexOf(String(over.id)));
|
|
1850
|
+
};
|
|
1851
|
+
return /* @__PURE__ */ jsxs(Stack, {
|
|
1852
|
+
gap: "xs",
|
|
1853
|
+
...stackProps,
|
|
1854
|
+
children: [/* @__PURE__ */ jsx(ScrollArea, {
|
|
899
1855
|
flex: 1,
|
|
900
1856
|
mih: 0,
|
|
901
|
-
|
|
1857
|
+
type: "auto",
|
|
1858
|
+
scrollbarSize: 6,
|
|
902
1859
|
children: /* @__PURE__ */ jsxs(Stack, {
|
|
903
|
-
gap: "
|
|
904
|
-
|
|
905
|
-
children: [/* @__PURE__ */ jsxs(Text, {
|
|
1860
|
+
gap: "xs",
|
|
1861
|
+
children: [ids.length === 0 && /* @__PURE__ */ jsx(Text, {
|
|
906
1862
|
size: "sm",
|
|
907
1863
|
c: "dimmed",
|
|
908
|
-
children:
|
|
909
|
-
}), /* @__PURE__ */ jsx(
|
|
910
|
-
|
|
911
|
-
|
|
1864
|
+
children: "No filters yet. Add one to start the pipeline."
|
|
1865
|
+
}), /* @__PURE__ */ jsx(DndContext, {
|
|
1866
|
+
sensors,
|
|
1867
|
+
collisionDetection: closestCenter,
|
|
1868
|
+
onDragEnd,
|
|
1869
|
+
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
1870
|
+
items: ids,
|
|
1871
|
+
strategy: verticalListSortingStrategy,
|
|
1872
|
+
children: ids.map((id, index) => {
|
|
1873
|
+
const step = steps[index];
|
|
1874
|
+
return step && /* @__PURE__ */ jsx(PipelineStep, {
|
|
1875
|
+
id,
|
|
1876
|
+
index,
|
|
1877
|
+
step,
|
|
1878
|
+
selected: id === selectedId,
|
|
1879
|
+
onSelect: () => onSelect(id),
|
|
1880
|
+
onRemove: () => onRemove(index)
|
|
1881
|
+
}, id);
|
|
1882
|
+
})
|
|
1883
|
+
})
|
|
912
1884
|
})]
|
|
913
1885
|
})
|
|
914
|
-
},
|
|
1886
|
+
}), /* @__PURE__ */ jsxs(Menu, {
|
|
1887
|
+
position: "bottom-start",
|
|
1888
|
+
width: "target",
|
|
1889
|
+
children: [/* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(Button, {
|
|
1890
|
+
variant: "light",
|
|
1891
|
+
leftSection: /* @__PURE__ */ jsx(PlusIcon, { size: 16 }),
|
|
1892
|
+
fullWidth: true,
|
|
1893
|
+
children: "Add filter"
|
|
1894
|
+
}) }), /* @__PURE__ */ jsxs(Menu.Dropdown, { children: [
|
|
1895
|
+
/* @__PURE__ */ jsx(Menu.Search, {
|
|
1896
|
+
value: search,
|
|
1897
|
+
onChange: (event) => setSearch(event.currentTarget.value),
|
|
1898
|
+
placeholder: "Search filters"
|
|
1899
|
+
}),
|
|
1900
|
+
shown.map((key) => /* @__PURE__ */ jsx(Menu.Item, {
|
|
1901
|
+
onClick: () => onAdd(key),
|
|
1902
|
+
children: FILTER_KINDS[key].title
|
|
1903
|
+
}, key)),
|
|
1904
|
+
shown.length === 0 && /* @__PURE__ */ jsx(Menu.Label, { children: "No filter matches" })
|
|
1905
|
+
] })]
|
|
1906
|
+
})]
|
|
1907
|
+
});
|
|
1908
|
+
};
|
|
1909
|
+
// src/components/FilterForm.tsx
|
|
1910
|
+
const CLEANUP_NOTE = "The steps run from top to bottom. After the last step the log is cleaned up: relations whose event or object is gone are removed, events without at least one E2O relation are filtered out, and objects without any E2O or O2O relation are filtered out too.";
|
|
1911
|
+
const FilterForm = ({ ocelId, currentFilter, onSubmit }) => {
|
|
1912
|
+
const { control, handleSubmit, reset, formState } = useForm({ defaultValues: { steps: currentFilter } });
|
|
1913
|
+
const { fields, append, remove, move } = useFieldArray({
|
|
1914
|
+
control,
|
|
1915
|
+
name: "steps"
|
|
1916
|
+
});
|
|
1917
|
+
const steps = useWatch({
|
|
1918
|
+
control,
|
|
1919
|
+
name: "steps"
|
|
1920
|
+
});
|
|
1921
|
+
const [selectedId, setSelectedId] = useState(fields[0]?.id);
|
|
1922
|
+
const ids = fields.map(({ id }) => id);
|
|
1923
|
+
const selectedIndex = fields.findIndex(({ id }) => id === selectedId);
|
|
1924
|
+
const selectedStep = steps[selectedIndex];
|
|
1925
|
+
const before = completeSteps(steps.slice(0, Math.max(selectedIndex, 0)));
|
|
1926
|
+
const selectAdded = useRef(false);
|
|
1927
|
+
useEffect(() => {
|
|
1928
|
+
if (selectAdded.current) {
|
|
1929
|
+
selectAdded.current = false;
|
|
1930
|
+
setSelectedId(fields[fields.length - 1]?.id);
|
|
1931
|
+
}
|
|
1932
|
+
}, [fields]);
|
|
1933
|
+
const addStep = (key) => {
|
|
1934
|
+
selectAdded.current = true;
|
|
1935
|
+
append(FILTER_KINDS[key].create());
|
|
1936
|
+
};
|
|
1937
|
+
return /* @__PURE__ */ jsxs(Group, {
|
|
1938
|
+
h: "100%",
|
|
1939
|
+
gap: 0,
|
|
1940
|
+
wrap: "nowrap",
|
|
1941
|
+
align: "stretch",
|
|
1942
|
+
children: [/* @__PURE__ */ jsx(Box, {
|
|
1943
|
+
flex: 1,
|
|
1944
|
+
miw: 0,
|
|
1945
|
+
p: "md",
|
|
1946
|
+
style: { overflow: "auto" },
|
|
1947
|
+
children: selectedStep ? /* @__PURE__ */ jsxs(Stack, {
|
|
1948
|
+
gap: "md",
|
|
1949
|
+
h: "100%",
|
|
1950
|
+
children: [/* @__PURE__ */ jsxs(Stack, {
|
|
1951
|
+
gap: 4,
|
|
1952
|
+
children: [/* @__PURE__ */ jsxs(Title, {
|
|
1953
|
+
order: 4,
|
|
1954
|
+
children: [
|
|
1955
|
+
selectedIndex + 1,
|
|
1956
|
+
". ",
|
|
1957
|
+
kindOf(selectedStep).title
|
|
1958
|
+
]
|
|
1959
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1960
|
+
size: "sm",
|
|
1961
|
+
c: "dimmed",
|
|
1962
|
+
children: kindOf(selectedStep).description
|
|
1963
|
+
})]
|
|
1964
|
+
}), /* @__PURE__ */ jsx(Controller, {
|
|
1965
|
+
control,
|
|
1966
|
+
name: `steps.${selectedIndex}`,
|
|
1967
|
+
render: ({ field }) => {
|
|
1968
|
+
const { Panel } = kindOf(field.value);
|
|
1969
|
+
return /* @__PURE__ */ jsx(Panel, {
|
|
1970
|
+
ocelId,
|
|
1971
|
+
filter: field.value,
|
|
1972
|
+
before,
|
|
1973
|
+
onChange: (step) => field.onChange({ target: { value: step } })
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
}, selectedId)]
|
|
1977
|
+
}) : /* @__PURE__ */ jsx(Center, {
|
|
1978
|
+
h: "100%",
|
|
1979
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1980
|
+
c: "dimmed",
|
|
1981
|
+
size: "sm",
|
|
1982
|
+
maw: 360,
|
|
1983
|
+
ta: "center",
|
|
1984
|
+
children: fields.length === 0 ? "Add a filter to the pipeline on the right to start filtering." : "Select a step on the right to edit it."
|
|
1985
|
+
})
|
|
1986
|
+
})
|
|
1987
|
+
}), /* @__PURE__ */ jsxs(Stack, {
|
|
1988
|
+
w: 320,
|
|
1989
|
+
gap: "sm",
|
|
1990
|
+
py: "md",
|
|
1991
|
+
style: { borderLeft: "1px solid var(--mantine-color-default-border)" },
|
|
1992
|
+
children: [
|
|
1993
|
+
/* @__PURE__ */ jsxs(Group, {
|
|
1994
|
+
justify: "space-between",
|
|
1995
|
+
wrap: "nowrap",
|
|
1996
|
+
h: 28,
|
|
1997
|
+
px: "md",
|
|
1998
|
+
children: [/* @__PURE__ */ jsxs(Group, {
|
|
1999
|
+
gap: 6,
|
|
2000
|
+
wrap: "nowrap",
|
|
2001
|
+
children: [/* @__PURE__ */ jsx(Title, {
|
|
2002
|
+
order: 5,
|
|
2003
|
+
lh: 1,
|
|
2004
|
+
children: "Pipeline"
|
|
2005
|
+
}), /* @__PURE__ */ jsx(Tooltip, {
|
|
2006
|
+
label: CLEANUP_NOTE,
|
|
2007
|
+
multiline: true,
|
|
2008
|
+
w: 280,
|
|
2009
|
+
withArrow: true,
|
|
2010
|
+
children: /* @__PURE__ */ jsx(InfoIcon, {
|
|
2011
|
+
size: 16,
|
|
2012
|
+
color: "var(--mantine-color-dimmed)",
|
|
2013
|
+
"aria-label": "How the pipeline is applied"
|
|
2014
|
+
})
|
|
2015
|
+
})]
|
|
2016
|
+
}), /* @__PURE__ */ jsxs(Group, {
|
|
2017
|
+
justify: "flex-end",
|
|
2018
|
+
gap: 4,
|
|
2019
|
+
wrap: "nowrap",
|
|
2020
|
+
ml: "auto",
|
|
2021
|
+
children: [formState.isDirty && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Tooltip, {
|
|
2022
|
+
label: "Apply",
|
|
2023
|
+
children: /* @__PURE__ */ jsx(ActionIcon, {
|
|
2024
|
+
variant: "subtle",
|
|
2025
|
+
onClick: handleSubmit((data) => onSubmit?.(completeSteps(data.steps))),
|
|
2026
|
+
color: "green",
|
|
2027
|
+
children: /* @__PURE__ */ jsx(CheckIcon, { size: 20 })
|
|
2028
|
+
})
|
|
2029
|
+
}), /* @__PURE__ */ jsx(Tooltip, {
|
|
2030
|
+
label: "Discard changes",
|
|
2031
|
+
children: /* @__PURE__ */ jsx(ActionIcon, {
|
|
2032
|
+
variant: "subtle",
|
|
2033
|
+
color: "yellow",
|
|
2034
|
+
onClick: () => reset({ steps: currentFilter }),
|
|
2035
|
+
children: /* @__PURE__ */ jsx(RotateCcwIcon, { size: 20 })
|
|
2036
|
+
})
|
|
2037
|
+
})] }), currentFilter.length > 0 && /* @__PURE__ */ jsx(Tooltip, {
|
|
2038
|
+
label: "Remove all filters",
|
|
2039
|
+
children: /* @__PURE__ */ jsx(ActionIcon, {
|
|
2040
|
+
variant: "subtle",
|
|
2041
|
+
color: "red",
|
|
2042
|
+
onClick: () => onSubmit?.([]),
|
|
2043
|
+
children: /* @__PURE__ */ jsx(Trash2Icon, { size: 20 })
|
|
2044
|
+
})
|
|
2045
|
+
})]
|
|
2046
|
+
})]
|
|
2047
|
+
}),
|
|
2048
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
2049
|
+
/* @__PURE__ */ jsx(PipelineList, {
|
|
2050
|
+
flex: 1,
|
|
2051
|
+
mih: 0,
|
|
2052
|
+
px: "md",
|
|
2053
|
+
ids,
|
|
2054
|
+
steps,
|
|
2055
|
+
selectedId,
|
|
2056
|
+
onSelect: setSelectedId,
|
|
2057
|
+
onRemove: remove,
|
|
2058
|
+
onMove: move,
|
|
2059
|
+
onAdd: addStep
|
|
2060
|
+
})
|
|
2061
|
+
]
|
|
2062
|
+
})]
|
|
915
2063
|
});
|
|
916
2064
|
};
|
|
917
|
-
|
|
918
|
-
//#region src/components/FilterPage.tsx
|
|
2065
|
+
// src/components/FilterPage.tsx
|
|
919
2066
|
const FilterPage = () => {
|
|
920
2067
|
const { id: ocelId } = useCurrentOcel();
|
|
921
2068
|
const { data: filterPipeline, refetch } = useGetFilter(ocelId ?? "", { query: { enabled: !!ocelId } });
|
|
@@ -939,8 +2086,7 @@ var FilterPage_default = defineModuleRoute({
|
|
|
939
2086
|
name: "filter",
|
|
940
2087
|
requiresOcel: true
|
|
941
2088
|
});
|
|
942
|
-
|
|
943
|
-
//#region src/index.ts
|
|
2089
|
+
// src/index.ts
|
|
944
2090
|
var src_default = defineModule({
|
|
945
2091
|
name: "filter",
|
|
946
2092
|
description: "Filter functionality provided by the ocelescope package",
|
|
@@ -949,5 +2095,4 @@ var src_default = defineModule({
|
|
|
949
2095
|
routes: [FilterPage_default],
|
|
950
2096
|
icon: FilterIcon
|
|
951
2097
|
});
|
|
952
|
-
//#endregion
|
|
953
2098
|
export { src_default as default };
|