@finos/legend-data-cube 0.3.117 → 0.3.119
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/lib/components/view/filter/DataCubeFilterEditor.d.ts.map +1 -1
- package/lib/components/view/filter/DataCubeFilterEditor.js +73 -36
- package/lib/components/view/filter/DataCubeFilterEditor.js.map +1 -1
- package/lib/components/view/filter/DataCubeFilterEditorUtils.d.ts +65 -0
- package/lib/components/view/filter/DataCubeFilterEditorUtils.d.ts.map +1 -0
- package/lib/components/view/filter/DataCubeFilterEditorUtils.js +137 -0
- package/lib/components/view/filter/DataCubeFilterEditorUtils.js.map +1 -0
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/core/DataCubeQueryBuilderUtils.d.ts.map +1 -1
- package/lib/stores/core/DataCubeQueryBuilderUtils.js +7 -0
- package/lib/stores/core/DataCubeQueryBuilderUtils.js.map +1 -1
- package/lib/stores/core/DataCubeQueryEngine.d.ts +26 -1
- package/lib/stores/core/DataCubeQueryEngine.d.ts.map +1 -1
- package/lib/stores/core/DataCubeQueryEngine.js +42 -0
- package/lib/stores/core/DataCubeQueryEngine.js.map +1 -1
- package/lib/stores/core/DataCubeSnapshotBuilderUtils.d.ts.map +1 -1
- package/lib/stores/core/DataCubeSnapshotBuilderUtils.js +12 -0
- package/lib/stores/core/DataCubeSnapshotBuilderUtils.js.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation.d.ts +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation.d.ts.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation.js +9 -0
- package/lib/stores/core/filter/DataCubeQueryFilterOperation.js.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__Equal.d.ts.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__Equal.js +7 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__Equal.js.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__GreaterThan.d.ts.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__GreaterThan.js +7 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__GreaterThan.js.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__GreaterThanOrEqual.d.ts.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__GreaterThanOrEqual.js +7 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__GreaterThanOrEqual.js.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__LessThan.d.ts.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__LessThan.js +7 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__LessThan.js.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__LessThanOrEqual.d.ts.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__LessThanOrEqual.js +7 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__LessThanOrEqual.js.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__NotEqual.d.ts.map +1 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__NotEqual.js +7 -1
- package/lib/stores/core/filter/DataCubeQueryFilterOperation__NotEqual.js.map +1 -1
- package/lib/stores/view/filter/DataCubeFilterEditorState.d.ts.map +1 -1
- package/lib/stores/view/filter/DataCubeFilterEditorState.js +3 -1
- package/lib/stores/view/filter/DataCubeFilterEditorState.js.map +1 -1
- package/package.json +3 -3
- package/src/components/view/filter/DataCubeFilterEditor.tsx +150 -88
- package/src/components/view/filter/DataCubeFilterEditorUtils.ts +165 -0
- package/src/stores/core/DataCubeQueryBuilderUtils.ts +7 -0
- package/src/stores/core/DataCubeQueryEngine.ts +65 -0
- package/src/stores/core/DataCubeSnapshotBuilderUtils.ts +14 -0
- package/src/stores/core/filter/DataCubeQueryFilterOperation.ts +15 -1
- package/src/stores/core/filter/DataCubeQueryFilterOperation__Equal.tsx +7 -0
- package/src/stores/core/filter/DataCubeQueryFilterOperation__GreaterThan.tsx +7 -0
- package/src/stores/core/filter/DataCubeQueryFilterOperation__GreaterThanOrEqual.tsx +7 -0
- package/src/stores/core/filter/DataCubeQueryFilterOperation__LessThan.tsx +7 -0
- package/src/stores/core/filter/DataCubeQueryFilterOperation__LessThanOrEqual.tsx +7 -0
- package/src/stores/core/filter/DataCubeQueryFilterOperation__NotEqual.tsx +7 -0
- package/src/stores/view/filter/DataCubeFilterEditorState.tsx +3 -1
- package/tsconfig.json +1 -0
|
@@ -18,9 +18,6 @@ import {
|
|
|
18
18
|
BasePopover,
|
|
19
19
|
cn,
|
|
20
20
|
DataCubeIcon,
|
|
21
|
-
DateCalendar,
|
|
22
|
-
DatePicker,
|
|
23
|
-
DatePickerField,
|
|
24
21
|
useDropdownMenu,
|
|
25
22
|
useForkRef,
|
|
26
23
|
} from '@finos/legend-art';
|
|
@@ -29,8 +26,18 @@ import {
|
|
|
29
26
|
DataCubeOperationAdvancedValueType,
|
|
30
27
|
DataCubeQueryFilterGroupOperator,
|
|
31
28
|
getDataType,
|
|
29
|
+
isCurrentMomentValue,
|
|
30
|
+
isDateTimeType,
|
|
32
31
|
type DataCubeOperationValue,
|
|
33
32
|
} from '../../../stores/core/DataCubeQueryEngine.js';
|
|
33
|
+
import {
|
|
34
|
+
buildDateValue,
|
|
35
|
+
DATE_VALUE_MODE_DESCRIPTION,
|
|
36
|
+
DATE_VALUE_MODE_LABEL,
|
|
37
|
+
getDateValueFormats,
|
|
38
|
+
getDateValueMode,
|
|
39
|
+
getDateValueModes,
|
|
40
|
+
} from './DataCubeFilterEditorUtils.js';
|
|
34
41
|
import {
|
|
35
42
|
DataCubeFilterEditorConditionGroupTreeNode,
|
|
36
43
|
DataCubeFilterEditorConditionTreeNode,
|
|
@@ -52,11 +59,7 @@ import {
|
|
|
52
59
|
useState,
|
|
53
60
|
useMemo,
|
|
54
61
|
} from 'react';
|
|
55
|
-
import {
|
|
56
|
-
DATE_FORMAT,
|
|
57
|
-
PRECISE_PRIMITIVE_TYPE,
|
|
58
|
-
PRIMITIVE_TYPE,
|
|
59
|
-
} from '@finos/legend-graph';
|
|
62
|
+
import { PRECISE_PRIMITIVE_TYPE, PRIMITIVE_TYPE } from '@finos/legend-graph';
|
|
60
63
|
|
|
61
64
|
import {
|
|
62
65
|
formatDate,
|
|
@@ -228,93 +231,131 @@ const DataCubeEditorFilterConditionNodeNumberValueEditor = observer(
|
|
|
228
231
|
}),
|
|
229
232
|
);
|
|
230
233
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const CustomDateFieldPicker = forwardRef<HTMLInputElement>(
|
|
234
|
-
function CustomDateFieldPicker(p, r) {
|
|
235
|
-
return (
|
|
236
|
-
<DatePickerField
|
|
237
|
-
{...p}
|
|
238
|
-
ref={r}
|
|
239
|
-
className="h-5 w-full flex-shrink-0 border border-neutral-400 px-1 pr-5 text-sm disabled:border-neutral-300 disabled:bg-neutral-50 disabled:text-neutral-300"
|
|
240
|
-
/>
|
|
241
|
-
);
|
|
242
|
-
},
|
|
243
|
-
);
|
|
234
|
+
const DATE_INPUT_CLASSNAME =
|
|
235
|
+
'h-5 w-full flex-shrink-0 border border-neutral-400 px-1 text-sm disabled:border-neutral-300 disabled:bg-neutral-50 disabled:text-neutral-300';
|
|
244
236
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
237
|
+
/**
|
|
238
|
+
* Editor for an absolute date value, picked with the control of the browser,
|
|
239
|
+
* like the query builder does. A value which carries a time of day is edited
|
|
240
|
+
* down to the second, and is written back with the time zone of the browser.
|
|
241
|
+
*/
|
|
242
|
+
const DataCubeEditorFilterConditionNodeDatePicker = observer(
|
|
243
|
+
forwardRef<
|
|
244
|
+
HTMLInputElement,
|
|
245
|
+
{
|
|
246
|
+
value: string;
|
|
247
|
+
updateValue: (value: string) => void;
|
|
248
|
+
valueType: string;
|
|
249
|
+
}
|
|
250
|
+
>(function DataCubeEditorFilterConditionNodeDatePicker(props, ref) {
|
|
251
|
+
const { value, updateValue, valueType } = props;
|
|
252
|
+
const hasTime = isDateTimeType(valueType);
|
|
253
|
+
const { valueFormat, displayFormat } = getDateValueFormats(valueType);
|
|
254
|
+
const moment = parseISO(value);
|
|
249
255
|
|
|
250
256
|
return (
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
onChange={(newValue: Date) => {
|
|
277
|
-
updateValue(formatDate(newValue, DATE_FORMAT));
|
|
278
|
-
setAnchorEl(null);
|
|
279
|
-
}}
|
|
280
|
-
/>
|
|
281
|
-
</BasePopover>
|
|
282
|
-
</>
|
|
257
|
+
<input
|
|
258
|
+
ref={ref}
|
|
259
|
+
className={DATE_INPUT_CLASSNAME}
|
|
260
|
+
// NOTE: despite its name, `datetime-local` registers a moment in the
|
|
261
|
+
// time zone of the browser
|
|
262
|
+
// See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local#setting_timezones
|
|
263
|
+
type={hasTime ? 'datetime-local' : 'date'}
|
|
264
|
+
// NOTE: configure the step so the seconds can be picked
|
|
265
|
+
// See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local#step
|
|
266
|
+
step={hasTime ? '1' : undefined}
|
|
267
|
+
spellCheck={false}
|
|
268
|
+
value={
|
|
269
|
+
!Number.isNaN(moment.getTime())
|
|
270
|
+
? formatDate(moment, displayFormat)
|
|
271
|
+
: ''
|
|
272
|
+
}
|
|
273
|
+
onChange={(event) => {
|
|
274
|
+
const newMoment = parseISO(event.target.value);
|
|
275
|
+
// NOTE: the control reports an empty value while the user is still
|
|
276
|
+
// filling it in
|
|
277
|
+
if (!Number.isNaN(newMoment.getTime())) {
|
|
278
|
+
updateValue(formatDate(newMoment, valueFormat));
|
|
279
|
+
}
|
|
280
|
+
}}
|
|
281
|
+
/>
|
|
283
282
|
);
|
|
284
|
-
},
|
|
283
|
+
}),
|
|
285
284
|
);
|
|
286
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Date value editor for scalar filter conditions. For columns which carry a
|
|
288
|
+
* date, it lets the user pick how the value is specified: an absolute date, an
|
|
289
|
+
* absolute date and time, `today()` or `now()`.
|
|
290
|
+
*/
|
|
287
291
|
const DataCubeEditorFilterConditionNodeDateValueEditor = observer(
|
|
288
292
|
forwardRef<
|
|
289
293
|
HTMLInputElement,
|
|
290
294
|
{
|
|
291
|
-
|
|
292
|
-
updateValue: (value: string) => void;
|
|
295
|
+
node: DataCubeFilterEditorConditionTreeNode;
|
|
293
296
|
}
|
|
294
|
-
>(function
|
|
295
|
-
const {
|
|
297
|
+
>(function DataCubeEditorFilterConditionNodeDateValueEditor(props, ref) {
|
|
298
|
+
const { node } = props;
|
|
299
|
+
const value = node.value;
|
|
300
|
+
const modes = getDateValueModes(node.column.type);
|
|
301
|
+
const mode = getDateValueMode(value);
|
|
302
|
+
const [
|
|
303
|
+
openModeDropdown,
|
|
304
|
+
closeModeDropdown,
|
|
305
|
+
modeDropdownProps,
|
|
306
|
+
modeDropdownPropsOpen,
|
|
307
|
+
] = useDropdownMenu();
|
|
296
308
|
|
|
297
|
-
|
|
298
|
-
<
|
|
309
|
+
const datePicker = (
|
|
310
|
+
<DataCubeEditorFilterConditionNodeDatePicker
|
|
299
311
|
ref={ref}
|
|
300
|
-
value={
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
field: CustomDateFieldPicker,
|
|
304
|
-
openPickerButton: () => (
|
|
305
|
-
<CustomDateFieldPickerOpenCalendarButton
|
|
306
|
-
value={value}
|
|
307
|
-
updateValue={updateValue}
|
|
308
|
-
/>
|
|
309
|
-
),
|
|
310
|
-
}}
|
|
311
|
-
onChange={(newValue: Date | null) => {
|
|
312
|
-
if (newValue) {
|
|
313
|
-
updateValue(formatDate(newValue, DATE_FORMAT));
|
|
314
|
-
}
|
|
315
|
-
}}
|
|
312
|
+
value={value.value as string}
|
|
313
|
+
updateValue={(val) => node.updateValue(val)}
|
|
314
|
+
valueType={value.type}
|
|
316
315
|
/>
|
|
317
316
|
);
|
|
317
|
+
|
|
318
|
+
// For columns which carry no date (e.g. a time-of-day column), only the
|
|
319
|
+
// date picker is shown, none of the modes apply.
|
|
320
|
+
if (!modes.length) {
|
|
321
|
+
return datePicker;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return (
|
|
325
|
+
<div className="flex h-full w-full items-center">
|
|
326
|
+
<FormDropdownMenuTrigger
|
|
327
|
+
className="relative mr-1 w-24 flex-shrink-0"
|
|
328
|
+
onClick={openModeDropdown}
|
|
329
|
+
open={modeDropdownPropsOpen}
|
|
330
|
+
title={DATE_VALUE_MODE_DESCRIPTION[mode]}
|
|
331
|
+
>
|
|
332
|
+
{DATE_VALUE_MODE_LABEL[mode]}
|
|
333
|
+
</FormDropdownMenuTrigger>
|
|
334
|
+
<FormDropdownMenu className="w-24" {...modeDropdownProps}>
|
|
335
|
+
{modes.map((_mode) => (
|
|
336
|
+
<FormDropdownMenuItem
|
|
337
|
+
key={_mode}
|
|
338
|
+
onClick={() => {
|
|
339
|
+
if (_mode !== mode) {
|
|
340
|
+
node.setValue(buildDateValue(_mode, value));
|
|
341
|
+
}
|
|
342
|
+
closeModeDropdown();
|
|
343
|
+
}}
|
|
344
|
+
autoFocus={_mode === mode}
|
|
345
|
+
title={DATE_VALUE_MODE_DESCRIPTION[_mode]}
|
|
346
|
+
>
|
|
347
|
+
{DATE_VALUE_MODE_LABEL[_mode]}
|
|
348
|
+
</FormDropdownMenuItem>
|
|
349
|
+
))}
|
|
350
|
+
</FormDropdownMenu>
|
|
351
|
+
{/* NOTE: the date control sizes itself against its container, so it
|
|
352
|
+
needs one which is only as wide as what the mode selector leaves, else
|
|
353
|
+
it overflows the value editor. */}
|
|
354
|
+
{!isCurrentMomentValue(value) && (
|
|
355
|
+
<div className="min-w-0 flex-1">{datePicker}</div>
|
|
356
|
+
)}
|
|
357
|
+
</div>
|
|
358
|
+
);
|
|
318
359
|
}),
|
|
319
360
|
);
|
|
320
361
|
|
|
@@ -454,12 +495,13 @@ const DataCubeEditorFilterConditionNodeListValueEditor = observer(
|
|
|
454
495
|
case PRECISE_PRIMITIVE_TYPE.STRICTTIME:
|
|
455
496
|
case PRECISE_PRIMITIVE_TYPE.TIMESTAMP: {
|
|
456
497
|
// try parse ISO, else use formatted now
|
|
498
|
+
const { valueFormat } = getDateValueFormats(firstType);
|
|
457
499
|
let dateVal = trimmed;
|
|
458
500
|
try {
|
|
459
501
|
const d = parseISO(trimmed);
|
|
460
|
-
dateVal = formatDate(d,
|
|
502
|
+
dateVal = formatDate(d, valueFormat);
|
|
461
503
|
} catch {
|
|
462
|
-
dateVal = formatDate(new Date(),
|
|
504
|
+
dateVal = formatDate(new Date(), valueFormat);
|
|
463
505
|
}
|
|
464
506
|
newItem = { type: firstType, value: dateVal };
|
|
465
507
|
break;
|
|
@@ -621,10 +663,14 @@ const DataCubeEditorFilterConditionNodeListValueEditor = observer(
|
|
|
621
663
|
case PRECISE_PRIMITIVE_TYPE.STRICTTIME:
|
|
622
664
|
case PRECISE_PRIMITIVE_TYPE.TIMESTAMP:
|
|
623
665
|
return (
|
|
624
|
-
<
|
|
666
|
+
<DataCubeEditorFilterConditionNodeDatePicker
|
|
667
|
+
valueType={it.type}
|
|
625
668
|
value={String(
|
|
626
669
|
it.value ??
|
|
627
|
-
formatDate(
|
|
670
|
+
formatDate(
|
|
671
|
+
new Date(),
|
|
672
|
+
getDateValueFormats(it.type).valueFormat,
|
|
673
|
+
),
|
|
628
674
|
)}
|
|
629
675
|
updateValue={(v: string) => {
|
|
630
676
|
const next = localItems.slice();
|
|
@@ -719,12 +765,13 @@ const DataCubeEditorFilterConditionNodeValueEditor = observer(
|
|
|
719
765
|
forwardRef<
|
|
720
766
|
HTMLElement,
|
|
721
767
|
{
|
|
768
|
+
node: DataCubeFilterEditorConditionTreeNode;
|
|
722
769
|
value: DataCubeOperationValue;
|
|
723
770
|
updateValue: (value: unknown) => void;
|
|
724
771
|
view: DataCubeViewState;
|
|
725
772
|
}
|
|
726
773
|
>(function DataCubeEditorFilterConditionNodeValueEditor(props, ref) {
|
|
727
|
-
const { value, updateValue, view } = props;
|
|
774
|
+
const { node, value, updateValue, view } = props;
|
|
728
775
|
// WIP: support column
|
|
729
776
|
switch (value.type) {
|
|
730
777
|
case PRIMITIVE_TYPE.STRING:
|
|
@@ -774,11 +821,12 @@ const DataCubeEditorFilterConditionNodeValueEditor = observer(
|
|
|
774
821
|
case PRECISE_PRIMITIVE_TYPE.DATETIME:
|
|
775
822
|
case PRECISE_PRIMITIVE_TYPE.STRICTTIME:
|
|
776
823
|
case PRECISE_PRIMITIVE_TYPE.TIMESTAMP:
|
|
824
|
+
case DataCubeOperationAdvancedValueType.TODAY:
|
|
825
|
+
case DataCubeOperationAdvancedValueType.NOW:
|
|
777
826
|
return (
|
|
778
827
|
<DataCubeEditorFilterConditionNodeDateValueEditor
|
|
779
828
|
ref={ref as React.RefObject<HTMLInputElement>}
|
|
780
|
-
|
|
781
|
-
updateValue={(val) => updateValue(val)}
|
|
829
|
+
node={node}
|
|
782
830
|
/>
|
|
783
831
|
);
|
|
784
832
|
case DataCubeOperationAdvancedValueType.COLUMN:
|
|
@@ -909,6 +957,12 @@ const DataCubeEditorFilterConditionNodeDisplay = observer(
|
|
|
909
957
|
() => valueEditorRef.current?.focus(),
|
|
910
958
|
[],
|
|
911
959
|
);
|
|
960
|
+
// NOTE: the value editor for columns which carry a date is wider to
|
|
961
|
+
// accommodate the selector for switching between a specific date and the
|
|
962
|
+
// `today()`/`now()` function.
|
|
963
|
+
const hasDateValueModeSelector = Boolean(
|
|
964
|
+
getDateValueModes(node.column.type).length,
|
|
965
|
+
);
|
|
912
966
|
|
|
913
967
|
return (
|
|
914
968
|
<div className="group flex h-6 items-center">
|
|
@@ -1034,9 +1088,17 @@ const DataCubeEditorFilterConditionNodeDisplay = observer(
|
|
|
1034
1088
|
</FormDropdownMenuItem>
|
|
1035
1089
|
))}
|
|
1036
1090
|
</FormDropdownMenu>
|
|
1037
|
-
<div
|
|
1091
|
+
<div
|
|
1092
|
+
className={cn('relative flex-shrink-0', {
|
|
1093
|
+
// NOTE: wide enough for a date-time value, which the user can
|
|
1094
|
+
// switch to whenever the mode selector is shown
|
|
1095
|
+
'w-80': hasDateValueModeSelector,
|
|
1096
|
+
'w-32': !hasDateValueModeSelector,
|
|
1097
|
+
})}
|
|
1098
|
+
>
|
|
1038
1099
|
<DataCubeEditorFilterConditionNodeValueEditor
|
|
1039
1100
|
ref={valueEditorRef}
|
|
1101
|
+
node={node}
|
|
1040
1102
|
value={node.value}
|
|
1041
1103
|
updateValue={(val) => node.updateValue(val)}
|
|
1042
1104
|
view={view}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-present, Goldman Sachs
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
DATE_FORMAT,
|
|
19
|
+
DATE_TIME_FORMAT,
|
|
20
|
+
PRIMITIVE_TYPE,
|
|
21
|
+
} from '@finos/legend-graph';
|
|
22
|
+
import { formatDate, parseISO } from '@finos/legend-shared';
|
|
23
|
+
import {
|
|
24
|
+
DataCubeOperationAdvancedValueType,
|
|
25
|
+
getCurrentMomentValueType,
|
|
26
|
+
isDateTimeType,
|
|
27
|
+
type DataCubeOperationValue,
|
|
28
|
+
} from '../../../stores/core/DataCubeQueryEngine.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The ways a date filter value can be specified: as an absolute moment - a
|
|
32
|
+
* date, or a date and a time of day - or as a function resolved when the query
|
|
33
|
+
* runs.
|
|
34
|
+
*/
|
|
35
|
+
export enum DataCubeDateValueMode {
|
|
36
|
+
ABSOLUTE_DATE = 'ABSOLUTE_DATE',
|
|
37
|
+
ABSOLUTE_DATE_TIME = 'ABSOLUTE_DATE_TIME',
|
|
38
|
+
TODAY = 'TODAY',
|
|
39
|
+
NOW = 'NOW',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const DATE_VALUE_MODE_LABEL: Record<DataCubeDateValueMode, string> = {
|
|
43
|
+
[DataCubeDateValueMode.ABSOLUTE_DATE]: 'Date',
|
|
44
|
+
[DataCubeDateValueMode.ABSOLUTE_DATE_TIME]: 'Date Time',
|
|
45
|
+
[DataCubeDateValueMode.TODAY]: 'Today',
|
|
46
|
+
[DataCubeDateValueMode.NOW]: 'Now',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// NOTE: the wording mirrors the date picker of the query builder, where a
|
|
50
|
+
// value specified by the user is an "absolute" one, as opposed to one which
|
|
51
|
+
// resolves relative to the moment the query runs.
|
|
52
|
+
export const DATE_VALUE_MODE_DESCRIPTION: Record<
|
|
53
|
+
DataCubeDateValueMode,
|
|
54
|
+
string
|
|
55
|
+
> = {
|
|
56
|
+
[DataCubeDateValueMode.ABSOLUTE_DATE]: 'Absolute date',
|
|
57
|
+
[DataCubeDateValueMode.ABSOLUTE_DATE_TIME]: 'Absolute date and time',
|
|
58
|
+
[DataCubeDateValueMode.NOW]: 'now()',
|
|
59
|
+
[DataCubeDateValueMode.TODAY]: 'today()',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// The format a `datetime-local` input takes: down to the second, and without
|
|
63
|
+
// the time zone, which is not part of what the user picks - it is taken from
|
|
64
|
+
// the browser when the value is written back out.
|
|
65
|
+
const DATE_TIME_INPUT_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns the format the specified date value is stored in, and the format the
|
|
69
|
+
* editor displays it in. These differ for a date-time, whose value - see
|
|
70
|
+
* `_defaultPrimitiveTypeValue()` - carries the time zone, which is more than
|
|
71
|
+
* the editor shows.
|
|
72
|
+
*/
|
|
73
|
+
export function getDateValueFormats(valueType: string): {
|
|
74
|
+
valueFormat: string;
|
|
75
|
+
displayFormat: string;
|
|
76
|
+
} {
|
|
77
|
+
return isDateTimeType(valueType)
|
|
78
|
+
? { valueFormat: DATE_TIME_FORMAT, displayFormat: DATE_TIME_INPUT_FORMAT }
|
|
79
|
+
: { valueFormat: DATE_FORMAT, displayFormat: DATE_FORMAT };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getDateValueMode(
|
|
83
|
+
value: DataCubeOperationValue,
|
|
84
|
+
): DataCubeDateValueMode {
|
|
85
|
+
switch (value.type) {
|
|
86
|
+
case DataCubeOperationAdvancedValueType.TODAY:
|
|
87
|
+
return DataCubeDateValueMode.TODAY;
|
|
88
|
+
case DataCubeOperationAdvancedValueType.NOW:
|
|
89
|
+
return DataCubeDateValueMode.NOW;
|
|
90
|
+
default:
|
|
91
|
+
return isDateTimeType(value.type)
|
|
92
|
+
? DataCubeDateValueMode.ABSOLUTE_DATE_TIME
|
|
93
|
+
: DataCubeDateValueMode.ABSOLUTE_DATE;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Returns the ways a condition on the specified column can specify its value;
|
|
99
|
+
* empty when the column carries no date (e.g. a time-of-day column), where
|
|
100
|
+
* none of these apply.
|
|
101
|
+
*
|
|
102
|
+
* NOTE: every column which carries a date offers all of them, like the query
|
|
103
|
+
* builder does: `Date` is the super type of both `StrictDate` and `DateTime`,
|
|
104
|
+
* so a column typed with it can hold a time of day, and comparing a date
|
|
105
|
+
* against a moment which carries one is meaningful either way around.
|
|
106
|
+
*/
|
|
107
|
+
export function getDateValueModes(columnType: string): DataCubeDateValueMode[] {
|
|
108
|
+
return getCurrentMomentValueType(columnType)
|
|
109
|
+
? [
|
|
110
|
+
DataCubeDateValueMode.ABSOLUTE_DATE,
|
|
111
|
+
DataCubeDateValueMode.ABSOLUTE_DATE_TIME,
|
|
112
|
+
DataCubeDateValueMode.TODAY,
|
|
113
|
+
DataCubeDateValueMode.NOW,
|
|
114
|
+
]
|
|
115
|
+
: [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Returns the moment a date value specifies, defaulting to the current one
|
|
120
|
+
* where it specifies none, e.g. when it is `today()`.
|
|
121
|
+
*/
|
|
122
|
+
function _dateValueMoment(value: DataCubeOperationValue): Date {
|
|
123
|
+
if (typeof value.value === 'string') {
|
|
124
|
+
const moment = parseISO(value.value);
|
|
125
|
+
if (!Number.isNaN(moment.getTime())) {
|
|
126
|
+
return moment;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return new Date();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Returns the value a condition takes when switched to the specified mode,
|
|
134
|
+
* keeping the moment it currently specifies.
|
|
135
|
+
*
|
|
136
|
+
* NOTE: an absolute value is built with the primitive type matching what it
|
|
137
|
+
* specifies rather than with the type of the column. The type of a value names
|
|
138
|
+
* the literal it is written as, which the type of the column does not: `Date`
|
|
139
|
+
* says nothing about whether a time of day is meant, and a timestamp column is
|
|
140
|
+
* typed with a relational data type, which no literal can be spelled with.
|
|
141
|
+
* This is also the type the value comes back as when the query it is written
|
|
142
|
+
* to is read again, so a value keeps its shape across a round-trip.
|
|
143
|
+
*/
|
|
144
|
+
export function buildDateValue(
|
|
145
|
+
mode: DataCubeDateValueMode,
|
|
146
|
+
value: DataCubeOperationValue,
|
|
147
|
+
): DataCubeOperationValue {
|
|
148
|
+
switch (mode) {
|
|
149
|
+
case DataCubeDateValueMode.TODAY:
|
|
150
|
+
return { type: DataCubeOperationAdvancedValueType.TODAY };
|
|
151
|
+
case DataCubeDateValueMode.NOW:
|
|
152
|
+
return { type: DataCubeOperationAdvancedValueType.NOW };
|
|
153
|
+
case DataCubeDateValueMode.ABSOLUTE_DATE_TIME:
|
|
154
|
+
return {
|
|
155
|
+
type: PRIMITIVE_TYPE.DATETIME,
|
|
156
|
+
value: formatDate(_dateValueMoment(value), DATE_TIME_FORMAT),
|
|
157
|
+
};
|
|
158
|
+
case DataCubeDateValueMode.ABSOLUTE_DATE:
|
|
159
|
+
default:
|
|
160
|
+
return {
|
|
161
|
+
type: PRIMITIVE_TYPE.STRICTDATE,
|
|
162
|
+
value: formatDate(_dateValueMoment(value), DATE_FORMAT),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -259,8 +259,11 @@ export function _primitiveValue(
|
|
|
259
259
|
case PRECISE_PRIMITIVE_TYPE.STRICTDATE:
|
|
260
260
|
return _val(new V1_CStrictDate(), guaranteeIsString(value));
|
|
261
261
|
case PRIMITIVE_TYPE.DATETIME:
|
|
262
|
+
case PRECISE_PRIMITIVE_TYPE.DATETIME:
|
|
263
|
+
case PRECISE_PRIMITIVE_TYPE.TIMESTAMP:
|
|
262
264
|
return _val(new V1_CDateTime(), guaranteeIsString(value));
|
|
263
265
|
case PRIMITIVE_TYPE.STRICTTIME:
|
|
266
|
+
case PRECISE_PRIMITIVE_TYPE.STRICTTIME:
|
|
264
267
|
return _val(new V1_CStrictTime(), guaranteeIsString(value));
|
|
265
268
|
case PRIMITIVE_TYPE.LATESTDATE:
|
|
266
269
|
return new V1_CLatestDate();
|
|
@@ -372,6 +375,10 @@ export function _value(value: DataCubeOperationValue): V1_ValueSpecification {
|
|
|
372
375
|
}
|
|
373
376
|
return _primitiveValue(value.type, value.value);
|
|
374
377
|
}
|
|
378
|
+
case DataCubeOperationAdvancedValueType.TODAY:
|
|
379
|
+
return _function(_functionName(DataCubeFunction.TODAY), []);
|
|
380
|
+
case DataCubeOperationAdvancedValueType.NOW:
|
|
381
|
+
return _function(_functionName(DataCubeFunction.NOW), []);
|
|
375
382
|
case DataCubeOperationAdvancedValueType.COLUMN:
|
|
376
383
|
return _property(guaranteeIsString(value.value));
|
|
377
384
|
case DataCubeOperationAdvancedValueType.LIST: {
|
|
@@ -47,6 +47,10 @@ export enum DataCubeFunction {
|
|
|
47
47
|
FROM = 'meta::pure::mapping::from',
|
|
48
48
|
TO_ONE = 'meta::pure::functions::multiplicity::toOne',
|
|
49
49
|
|
|
50
|
+
// date
|
|
51
|
+
TODAY = 'meta::pure::functions::date::today',
|
|
52
|
+
NOW = 'meta::pure::functions::date::now',
|
|
53
|
+
|
|
50
54
|
// sort
|
|
51
55
|
ASCENDING = 'meta::pure::functions::relation::ascending',
|
|
52
56
|
DESCENDING = 'meta::pure::functions::relation::descending',
|
|
@@ -157,6 +161,8 @@ export enum DataCubeOperationAdvancedValueType {
|
|
|
157
161
|
COLUMN = 'COLUMN',
|
|
158
162
|
LIST = 'LIST',
|
|
159
163
|
VOID = 'VOID',
|
|
164
|
+
TODAY = 'TODAY',
|
|
165
|
+
NOW = 'NOW',
|
|
160
166
|
// PARAMETER
|
|
161
167
|
}
|
|
162
168
|
|
|
@@ -492,6 +498,65 @@ export function isPrimitiveType(type: string) {
|
|
|
492
498
|
] as string[]
|
|
493
499
|
).includes(type);
|
|
494
500
|
}
|
|
501
|
+
/**
|
|
502
|
+
* Current-moment values are filter values which resolve at execution time
|
|
503
|
+
* rather than being specified by the user: `today()` and `now()`.
|
|
504
|
+
*/
|
|
505
|
+
export type DataCubeCurrentMomentValueType =
|
|
506
|
+
| DataCubeOperationAdvancedValueType.TODAY
|
|
507
|
+
| DataCubeOperationAdvancedValueType.NOW;
|
|
508
|
+
|
|
509
|
+
export function isCurrentMomentValue(
|
|
510
|
+
value: DataCubeOperationValue,
|
|
511
|
+
): value is DataCubeOperationValue & { type: DataCubeCurrentMomentValueType } {
|
|
512
|
+
return (
|
|
513
|
+
value.type === DataCubeOperationAdvancedValueType.TODAY ||
|
|
514
|
+
value.type === DataCubeOperationAdvancedValueType.NOW
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function isDateType(type: string) {
|
|
519
|
+
return (
|
|
520
|
+
[
|
|
521
|
+
PRIMITIVE_TYPE.DATE,
|
|
522
|
+
PRIMITIVE_TYPE.STRICTDATE,
|
|
523
|
+
PRECISE_PRIMITIVE_TYPE.STRICTDATE,
|
|
524
|
+
] as string[]
|
|
525
|
+
).includes(type);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Unlike `isDateType()`, these types carry a time of day as well as a date,
|
|
530
|
+
* so their values must be specified (and displayed) down to the second.
|
|
531
|
+
*/
|
|
532
|
+
export function isDateTimeType(type: string) {
|
|
533
|
+
return (
|
|
534
|
+
[
|
|
535
|
+
PRIMITIVE_TYPE.DATETIME,
|
|
536
|
+
PRECISE_PRIMITIVE_TYPE.DATETIME,
|
|
537
|
+
PRECISE_PRIMITIVE_TYPE.TIMESTAMP,
|
|
538
|
+
] as string[]
|
|
539
|
+
).includes(type);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Returns the current-moment value type applicable to a column: `today()`
|
|
544
|
+
* (StrictDate) for date columns, `now()` (DateTime) for date-time columns.
|
|
545
|
+
* Returns `undefined` for columns which carry no date (e.g. a time-of-day
|
|
546
|
+
* column), since a current-moment date value is meaningless there.
|
|
547
|
+
*/
|
|
548
|
+
export function getCurrentMomentValueType(
|
|
549
|
+
columnType: string,
|
|
550
|
+
): DataCubeCurrentMomentValueType | undefined {
|
|
551
|
+
if (isDateType(columnType)) {
|
|
552
|
+
return DataCubeOperationAdvancedValueType.TODAY;
|
|
553
|
+
}
|
|
554
|
+
if (isDateTimeType(columnType)) {
|
|
555
|
+
return DataCubeOperationAdvancedValueType.NOW;
|
|
556
|
+
}
|
|
557
|
+
return undefined;
|
|
558
|
+
}
|
|
559
|
+
|
|
495
560
|
export function isPivotResultColumnName(columnName: string) {
|
|
496
561
|
return columnName.includes(PIVOT_COLUMN_NAME_VALUE_SEPARATOR);
|
|
497
562
|
}
|
|
@@ -250,6 +250,20 @@ export function _operationValue(
|
|
|
250
250
|
_operationValue(val, columnGetter, columnChecker),
|
|
251
251
|
),
|
|
252
252
|
};
|
|
253
|
+
} else if (
|
|
254
|
+
value instanceof V1_AppliedFunction &&
|
|
255
|
+
matchFunctionName(value.function, DataCubeFunction.TODAY)
|
|
256
|
+
) {
|
|
257
|
+
return {
|
|
258
|
+
type: DataCubeOperationAdvancedValueType.TODAY,
|
|
259
|
+
};
|
|
260
|
+
} else if (
|
|
261
|
+
value instanceof V1_AppliedFunction &&
|
|
262
|
+
matchFunctionName(value.function, DataCubeFunction.NOW)
|
|
263
|
+
) {
|
|
264
|
+
return {
|
|
265
|
+
type: DataCubeOperationAdvancedValueType.NOW,
|
|
266
|
+
};
|
|
253
267
|
} else if (value === undefined) {
|
|
254
268
|
return {
|
|
255
269
|
type: DataCubeOperationAdvancedValueType.VOID,
|
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import
|
|
17
|
+
import {
|
|
18
|
+
getCurrentMomentValueType,
|
|
19
|
+
isCurrentMomentValue,
|
|
20
|
+
type DataCubeOperationValue,
|
|
21
|
+
} from '../DataCubeQueryEngine.js';
|
|
18
22
|
import type { DataCubeSnapshotFilterCondition } from '../DataCubeSnapshot.js';
|
|
19
23
|
import type { DataCubeColumn } from '../model/DataCubeColumn.js';
|
|
20
24
|
import { type V1_AppliedFunction } from '@finos/legend-graph';
|
|
@@ -52,6 +56,16 @@ export abstract class DataCubeQueryFilterOperation {
|
|
|
52
56
|
) {
|
|
53
57
|
return undefined;
|
|
54
58
|
}
|
|
59
|
+
// NOTE: `isCompatibleWithValue()` cannot tell whether a current-moment
|
|
60
|
+
// value (`today()`/`now()`) makes sense for the column it is compared
|
|
61
|
+
// against, since it does not have access to the column, so check here that
|
|
62
|
+
// the column actually carries a date.
|
|
63
|
+
if (
|
|
64
|
+
isCurrentMomentValue(value) &&
|
|
65
|
+
!getCurrentMomentValueType(column.type)
|
|
66
|
+
) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
55
69
|
return {
|
|
56
70
|
...column,
|
|
57
71
|
operator: this.operator,
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
isPrimitiveType,
|
|
24
24
|
ofDataType,
|
|
25
25
|
_defaultPrimitiveTypeValue,
|
|
26
|
+
isCurrentMomentValue,
|
|
26
27
|
type DataCubeOperationValue,
|
|
27
28
|
} from '../DataCubeQueryEngine.js';
|
|
28
29
|
import {
|
|
@@ -62,6 +63,12 @@ export class DataCubeQueryFilterOperation__Equal extends DataCubeQueryFilterOper
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
isCompatibleWithValue(value: DataCubeOperationValue) {
|
|
66
|
+
// current-moment values (`today()`/`now()`) are date values resolved at
|
|
67
|
+
// execution time; the column they apply to is checked in
|
|
68
|
+
// `_finalizeConditionSnapshot()`
|
|
69
|
+
if (isCurrentMomentValue(value)) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
65
72
|
return (
|
|
66
73
|
value.value !== undefined &&
|
|
67
74
|
isPrimitiveType(value.type) &&
|