diff --git a/data-filter/lib/filter/filters/date-only.filter.spec.ts b/data-filter/lib/filter/filters/date-only.filter.spec.ts new file mode 100644 index 0000000..7dd38ee --- /dev/null +++ b/data-filter/lib/filter/filters/date-only.filter.spec.ts @@ -0,0 +1,473 @@ +import { DefaultTranslateAdapter } from "../../adapters/default-translate.adapter"; +import { FilterUtils } from "../filter.utils"; +import { FilterOperatorTypes } from "../operators"; +import { Op, WhereOptions } from "sequelize"; +import { FilterBaseConfigurationModel } from "../models/filter-configuration.model"; +import { FilterType } from "../type"; +import { DateOnlyFilter } from "./date-only.filter"; + +describe("DateOnlyFilter", () => { + describe("getConfig", () => { + it("should return a valid config", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + filter.translateService = new DefaultTranslateAdapter(); + const config = await filter.getConfig("", null); + expect(config).toBeDefined(); + expect(config).toStrictEqual({ + type: FilterType.Date, + operators: [ + { + id: "equal", + name: FilterUtils.getOperatorTranslationKey("equal") + }, + { + id: "not_equal", + name: FilterUtils.getOperatorTranslationKey("not_equal") + }, + { + id: "greater", + name: FilterUtils.getOperatorTranslationKey("greater") + }, + { + id: "greater_or_equal", + name: FilterUtils.getOperatorTranslationKey("greater_or_equal") + }, + { + id: "less", + name: FilterUtils.getOperatorTranslationKey("less") + }, + { + id: "less_or_equal", + name: FilterUtils.getOperatorTranslationKey("less_or_equal") + }, + { + id: "between", + name: FilterUtils.getOperatorTranslationKey("between") + }, + { + id: "not_between", + name: FilterUtils.getOperatorTranslationKey("not_between") + } + ] + }); + }); + + it("with group should return a valid config", async () => { + const filter = new DateOnlyFilter({ + attribute: "test", + group: "test" + }); + filter.translateService = new DefaultTranslateAdapter(); + const config = await filter.getConfig("", null); + expect(config).toBeDefined(); + expect(config).toStrictEqual({ + type: FilterType.Date, + operators: [ + { + id: "equal", + name: FilterUtils.getOperatorTranslationKey("equal") + }, + { + id: "not_equal", + name: FilterUtils.getOperatorTranslationKey("not_equal") + }, + { + id: "greater", + name: FilterUtils.getOperatorTranslationKey("greater") + }, + { + id: "greater_or_equal", + name: FilterUtils.getOperatorTranslationKey("greater_or_equal") + }, + { + id: "less", + name: FilterUtils.getOperatorTranslationKey("less") + }, + { + id: "less_or_equal", + name: FilterUtils.getOperatorTranslationKey("less_or_equal") + }, + { + id: "between", + name: FilterUtils.getOperatorTranslationKey("between") + }, + { + id: "not_between", + name: FilterUtils.getOperatorTranslationKey("not_between") + } + ], + group: { + name: FilterUtils.getGroupTranslationKey("test"), + key: "test" + } + }); + }); + + it("with custom operator should return a valid config", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }).addOperators({ + name: "none" + }); + filter.translateService = new DefaultTranslateAdapter(); + const config = await filter.getConfig("test", null); + expect(config).toBeDefined(); + expect(config).toStrictEqual({ + type: FilterType.Date, + operators: [ + { + id: "equal", + name: FilterUtils.getOperatorTranslationKey("equal") + }, + { + id: "not_equal", + name: FilterUtils.getOperatorTranslationKey("not_equal") + }, + { + id: "greater", + name: FilterUtils.getOperatorTranslationKey("greater") + }, + { + id: "greater_or_equal", + name: FilterUtils.getOperatorTranslationKey("greater_or_equal") + }, + { + id: "less", + name: FilterUtils.getOperatorTranslationKey("less") + }, + { + id: "less_or_equal", + name: FilterUtils.getOperatorTranslationKey("less_or_equal") + }, + { + id: "between", + name: FilterUtils.getOperatorTranslationKey("between") + }, + { + id: "not_between", + name: FilterUtils.getOperatorTranslationKey("not_between") + }, + { + id: "none", + name: FilterUtils.getCustomOperatorTranslationKey("test", "none") + } + ] + }); + }); + + it("with specified operators should return a valid config", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }).setOperators(FilterOperatorTypes.Equal, FilterOperatorTypes.NotEqual); + filter.translateService = new DefaultTranslateAdapter(); + const config = await filter.getConfig("", null); + expect(config).toBeDefined(); + expect(config).toStrictEqual({ + type: FilterType.Date, + operators: [ + { + id: "equal", + name: FilterUtils.getOperatorTranslationKey("equal") + }, + { + id: "not_equal", + name: FilterUtils.getOperatorTranslationKey("not_equal") + } + ] + }); + }); + }); + + describe("getWhereOptions", () => { + it("legacy date formats should be supported", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + + const dateOnly = await filter.getWhereOptions({ + id: "test", + value: "2020-03-06", + operation: FilterOperatorTypes.Equal + }); + expect(dateOnly).toBeDefined(); + expect(dateOnly).toStrictEqual({ + test: "2020-03-06" + }); + + const dateTime = await filter.getWhereOptions({ + id: "test", + value: "2020-03-06 00:55:12", + operation: FilterOperatorTypes.Equal + }); + expect(dateTime).toBeDefined(); + expect(dateTime).toStrictEqual({ + test: "2020-03-06" + }); + }); + + it("with equal operator should return a valid filter config", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: "2020-03-06T00:55:12", + operation: FilterOperatorTypes.Equal + }); + expect(options).toBeDefined(); + expect(options).toStrictEqual({ + test: "2020-03-06" + }); + }); + + it("with not equal operator should return a valid filter config", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: "2020-03-06T00:55:12", + operation: FilterOperatorTypes.NotEqual + }); + expect(options).toBeDefined(); + expect(options).toStrictEqual({ + test: { + [Op.ne]: "2020-03-06" + } + }); + }); + + it("with less operator should return a valid filter config", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: "2020-03-06T00:55:12", + operation: FilterOperatorTypes.Less + }); + expect(options).toBeDefined(); + expect(options).toStrictEqual({ + test: { + [Op.lt]: "2020-03-06" + } + }); + }); + + it("with less or equal operator should return a valid filter config", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: "2020-03-06T00:55:12", + operation: FilterOperatorTypes.LessOrEqual + }); + expect(options).toBeDefined(); + expect(options).toStrictEqual({ + test: { + [Op.lte]: "2020-03-06" + } + }); + }); + + it("with greater operator should return a valid filter config", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: "2020-03-06T00:55:12", + operation: FilterOperatorTypes.Greater + }); + expect(options).toBeDefined(); + expect(options).toStrictEqual({ + test: { + [Op.gt]: "2020-03-06" + } + }); + }); + + it("with greater or equal operator should return a valid filter config", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: "2020-03-06T00:55:12", + operation: FilterOperatorTypes.GreaterOrEqual + }); + expect(options).toBeDefined(); + expect(options).toStrictEqual({ + test: { + [Op.gte]: "2020-03-06" + } + }); + }); + + it("with between operator should return a valid filter config", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: ["2020-03-06T00:55:12", "2020-06-06T00:55:12"], + operation: FilterOperatorTypes.Between + }); + expect(options).toBeDefined(); + expect(options).toStrictEqual({ + test: { + [Op.between]: [ + "2020-03-06", + "2020-06-06" + ] + } + }); + }); + + it("with not between operator should return a valid filter config", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: ["2020-03-06T00:55:12", "2020-06-06T00:55:12"], + operation: FilterOperatorTypes.NotBetween + }); + expect(options).toBeDefined(); + expect(options).toStrictEqual({ + test: { + [Op.notBetween]: [ + "2020-03-06", + "2020-06-06" + ] + } + }); + }); + + describe("early returns", () => { + it("with between operator and non-array value should return undefined", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: "2020-03-06T00:55:12", + operation: FilterOperatorTypes.Between + }); + expect(options).toBeUndefined(); + }); + + it("with between operator and array with wrong length should return undefined", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: ["2020-03-06T00:55:12"], + operation: FilterOperatorTypes.Between + }); + expect(options).toBeUndefined(); + }); + + it("with between operator and array with non-string values should return undefined", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: [123, "2020-06-06T00:55:12"], + operation: FilterOperatorTypes.Between + }); + expect(options).toBeUndefined(); + }); + + it("with not between operator and non-array value should return undefined", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: "2020-03-06T00:55:12", + operation: FilterOperatorTypes.NotBetween + }); + expect(options).toBeUndefined(); + }); + + it("with not between operator and array with wrong length should return undefined", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: ["2020-03-06T00:55:12", "2020-06-06T00:55:12", "2020-09-06T00:55:12"], + operation: FilterOperatorTypes.NotBetween + }); + expect(options).toBeUndefined(); + }); + + it("with not between operator and array with non-string values should return undefined", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: ["2020-03-06T00:55:12", 456], + operation: FilterOperatorTypes.NotBetween + }); + expect(options).toBeUndefined(); + }); + + it("with equal operator and non-string value should return undefined", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: 123, + operation: FilterOperatorTypes.Equal + }); + expect(options).toBeUndefined(); + }); + + it("with not equal operator and non-string value should return undefined", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: null, + operation: FilterOperatorTypes.NotEqual + }); + expect(options).toBeUndefined(); + }); + + it("with less operator and non-string value should return undefined", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: [], + operation: FilterOperatorTypes.Less + }); + expect(options).toBeUndefined(); + }); + + it("with greater operator and non-string value should return undefined", async () => { + const filter = new DateOnlyFilter({ + attribute: "test" + }); + const options = await filter.getWhereOptions({ + id: "test", + value: {}, + operation: FilterOperatorTypes.Greater + }); + expect(options).toBeUndefined(); + }); + }); + }); +}); + diff --git a/data-filter/lib/filter/filters/date-only.filter.ts b/data-filter/lib/filter/filters/date-only.filter.ts index e620986..c46985b 100644 --- a/data-filter/lib/filter/filters/date-only.filter.ts +++ b/data-filter/lib/filter/filters/date-only.filter.ts @@ -1,11 +1,45 @@ -import { DateFilter } from "./date.filter"; -import { BaseFilterDefinition } from "./filter"; +import { format, parseISO } from "date-fns"; +import { WhereOptions } from "sequelize"; +import { QueryRuleModel } from "../models"; +import { DateOperators, FilterOperatorTypes } from "../operators"; +import { BaseFilterDefinition, Filter } from "./filter"; +import { FilterType } from "../type"; + +export class DateOnlyFilter extends Filter { + public type = FilterType.Date; + public operators = [...DateOperators]; -export class DateOnlyFilter extends DateFilter { constructor(definition: BaseFilterDefinition) { - super({ - ...definition, - skipTimezone: true + super(definition); + } + + public override async getWhereOptions(rule: QueryRuleModel): Promise { + if (rule.operation === FilterOperatorTypes.Between || rule.operation === FilterOperatorTypes.NotBetween) { + if ( + !Array.isArray(rule.value) || + rule.value.length !== 2 || + rule.value.some((x) => typeof x !== "string") + ) { + return; + } + + const values = rule.value.map((x) => format(parseISO(x), "yyyy-MM-dd")); + return super.getWhereOptions({ + ...rule, + value: values, + }); + } + + if (typeof rule.value !== "string") { + return; + } + + const parsedValue = format(parseISO(rule.value), "yyyy-MM-dd"); + + return super.getWhereOptions({ + operation: rule.operation, + value: parsedValue, + id: rule.id, }); } }