Skip to content

Commit

Permalink
JL: support epoch in date/datetime (#1171)
Browse files Browse the repository at this point in the history
  • Loading branch information
ukrbublik authored Jan 13, 2025
1 parent 8b0ecce commit 045a3d7
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog
- 6.6.7
- Fix import of ambiguous operators (like, select_any_in) (PR #1168) (issue #1159)
- Allow import of epoch for date/datetime widgets from JsonLogic (PR #1171) (issue #1154)
- 6.6.6
- Fix issue with process global (PR #1166) (issue #1165)
- 6.6.5
Expand Down
10 changes: 10 additions & 0 deletions packages/core/modules/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1023,10 +1023,20 @@ const widgets = {
jsonLogic: function (val, fieldDef, wgtDef) {
return this.utils.moment(val, wgtDef.valueFormat).toDate();
},
// Example of importing and exporting to epoch timestamp (in ms) for JsonLogic:
// jsonLogicImport: function(timestamp, wgtDef) {
// const momentVal = this.utils.moment(timestamp, "x");
// return momentVal.isValid() ? momentVal.toDate() : undefined;
// },
// jsonLogic: function (val, fieldDef, wgtDef) {
// return this.utils.moment(val, wgtDef.valueFormat).format("x");
// },
toJS: function (val, fieldSettings) {
const dateVal = this.utils.moment(val, fieldSettings.valueFormat);
return dateVal.isValid() ? dateVal.toDate() : undefined;
},
// todo: $toDate (works onliny in $expr)
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDate/
mongoFormatValue: function (val, fieldDef, wgtDef) {
const dateVal = this.utils.moment(val, wgtDef.valueFormat);
return dateVal.isValid() ? dateVal.toDate() : undefined;
Expand Down
72 changes: 41 additions & 31 deletions packages/core/modules/import/jsonLogic.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,33 +300,52 @@ const convertValRhs = (val, fieldConfig, widget, config, meta) => {
return undefined;
}

// number of seconds -> time string
if (fieldType === "time" && typeof val === "number") {
const [h, m, s] = [Math.floor(val / 60 / 60) % 24, Math.floor(val / 60) % 60, val % 60];
const valueFormat = widgetConfig.valueFormat;
if (valueFormat) {
const dateVal = new Date(val);
dateVal.setMilliseconds(0);
dateVal.setHours(h);
dateVal.setMinutes(m);
dateVal.setSeconds(s);
val = moment(dateVal).format(valueFormat);
} else {
val = `${h}:${m}:${s}`;
}
}

// "2020-01-08T22:00:00.000Z" -> Date object
if (["date", "datetime"].includes(fieldType) && val && !(val instanceof Date)) {
if (widgetConfig?.jsonLogicImport) {
try {
const dateVal = new Date(val);
if (dateVal instanceof Date && dateVal.toISOString() === val) {
val = dateVal;
}
val = widgetConfig.jsonLogicImport.call(
config.ctx, val,
{...widgetConfig, ...(fieldConfig?.fieldSettings ?? {})}
);
} catch(e) {
meta.errors.push(`Can't convert value ${val} as Date`);
meta.errors.push(`Can't import value ${val} using import func of widget ${widget}: ${e?.message ?? e}`);
val = undefined;
}
} else {
// number of seconds -> time string
if (fieldType === "time" && typeof val === "number") {
const [h, m, s] = [Math.floor(val / 60 / 60) % 24, Math.floor(val / 60) % 60, val % 60];
const valueFormat = widgetConfig.valueFormat;
if (valueFormat) {
const dateVal = new Date(val);
dateVal.setMilliseconds(0);
dateVal.setHours(h);
dateVal.setMinutes(m);
dateVal.setSeconds(s);
val = moment(dateVal).format(valueFormat);
} else {
val = `${h}:${m}:${s}`;
}
}

// "2020-01-08T22:00:00.000Z" -> Date object
if (["date", "datetime"].includes(fieldType) && val && !(val instanceof Date)) {
try {
const isEpoch = typeof val === "number" || typeof val === "string" && !isNaN(val);
// Note: can import only from ms timestamp, not seconds timestamp
const epoch = isEpoch && typeof val === "string" ? parseInt(val) : val;
const dateVal = new Date(isEpoch ? epoch : val);
if (dateVal instanceof Date) {
val = dateVal;
}
if (isNaN(dateVal)) {
throw new Error("Invalid date");
}
} catch(e) {
meta.errors.push(`Can't convert value ${val} as Date`);
val = undefined;
}
}
}

// Date object -> formatted string
Expand All @@ -343,15 +362,6 @@ const convertValRhs = (val, fieldConfig, widget, config, meta) => {
asyncListValues = vals;
}

if (widgetConfig?.jsonLogicImport) {
try {
val = widgetConfig.jsonLogicImport.call(config.ctx, val);
} catch(e) {
meta.errors.push(`Can't import value ${val} using import func of widget ${widget}: ${e?.message ?? e}`);
val = undefined;
}
}

return {
valueSrc: "value",
value: val,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/modules/utils/configSerialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const compileMetaWidget = {
mongoFormatValue: { type: "f", args: ["val", "fieldDef", "wgtDef", "op", "opDef"] },
elasticSearchFormatValue: { type: "f", args: ["queryType", "val", "op", "field", "config"] },
jsonLogic: { type: "f", args: ["val", "fieldDef", "wgtDef", "op", "opDef"] },
jsonLogicImport: { type: "f", args: ["val"] },
jsonLogicImport: { type: "f", args: ["val", "wgtDef"] },
validateValue: { type: "f", args: ["val", "fieldSettings", "op", "opDef", "rightFieldDef"] }, // obsolete
toJS: { type: "f", args: ["val"] },
};
Expand Down
40 changes: 40 additions & 0 deletions packages/tests/specs/Basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,46 @@ describe("basic query", () => {
]);
});

describe("should import @epoch timestamp ms from JL", () => {
export_checks([configs.with_date_and_time], inits.with_date_epoch_ms, "JsonLogic", {
logic: {
"and": [
{
"==": [
{
"var": "datetime"
},
"2025-01-13T15:39:28.000Z"
]
}
]
}
});
});

describe("should import @epoch timestamp sec from JL if configured", () => {
export_checks([configs.with_date_and_time, configs.with_datetime_import_epoch_sec_jl], inits.with_date_epoch, "JsonLogic", {
logic: {
"and": [
{
"==": [
{
"var": "datetime"
},
"2025-01-13T15:39:28.000Z"
]
}
]
}
});
});

describe("should export @epoch timestamp ms to JL if configured", () => {
export_checks([configs.with_date_and_time, configs.with_datetime_export_epoch_ms_jl], inits.with_date_epoch_ms, "JsonLogic", {
logic: inits.with_date_epoch_ms
});
});

});

describe("export", () => {
Expand Down
26 changes: 26 additions & 0 deletions packages/tests/support/configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,32 @@ export const with_date_and_time = (BasicConfig) => ({
},
});

export const with_datetime_import_epoch_sec_jl = (BasicConfig) => ({
...BasicConfig,
widgets: {
...BasicConfig.widgets,
datetime: {
...BasicConfig.widgets.datetime,
jsonLogicImport: function(timestamp, wgtDef) {
const momentVal = this.utils.moment(timestamp, "X");
return momentVal.isValid() ? momentVal.toDate() : undefined;
},
}
}
});

export const with_datetime_export_epoch_ms_jl = (BasicConfig) => ({
...BasicConfig,
widgets: {
...BasicConfig.widgets,
datetime: {
...BasicConfig.widgets.datetime,
jsonLogic: function (val, fieldDef, wgtDef) {
return this.utils.moment(val, wgtDef.valueFormat).format("x");
},
}
}
});

export const with_theme_material = (BasicConfig) => ({
...BasicConfig,
Expand Down
16 changes: 16 additions & 0 deletions packages/tests/support/inits.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,22 @@ export const with_date_and_time = {
}]
};

export const with_date_epoch = {
"and": [
{
"==": [ { "var": "datetime" }, "1736782768" ]
}
]
};

export const with_date_epoch_ms = {
"and": [
{
"==": [ { "var": "datetime" }, "1736782768000" ]
}
]
};

export const with_select_and_multiselect = {
"and": [{
"==": [ { "var": "color" }, "yellow" ]
Expand Down

0 comments on commit 045a3d7

Please sign in to comment.