diff --git a/packages/condition/README.md b/packages/condition/README.md new file mode 100644 index 0000000000..167567fab7 --- /dev/null +++ b/packages/condition/README.md @@ -0,0 +1,182 @@ +# @univerjs/univer-condition + +[![npm version](https://img.shields.io/npm/v/@univerjs/univer-condition)](https://npmjs.org/packages/@univerjs/univer-condition) +[![license](https://img.shields.io/npm/l/@univerjs/univer-condition)](https://img.shields.io/npm/l/@univerjs/univer-condition) + +## Introduction + +> In sheet, there are many features have ability to custom a rule , when the rule is match, something will happen. + +- A formatter will be used, (condition format) +- A hidden row action will execute, (filter) +- A validation will be apply (data validation) +- A pivot filter will be execute (pivot table filter) + +### design + +``` typescript +interface ICondition { + conType :ConType; + compareType:compareType + expect:any; +} + +enum ConType{ + +} +enum compareType{ + +} +``` + +## Usage + +### Installation + +```shell +# Using npm +npm i @univerjs/univer-condition + +# Using pnpm +pnpm add @univerjs/univer-condition +``` + +### Classification + +#### Number + +> For compare type operator , the following table list all + +| enum | description | +| :----------------- | :---------------------------------------------------------------------------------------------------------------------------- | +| equal | The given value is equal to the expected | +| notEqual | a value is not equal to a expected value | +| greaterThan | a value is greater than to a expected value | +| greaterThanOrEqual | a value is greater than or equal to a expected value | +| lessThan | a value is less than a expected value | +| lessThanOrEqual | a value is less than than or equal to a expected value | +| between | closed interval , a value is greater than or equal to the smaller expected value, and less than or equal to the bigger value | +| notBetween | open interval, a value is greater than or equal to the bigger expected value, and less than or equal to the smaller value | + +> The following show statistics operator + +| enum | description | +| :---- | :----------------------------------------- | +| above | the value is bigger than the average value | +| below | the value is less than the average value | +| top10 | Value in the top/bottom N values | + + + +#### Date +> The date is a special type in excel, but in fact ,there are no date value in excel , the date be expressed by a number value and a format + +For date type condition , there are there kinds: Date dynamic condition , Date Compare condition, Date Choose Condition, Date group + +- when using whole day is set, it means the data 2024/05/27 00-00-01 is equal to the data 2024/05/27 23-59-59 + +##### Date dynamic condition + +- Notice : In most country , the Saturday is last day of a week + +| enum | description | +| :---------- | :------------------------------------------- | +| tomorrow | day after today | +| today | today | +| yesterday | yesterday | +| nextWeek | next week | +| thisWeek | this week | +| lastWeek | last week | +| nextMonth | next month | +| thisMonth | this month | +| lastMonth | last month | +| nextQuarter | next quarter | +| thisQuarter | this quarter | +| lastQuarter | last quarter | +| nextYear | next year | +| thisYear | this year | +| lastYear | last year | +| yearToDate | the time area for this year begin to current | + +##### Date Compare condition + + + +| enum | description | +| :------------------- | :---------------------------------------- | +| dateEqual | the same date | +| dateNotEqual | not the same date | +| dateOlderThan | old than expected date | +| dateOlderThanOrEqual | old than or equal date | +| dateNewerThan | before expected date | +| dateNewerThanOrEqual | before or equal expected date | +| dateBetween | a date is between two other expected date | +| dateNotBetween | a date is between two other expected date | + +##### Date Choose Condition + +| enum | description | +| :--- | :--------------------------- | +| Q1 | The first quarter of a year | +| Q2 | The second quarter of a year | +| Q3 | The 3'th quarter of a year | +| Q4 | The last quarter of a year | +| M1 | January | +| M2 | February | +| M3 | March | +| M4 | April | +| M5 | May | +| M6 | June | +| M7 | July | +| M8 | August | +| M9 | September | +| M10 | October | +| M11 | November | +| M12 | December | + +#### Date group +| enum | description | +| :----- | :-------------- | +| day | Group by day | +| hour | Group by hour | +| minute | Group by minute | +| month | Group by month | +| second | Group by second | +| year | Group by year | + + + +#### Text + +| enum | description | +| :----------------- | :---------------------------------------------------------------------------------------------------------------------------- | +| Equal | a value is equal a expected value | +| NotEqual | a value is not equal a expected value | +| BeginsWith | the string start with expected text | +| NotBeginsWith | the string not start with expected text | +| EndsWith | the string end with expected text | +| NotEndsWith | the string not end with expected text | +| Contains | the string contain expected text | +| NotContains | the string not contain expected text | +| GreaterThan | a value is greater than the expected value | +| GreaterThanOrEqual | a value is greater than or equal to the expected value | +| LessThan | a value is greater than the expected value | +| LessThanOrEqual | a value is greater than or equal the expected value | +| Between | closed interval , a value is greater than or equal to the smaller expected value, and less than or equal to the bigger value | +| NotBetween | open interval, a value is greater than or equal to the bigger expected value, and less than or equal to the smaller value | + +#### Logic +> A logic condition can combine some condition together + +| enum | description | +| :--- | :------------------------------------------------------------------------------ | +| not | return a opposite of expect condition | +| and | all expect conditions is true it will return true , otherwise it will be false | +| or | one of expect conditions is true , it will return true | + +#### Color & IconSet + +- For color , there are font color and cell color two kinds of a cell +- For icon set filter, this element specifies the icon set and particular icon within that set to filter by. For any cells whose icon does +not match the specified criteria, the corresponding rows shall be hidden from view when the filter is applied. + diff --git a/packages/condition/package.json b/packages/condition/package.json new file mode 100644 index 0000000000..8217e68e1a --- /dev/null +++ b/packages/condition/package.json @@ -0,0 +1,75 @@ +{ + "name": "@univerjs/univer-condition", + "version": "0.0.1", + "private": true, + "description": "", + "author": "DreamNum ", + "license": "Apache-2.0", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/univer" + }, + "homepage": "https://univer.ai", + "repository": { + "type": "git", + "url": "https://github.com/dream-num/univer" + }, + "bugs": { + "url": "https://github.com/dream-num/univer/issues" + }, + "keywords": [], + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "main": "./lib/cjs/index.js", + "module": "./lib/es/index.js", + "types": "./lib/types/index.d.ts", + "publishConfig": { + "access": "public", + "main": "./lib/cjs/index.js", + "module": "./lib/es/index.js", + "exports": { + ".": { + "import": "./lib/es/index.js", + "require": "./lib/cjs/index.js", + "types": "./lib/types/index.d.ts" + }, + "./*": { + "import": "./lib/es/*", + "require": "./lib/cjs/*", + "types": "./lib/types/index.d.ts" + }, + "./lib/*": "./lib/*" + } + }, + "directories": { + "lib": "lib" + }, + "files": [ + "lib" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "coverage": "vitest run --coverage", + "lint:types": "tsc --noEmit", + "build": "tsc && vite build" + }, + "peerDependencies": { + "@univerjs/core": "workspace:*", + "@wendellhu/redi": ">=0.12.13", + "rxjs": ">=7.0.0" + }, + "dependencies": { + }, + "devDependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/shared": "workspace:*", + "@wendellhu/redi": "^0.15.2", + "rxjs": "^7.8.1", + "typescript": "^5.4.5", + "vite": "^5.2.11", + "vitest": "^1.6.0" + } +} diff --git a/packages/condition/src/_tests_/test.date.spec.ts b/packages/condition/src/_tests_/test.date.spec.ts new file mode 100644 index 0000000000..98936d16b4 --- /dev/null +++ b/packages/condition/src/_tests_/test.date.spec.ts @@ -0,0 +1,117 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; +import { dateM1, dateM10, dateM11, dateM12, dateM2, dateM3, dateM4, dateM5, dateM6, dateM7, dateM8, dateM9, dateQ1, dateQ2, dateQ3, dateQ4 } from '../condition/dateGenerator'; + +describe('date grouping test', () => { + it('dateM1 test', () => { + const date = new Date(); + date.setMonth(0, 1); + expect(dateM1(date)).toBe(true); + }); + it('dateM2 test', () => { + const date = new Date(); + date.setMonth(1, 1); + expect(dateM2(date)).toBe(true); + }); + it('dateM3 test', () => { + const date = new Date(); + date.setMonth(2, 1); + expect(dateM3(date)).toBe(true); + }); + it('dateM4 test', () => { + const date = new Date(); + date.setMonth(3, 1); + expect(dateM4(date)).toBe(true); + }); + it('dateM5 test', () => { + const date = new Date(); + date.setMonth(4, 1); + expect(dateM5(date)).toBe(true); + }); + it('dateM6 test', () => { + const date = new Date(); + date.setMonth(5, 1); + expect(dateM6(date)).toBe(true); + }); + it('dateM7 test', () => { + const date = new Date(); + date.setMonth(6, 1); + expect(dateM7(date)).toBe(true); + }); + it('dateM8 test', () => { + const date = new Date(); + date.setMonth(7, 1); + expect(dateM8(date)).toBe(true); + }); + it('dateM9 test', () => { + const date = new Date(); + date.setMonth(8, 1); + expect(dateM9(date)).toBe(true); + }); + it('dateM10 test', () => { + const date = new Date(); + date.setMonth(9, 1); + expect(dateM10(date)).toBe(true); + }); + it('dateM11 test', () => { + const date = new Date(); + date.setMonth(10, 1); + expect(dateM11(date)).toBe(true); + }); + it('dateM12 test', () => { + const date = new Date(); + date.setMonth(11, 1); + expect(dateM12(date)).toBe(true); + }); + it('dateQ1 test', () => { + const date = new Date(); + date.setMonth(0, 1); + expect(dateQ1(date)).toBe(true); + }); + it('dateQ2 test', () => { + const date = new Date(); + date.setMonth(3, 1); + expect(dateQ2(date)).toBe(true); + }); + it('dateQ3 test', () => { + const date = new Date(); + date.setMonth(6, 1); + expect(dateQ3(date)).toBe(true); + }); + it('dateQ4 test', () => { + const date = new Date(); + date.setMonth(9, 1); + expect(dateQ4(date)).toBe(true); + }); +}); + +describe('spacial date grouping test', () => { + it('spacial dateM1 test', () => { + const date = new Date(); + date.setMonth(0, 31); + expect(dateM1(date)).toBe(true); + }); + it('spacial dateM1 test', () => { + const date = new Date(); + date.setMonth(0, 31); + date.setMonth(1); + // in this case, the February 31 is not a valid date, so the date change to March 3 + expect(dateM2(date)).toBe(false); + expect(dateM3(date)).toBe(true); + }); +}); diff --git a/packages/condition/src/_tests_/test.dateGroup.spec.ts b/packages/condition/src/_tests_/test.dateGroup.spec.ts new file mode 100644 index 0000000000..7fe8e98d80 --- /dev/null +++ b/packages/condition/src/_tests_/test.dateGroup.spec.ts @@ -0,0 +1,153 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; +import { groupByDay, groupByHour, groupByMinute, groupByMonth, groupByYear } from '../condition/dateGroupGenerator'; +import type { IDateGroupByDayExpected, IDateGroupByHourExpected, IDateGroupByMinuteExpected, IDateGroupByMonthExpected, IDateGroupByYearExpected } from '../condition/types'; +import { DateGroupCompareTypeEnum } from '../condition/types'; + +describe('dateGroup test', () => { + describe('groupByDay test', () => { + it('should group dates by day', () => { + // Test case 1 + const input1 = [new Date('2022-01-01'), new Date('2023-01-01'), new Date('2022-01-02')]; + const expected1: IDateGroupByDayExpected = { + year: 2022, + month: 0, + day: 1, + dateGroupCompareType: DateGroupCompareTypeEnum.day, + }; + expect(groupByDay(input1[0], expected1)).toEqual(true); + expect(groupByDay(input1[1], expected1)).toEqual(false); + expect(groupByDay(input1[2], expected1)).toEqual(false); + + // Test case 2 + const input2 = [new Date('2022-02-01'), new Date('2022-02-02'), new Date('2022-02-02')]; + const expected2: IDateGroupByDayExpected = { + year: 2022, + month: 1, + day: 2, + dateGroupCompareType: DateGroupCompareTypeEnum.day, + }; + expect(groupByDay(input2[0], expected2)).toEqual(false); + expect(groupByDay(input2[1], expected2)).toEqual(true); + expect(groupByDay(input2[2], expected2)).toEqual(true); + }); + it('should group dates by month', () => { + // Test case 1 + const input1 = [new Date('2022-01-01'), new Date('2023-01-01'), new Date('2022-01-02')]; + const expected1: IDateGroupByMonthExpected = { + year: 2022, + month: 0, + dateGroupCompareType: DateGroupCompareTypeEnum.month, + }; + expect(groupByMonth(input1[0], expected1)).toEqual(true); + expect(groupByMonth(input1[1], expected1)).toEqual(false); + expect(groupByMonth(input1[2], expected1)).toEqual(true); + + // Test case 2 + const input2 = [new Date('2022-02-01'), new Date('2022-02-02'), new Date('2022-02-02')]; + const expected2: IDateGroupByMonthExpected = { + year: 2022, + month: 1, + dateGroupCompareType: DateGroupCompareTypeEnum.month, + }; + expect(groupByMonth(input2[0], expected2)).toEqual(true); + expect(groupByMonth(input2[1], expected2)).toEqual(true); + expect(groupByMonth(input2[2], expected2)).toEqual(true); + }); + + it('should group dates by year', () => { + // Test case 1 + const input1 = [new Date('2022-01-01'), new Date('2023-01-01'), new Date('2022-01-02')]; + const expected1: IDateGroupByYearExpected = { + year: 2022, + dateGroupCompareType: DateGroupCompareTypeEnum.year, + }; + expect(groupByYear(input1[0], expected1)).toEqual(true); + expect(groupByYear(input1[1], expected1)).toEqual(false); + expect(groupByYear(input1[2], expected1)).toEqual(true); + + // Test case 2 + const input2 = [new Date('2022-02-01'), new Date('2022-02-02'), new Date('2022-02-02')]; + const expected2: IDateGroupByYearExpected = { + year: 2022, + dateGroupCompareType: DateGroupCompareTypeEnum.year, + }; + expect(groupByYear(input2[0], expected2)).toEqual(true); + expect(groupByYear(input2[1], expected2)).toEqual(true); + expect(groupByYear(input2[2], expected2)).toEqual(true); + }); + + it('should group dates by hour', () => { + // Test case 1 + const input1 = [new Date('2022-01-01T01:00:00'), new Date('2023-01-01T01:00:00'), new Date('2022-01-02T01:00:00')]; + const expected1: IDateGroupByHourExpected = { + year: 2022, + month: 0, + day: 1, + hour: 1, + dateGroupCompareType: DateGroupCompareTypeEnum.hour, + }; + + expect(groupByHour(input1[0], expected1)).toEqual(true); + expect(groupByHour(input1[1], expected1)).toEqual(false); + expect(groupByHour(input1[2], expected1)).toEqual(false); + + // Test case 2 + const input2 = [new Date('2022-02-01T02:00:00'), new Date('2022-02-02T02:00:00'), new Date('2022-02-02T02:00:00')]; + const expected2: IDateGroupByHourExpected = { + year: 2022, + month: 1, + day: 2, + hour: 2, + dateGroupCompareType: DateGroupCompareTypeEnum.hour, + }; + expect(groupByHour(input2[0], expected2)).toEqual(false); + expect(groupByHour(input2[1], expected2)).toEqual(true); + expect(groupByHour(input2[2], expected2)).toEqual(true); + }); + it('should group dates by minute', () => { + // Test case 1 + const input1 = [new Date('2022-01-01T01:00:00'), new Date('2023-01-01T01:00:00'), new Date('2022-01-02T01:00:00')]; + const expected1: IDateGroupByMinuteExpected = { + year: 2022, + month: 0, + day: 1, + hour: 1, + minute: 0, + dateGroupCompareType: DateGroupCompareTypeEnum.minute, + }; + expect(groupByMinute(input1[0], expected1)).toEqual(true); + expect(groupByMinute(input1[1], expected1)).toEqual(false); + expect(groupByMinute(input1[2], expected1)).toEqual(false); + + // Test case 2 + const input2 = [new Date('2022-02-01T02:00:00'), new Date('2022-02-02T02:00:00'), new Date('2022-02-02T02:00:00')]; + const expected2: IDateGroupByMinuteExpected = { + year: 2022, + month: 1, + day: 2, + hour: 2, + minute: 0, + dateGroupCompareType: DateGroupCompareTypeEnum.minute, + }; + expect(groupByMinute(input2[0], expected2)).toEqual(false); + expect(groupByMinute(input2[1], expected2)).toEqual(true); + expect(groupByMinute(input2[2], expected2)).toEqual(true); + }); + }); +}); diff --git a/packages/condition/src/_tests_/test.dynamic.spec.ts b/packages/condition/src/_tests_/test.dynamic.spec.ts new file mode 100644 index 0000000000..15b83821a0 --- /dev/null +++ b/packages/condition/src/_tests_/test.dynamic.spec.ts @@ -0,0 +1,73 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; +import { above, below, getBottomN, getTopN, thisWeek, today, tomorrow, yesterday } from '../condition/dynamicGenerator'; + +describe('dynamic test', () => { + it('above text', () => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + const avg = arr.reduce((a, b) => a + b) / arr.length; + + expect(above(1, avg)).toBe(false); + expect(above(6, avg)).toBe(true); + }); + it('below text', () => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + const avg = arr.reduce((a, b) => a + b) / arr.length; + + expect(below(1, avg)).toBe(true); + expect(below(6, avg)).toBe(false); + }); + it('getTopN test', () => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + expect(getTopN(arr, 5, 4)).toMatchObject(false); + expect(getTopN(arr, 5, 6)).toMatchObject(true); + }); + it('getBottomN test', () => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + expect(getBottomN(arr, 5, 4)).toMatchObject(true); + expect(getBottomN(arr, 5, 6)).toMatchObject(false); + }); + it('today test', () => { + const date = new Date(); + expect(today(date)).toBe(true); + }); + it('today test2', () => { + const date = new Date(); + date.setHours(0, 0, 0, 0); + expect(today(date)).toBe(true); + }); + it('tomorrow test', () => { + const date = new Date(); + date.setDate(date.getDate() + 1); + expect(tomorrow(date)).toBe(true); + }); + it('yesterday test', () => { + const date = new Date(); + date.setDate(date.getDate() - 1); + expect(yesterday(date)).toBe(true); + }); + it('thisWeek test', () => { + const date = new Date(); + expect(thisWeek(date)).toBe(true); + + date.setDate(date.getDate() - 7); + expect(thisWeek(date)).toBe(false); + }); +}); diff --git a/packages/condition/src/_tests_/test.heap.spec.ts b/packages/condition/src/_tests_/test.heap.spec.ts new file mode 100644 index 0000000000..30d9f6c71c --- /dev/null +++ b/packages/condition/src/_tests_/test.heap.spec.ts @@ -0,0 +1,73 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; +import { getLargestK, getSmallestK } from '../condition/topN'; + +const randomSort = (a: number, b: number) => Math.random() > 0.5 ? 1 : -1; + +describe('heap test', () => { + it('topN test', () => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + const small = getSmallestK(arr, 5); + const large = getLargestK(arr, 5); + expect(small.sort()).toMatchObject([5, 3, 4, 1, 2].sort()); // large heap + expect(large.sort()).toMatchObject([6, 8, 7, 10, 9].sort()); // small heap + }); + + it('topN test2', () => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + const small = getSmallestK(arr, 4); + const large = getLargestK(arr, 4); + expect(small.sort()).toMatchObject([4, 2, 3, 1].sort()); + expect(large.sort()).toMatchObject([10, 9, 8, 7].sort()); + }); + + it('topN test randomSort arr', () => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].sort(randomSort); + const small = getSmallestK(arr, 4); + const large = getLargestK(arr, 4); + expect(small.sort()).toMatchObject([4, 2, 3, 1].sort()); + expect(large.sort()).toMatchObject([10, 9, 8, 7].sort()); + }); + + it('topN test randomSort arr2', () => { + const arr = [1, -2, 3, 4, -5, 6, 7, 8, 9, -10].sort(randomSort); + const small = getSmallestK(arr, 5); + const large = getLargestK(arr, 5); + expect(small.sort()).toMatchObject([3, -2, 1, -10, -5].sort()); + expect(large.sort()).toMatchObject([4, 7, 6, 9, 8].sort()); + }); + + it('topN test compare with array sort', () => { + const arr: number[] = []; + for (let i = 0; i < 100; i++) { + arr.push(Math.floor(Math.random() * 1000)); + } + const compare = (a: number, b: number) => a - b; + + const small = getSmallestK(arr, 5); + const large = getLargestK(arr, 5); + + arr.sort(compare); + const rs = arr.slice(0, 5); + expect(rs).toMatchObject(small.sort(compare)); + + arr.reverse(); + const rs2 = arr.slice(0, 5); + expect(rs2.sort(compare)).toMatchObject(large.sort(compare)); + }); +}); diff --git a/packages/condition/src/_tests_/test.text.spec.ts b/packages/condition/src/_tests_/test.text.spec.ts new file mode 100644 index 0000000000..7a06ab6f94 --- /dev/null +++ b/packages/condition/src/_tests_/test.text.spec.ts @@ -0,0 +1,100 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; +import { textContain, textEqual, textNotContain, textNotEqual, textStartWith } from '../condition/textFuncGenerator'; + +describe('text condition test', () => { + it('textEqual test', () => { + // unit test for textEqual + expect(textEqual('a', 'a')).toBe(true); + expect(textEqual('a', 'b')).toBe(false); + expect(textEqual('abc', 'abc')).toBe(true); + expect(textEqual('abc', 'def')).toBe(false); + }); + + it('textNotEqual test', () => { + // unit test for textNotEqual + expect(textNotEqual('a', 'a')).toBe(false); + expect(textNotEqual('a', 'b')).toBe(true); + expect(textNotEqual('abc', 'abc')).toBe(false); + expect(textNotEqual('abc', 'def')).toBe(true); + }); + it('textContain test', () => { + // unit test for textContain + expect(textContain('a', 'a')).toBe(true); + expect(textContain('a', 'b')).toBe(false); + expect(textContain('abc', 'abc')).toBe(true); + expect(textContain('abc', 'def')).toBe(false); + }); + it('textNotContain test', () => { + // unit test for textNotContain + expect(textNotContain('a', 'a')).toBe(false); + expect(textNotContain('a', 'b')).toBe(true); + expect(textNotContain('abc', 'abc')).toBe(false); + expect(textNotContain('abc', 'def')).toBe(true); + }); + + it('textNotContain test with empty strings', () => { + // unit test for textNotContain with empty strings + expect(textNotContain('', '')).toBe(false); + expect(textNotContain('', 'a')).toBe(true); + expect(textNotContain('abc', '')).toBe(false); + }); + + it('textContain test with empty strings', () => { + // unit test for textContain with empty strings + expect(textContain('', '')).toBe(true); + expect(textContain('', 'a')).toBe(false); + expect(textContain('abc', '')).toBe(true); + }); + + it('textEqual test with empty strings', () => { + // unit test for textEqual with empty strings + expect(textEqual('', '')).toBe(true); + expect(textEqual('', 'a')).toBe(false); + expect(textEqual('abc', '')).toBe(false); + }); + + it('textNotEqual test with empty strings', () => { + // unit test for textNotEqual with empty strings + expect(textNotEqual('', '')).toBe(false); + expect(textNotEqual('', 'a')).toBe(true); + expect(textNotEqual('abc', '')).toBe(true); + }); + it('textStartWith test with empty strings', () => { + // unit test for textStartWith with empty strings + expect(textStartWith('', '')).toBe(true); + expect(textStartWith('', 'a')).toBe(false); + expect(textStartWith('abc', '')).toBe(true); + }); + + it('textStartWith test with different case', () => { + // unit test for textStartWith with different case + expect(textStartWith('abc', 'a')).toBe(true); + expect(textStartWith('ABC', 'a')).toBe(false); + expect(textStartWith('abc', 'A')).toBe(false); + expect(textStartWith('ABC', 'A')).toBe(true); + }); + + it('textStartWith test with whitespace', () => { + // unit test for textStartWith with whitespace + expect(textStartWith('abc', 'a ')).toBe(false); + expect(textStartWith('abc', ' a')).toBe(false); + expect(textStartWith(' abc', 'a')).toBe(true); + expect(textStartWith('a bc', 'a')).toBe(true); + }); +}); diff --git a/packages/condition/src/condition/dateGenerator.ts b/packages/condition/src/condition/dateGenerator.ts new file mode 100644 index 0000000000..0521ecfbdb --- /dev/null +++ b/packages/condition/src/condition/dateGenerator.ts @@ -0,0 +1,132 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @descriptionThe first quarter of a year + * @returns {boolean} return the date is match + */ +export const dateQ1 = (date: Date): boolean => { + const month = date.getMonth(); + return month <= 2; +}; +/** + * @description The second quarter of a year + * @returns {boolean} return the date is match + */ +export const dateQ2 = (date: Date): boolean => { + const month = date.getMonth(); + return month > 2 && month <= 5; +}; +/** + * @description The 3'th quarter of a year + * @returns {boolean} return the date is match + */ +export const dateQ3 = (date: Date): boolean => { + const month = date.getMonth(); + return month > 5 && month <= 8; +}; +/** + * @description The last quarter of a year + * @returns {boolean} return the date is match + */ +export const dateQ4 = (date: Date): boolean => { + const month = date.getMonth(); + return month > 8 && month <= 11; +}; +/** + * @description January + * @returns {boolean} return the date is match + */ +export const dateM1 = (date: Date): boolean => { + return date.getMonth() === 0; +}; +/** + * @description February + * @returns {boolean} return the date is match + */ +export const dateM2 = (date: Date): boolean => { + return date.getMonth() === 1; +}; +/** + * @description March + * @returns {boolean} return the date is match + */ +export const dateM3 = (date: Date): boolean => { + return date.getMonth() === 2; +}; +/** + * @description April + * @returns {boolean} return the date is match + */ +export const dateM4 = (date: Date): boolean => { + return date.getMonth() === 3; +}; +/** + * @description May + * @returns {boolean} return the date is match + */ +export const dateM5 = (date: Date): boolean => { + return date.getMonth() === 4; +}; +/** + * @description June + * @returns {boolean} return the date is match + */ +export const dateM6 = (date: Date): boolean => { + return date.getMonth() === 5; +}; +/** + * @description July + * @returns {boolean} return the date is match + */ +export const dateM7 = (date: Date): boolean => { + return date.getMonth() === 6; +}; +/** + * @description August + * @returns {boolean} return the date is match + */ +export const dateM8 = (date: Date): boolean => { + return date.getMonth() === 7; +}; +/** + * @description September + * @returns {boolean} return the date is match + */ +export const dateM9 = (date: Date): boolean => { + return date.getMonth() === 8; +}; +/** + * @description October + * @returns {boolean} return the date is match + */ +export const dateM10 = (date: Date): boolean => { + return date.getMonth() === 9; +}; +/** + * @description November + * @returns {boolean} return the date is match + */ +export const dateM11 = (date: Date): boolean => { + return date.getMonth() === 10; +}; +/** + * @description December + * @returns {boolean} return the date is match + */ +export const dateM12 = (date: Date): boolean => { + return date.getMonth() === 11; +}; diff --git a/packages/condition/src/condition/dateGroupGenerator.ts b/packages/condition/src/condition/dateGroupGenerator.ts new file mode 100644 index 0000000000..1451963836 --- /dev/null +++ b/packages/condition/src/condition/dateGroupGenerator.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IDateGroupByDayExpected, IDateGroupByHourExpected, IDateGroupByMinuteExpected, IDateGroupByMonthExpected, IDateGroupByYearExpected } from './types'; + +/** + * @description match xml dateTimeGrouping by day, + * @param {Date} date the date to be compared + * @param {IDateGroupByDayExpected} expected the expected date group by day + * @returns {boolean} return true if the date matches the expected grouping + */ +export const groupByDay = (date: Date, expected: IDateGroupByDayExpected) => { + return date.getFullYear() === expected.year && date.getMonth() === expected.month && date.getDate() === expected.day; +}; + +/** + * @description match xml dateTimeGrouping by month, + * @param {Date} date the date to be compared + * @param {IDateGroupByMonthExpected} expected the expected date group by month + * @returns {boolean} return true if the date matches the expected grouping + */ +export const groupByMonth = (date: Date, expected: IDateGroupByMonthExpected) => { + return date.getFullYear() === expected.year && date.getMonth() === expected.month; +}; + +/** + * @description match xml dateTimeGrouping by year, + * @param {Date} date the date to be compared + * @param {IDateGroupByYearExpected} expected the expected date group by year + * @returns {boolean} return true if the date matches the expected grouping + */ +export const groupByYear = (date: Date, expected: IDateGroupByYearExpected) => { + return date.getFullYear() === expected.year; +}; + +/** + * @description match xml dateTimeGrouping by hour, + * @param {Date} date the date to be compared + * @param {IDateGroupByHourExpected} expected the expected date group by hour + * @returns {boolean} return true if the date matches the expected grouping + */ +export const groupByHour = (date: Date, expected: IDateGroupByHourExpected) => { + return date.getFullYear() === expected.year && date.getMonth() === expected.month && date.getDate() === expected.day && date.getHours() === expected.hour; +}; + +/** + * @description match xml dateTimeGrouping by minute, + * @param {Date} date the date to be compared + * @param {IDateGroupByMinuteExpected} expected the expected date group by minute + * @returns {boolean} return true if the date matches the expected grouping + */ +export const groupByMinute = (date: Date, expected: IDateGroupByMinuteExpected) => { + return date.getFullYear() === expected.year && date.getMonth() === expected.month && date.getDate() === expected.day && date.getHours() === expected.hour && date.getMinutes() === expected.minute; +}; diff --git a/packages/condition/src/condition/dynamicGenerator.ts b/packages/condition/src/condition/dynamicGenerator.ts new file mode 100644 index 0000000000..b59084dde0 --- /dev/null +++ b/packages/condition/src/condition/dynamicGenerator.ts @@ -0,0 +1,302 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getLargestK, getSmallestK } from './topN'; + +/** + * @description Checks if a value is above the average. + * @param {number} value - The value to check. + * @param {number} average - The average value. + * @returns {boolean} A boolean value indicating whether the value is above the average. + */ +export const above = (value: number, average: number): boolean => { + return value > average; +}; + +/** + * @description Checks if a value is below the average. + * @param {number} value - The value to check. + * @param {number} average - The average value. + * @returns {boolean} A boolean value indicating whether the value is below the average. + */ +export const below = (value: number, average: number): boolean => { + return value < average; +}; + +/** + * @description Gets the largest N values from a list and checks if the expected value is included. + * @param {number[]} list - The list of numbers. + * @param {number} top - The number of top values to retrieve. + * @param {number} expectedValue - The expected value to check for inclusion. + * @returns {boolean} A boolean value indicating whether the expected value is included in the top N values. + */ +export const getTopN = (list: number[], top: number, expectedValue: number): boolean => { + const heap = getLargestK(list, top); + return heap.includes(expectedValue); +}; + +/** + * @description Gets the smallest N values from a list and checks if the expected value is included. + * @param {number[]} list - The list of numbers. + * @param {number} bottom - The number of bottom values to retrieve. + * @param {number} expectedValue - The expected value to check for inclusion. + * @returns {boolean} A boolean value indicating whether the expected value is included in the bottom N values. + */ +export const getBottomN = (list: number[], bottom: number, expectedValue: number): boolean => { + const heap = getSmallestK(list, bottom); + return heap.includes(expectedValue); +}; + +/** + * @description get is same day with given date xml: + * @param {Date} expectedDate the date to be compared + * @param {Date} [anchorTime] the anchor time to compare, if the anchor time is not given, it will use the current time + * @returns {boolean} return true if the date is same day with the anchor time + */ +export const today = (expectedDate: Date, anchorTime: Date = new Date()): boolean => { + return expectedDate.toDateString() === anchorTime.toDateString(); +}; + +/** + * @description get is tomorrow with given date xml: + * @param {Date} date the date to be compared + * @param {Date} anchorTime the anchor time to compare, if the anchor time is not given, it will use the current time + * @returns {boolean} return true if the date is tomorrow with the anchor time + */ +export const tomorrow = (date: Date, anchorTime: Date = new Date()): boolean => { + const tomorrow = new Date(anchorTime); + tomorrow.setDate(tomorrow.getDate() + 1); + return date.toDateString() === tomorrow.toDateString(); +}; + +/** + * @description get is yesterday with given date xml: + * @param {Date} date the date to be compared + * @param {Date} anchorTime the anchor time to compare, if the anchor time is not given, it will use the current time + * @returns {boolean} return true if the date is yesterday with the anchor time + */ +export const yesterday = (date: Date, anchorTime: Date = new Date()): boolean => { + const yesterday = new Date(anchorTime); + yesterday.setDate(yesterday.getDate() - 1); + return date.toDateString() === yesterday.toDateString(); +}; +/** + * @description get the day a week start date + * @param {Date} date the date to be compared + * @returns {Date} return the week start date + */ +const getWeekStart = (date: Date): Date => { + // Sunday - Saturday : 0 - 6 + const day = date.getDay(); + const diff = date.getDate() - day + (day === 0 ? -6 : 1); + const weekStart = new Date(date); + weekStart.setDate(diff); + return weekStart; +}; +const perWeek = 7 * 24 * 60 * 60 * 1000; + +/** + * @description get is same week with given date xml: + * @param {Date} date the date to be compared + * @param {Date} [anchorTime] the anchor time to compare, if the anchor time is not given, it will use the current time + * @returns {boolean} return true if the date is same week with the anchor time + */ +export const thisWeek = (date: Date, anchorTime: Date = new Date()): boolean => { + const weekStart = getWeekStart(date); + const anchorTimeWeekStart = getWeekStart(anchorTime); + return weekStart.toDateString() === anchorTimeWeekStart.toDateString(); +}; + +/** + * @description Checks if a given date is exactly one week after the anchor time. + * @param {Date} date - The date to check. + * @param {Date} [anchorTime] - The anchor time. Defaults to the current date and time. + * @returns A boolean value indicating whether the given date is exactly one week after the anchor time. + */ +export const nextWeek = (date: Date, anchorTime: Date = new Date()): boolean => { + const weekStart = getWeekStart(date); + const anchorTimeNextWeekStart = new Date(getWeekStart(anchorTime).getTime() + perWeek); + return weekStart.toDateString() === anchorTimeNextWeekStart.toDateString(); +}; + +/** + * @description Checks if a given date is exactly one week before the anchor time. + * @param {Date} date - The date to check. + * @param {Date} [anchorTime] - The anchor time. Defaults to the current date and time. + * @returns A boolean value indicating whether the given date is exactly one week before the anchor time. + */ +export const lastWeek = (date: Date, anchorTime: Date = new Date()): boolean => { + const weekStart = getWeekStart(date); + const anchorTimeLastWeekStart = new Date(getWeekStart(anchorTime).getTime() - perWeek); + return weekStart.toDateString() === anchorTimeLastWeekStart.toDateString(); +}; + +/** + * @description get is same month with given date xml: + * @param {Date} date + * @param {Date} [anchorTime] the anchor time to compare, if the anchor time is not given, it will use the current time + * @returns {boolean} return true if the date is same month with the anchor time + */ +export const thisMonth = (date: Date, anchorTime: Date = new Date()): boolean => { + return date.getFullYear() === anchorTime.getFullYear() && date.getMonth() === anchorTime.getMonth(); +}; + +const getMonthStart = (date: Date): Date => { + const monthStart = new Date(date); + monthStart.setHours(0, 0, 0, 0); + monthStart.setDate(1); + return monthStart; +}; + +/** + * @description Checks if a given date is exactly one month after the anchor time. xml: + * @param {Date} date - The date to check. + * @param {Date} [anchorTime] - The anchor time. Defaults to the current date and time. + * @returns A boolean value indicating whether the given date is exactly one month after the anchor time. + */ +export const nextMonth = (date: Date, anchorTime: Date = new Date()): boolean => { + const nextMonthStart = new Date(anchorTime); + // when the date is January 31, the next month is February ,but February doesn't have 31 days, so it will be March 3 + // so must set the date to 1 + nextMonthStart.setHours(0, 0, 0, 0); + nextMonthStart.setMonth(nextMonthStart.getMonth() + 1, 1); + + const monthEnd = new Date(nextMonthStart); + monthEnd.setMonth(monthEnd.getMonth() + 1, 0); + + const dateTime = date.getTime(); + + return dateTime >= nextMonthStart.getTime() && dateTime < monthEnd.getTime(); +}; + +/** + * @description Checks if a given date is exactly one month before the anchor time. xml: + * @param {Date} date - The date to check. + * @param {Date} [anchorTime] - The anchor time. Defaults to the current date and time. + * @returns A boolean value indicating whether the given date is exactly one month before the anchor time. + */ +export const lastMonth = (date: Date, anchorTime: Date = new Date()): boolean => { + const lastMonthStart = getMonthStart(anchorTime); + + const monthEnd = new Date(lastMonthStart); + monthEnd.setMonth(monthEnd.getMonth() + 1, 0); + + const dateTime = date.getTime(); + + return dateTime >= lastMonthStart.getTime() && dateTime < monthEnd.getTime(); +}; + +const getQuarterStart = (date: Date): Date => { + const quarterStart = new Date(date); + quarterStart.setHours(0, 0, 0, 0); + quarterStart.setDate(1); + const month = quarterStart.getMonth(); + quarterStart.setMonth(month - month % 3); + return quarterStart; +}; + +/** + * @description Checks if a given date is within the current quarter. xml: + * @param {Date} date - The date to check. + * @param {Date} [anchorTime] - The anchor time. Defaults to the current date and time. + * @returns A boolean value indicating whether the given date is within the current quarter. + */ +export const thisQuarter = (date: Date, anchorTime: Date = new Date()): boolean => { + const quarterStart = getQuarterStart(anchorTime); + const nextQuarterStart = new Date(quarterStart); + nextQuarterStart.setMonth(nextQuarterStart.getMonth() + 3); + const dateTime = date.getTime(); + return dateTime >= quarterStart.getTime() && dateTime < nextQuarterStart.getTime(); +}; + +/** + * @description Checks if a given date is exactly one quarter after the anchor time. xml: + * @param {Date} date - The date to check. + * @param {Date} [anchorTime] - The anchor time. Defaults to the current date and time. + * @returns A boolean value indicating whether the given date is exactly one quarter after the anchor time. + */ +export const nextQuarter = (date: Date, anchorTime: Date = new Date()): boolean => { + const quarterStart = getQuarterStart(anchorTime); + const nextQuarterStart = new Date(quarterStart); + nextQuarterStart.setMonth(nextQuarterStart.getMonth() + 3); + const nextQuarterEnd = new Date(nextQuarterStart); + nextQuarterEnd.setMonth(nextQuarterEnd.getMonth() + 3, 0); + + const dateTime = date.getTime(); + + return dateTime >= nextQuarterStart.getTime() && dateTime < nextQuarterEnd.getTime(); +}; + +/** + * @description Checks if a given date is exactly one quarter before the anchor time. xml: + * @param {Date} date - The date to check. + * @param {Date} [anchorTime] - The anchor time. Defaults to the current date and time. + * @returns A boolean value indicating whether the given date is exactly one quarter before the anchor time. + */ +export const lastQuarter = (date: Date, anchorTime: Date = new Date()): boolean => { + const quarterStart = getQuarterStart(anchorTime); + const lastQuarterStart = new Date(quarterStart); + lastQuarterStart.setMonth(lastQuarterStart.getMonth() - 3); + const lastQuarterEnd = new Date(quarterStart); + lastQuarterEnd.setDate(0); + + const dateTime = date.getTime(); + + return dateTime >= lastQuarterStart.getTime() && dateTime < lastQuarterEnd.getTime(); +}; + +/** + * @description get is same year with given date xml: + * @param {Date} date + * @param {Date} [anchorTime] the anchor time to compare, if the anchor time is not given, it will use the current time + * @returns {boolean} return true if the date is same year with the anchor time + */ +export const thisYear = (date: Date, anchorTime: Date = new Date()): boolean => { + return date.getFullYear() === anchorTime.getFullYear(); +}; + +/** + * @description Checks if a given date is exactly one year after the anchor time. xml: + * @param {Date} date - The date to check. + * @param {Date} [anchorTime] - The anchor time. Defaults to the current date and time. + * @returns A boolean value indicating whether the given date is exactly one year after the anchor time. + */ +export const nextYear = (date: Date, anchorTime: Date = new Date()): boolean => { + return date.getFullYear() === anchorTime.getFullYear() + 1; +}; + +/** + * @description Checks if a given date is exactly one year before the anchor time. xml + * @param {Date} date - The date to check. + * @param {Date} [anchorTime] - The anchor time. Defaults to the current date and time. + * @returns A boolean value indicating whether the given date is exactly one year before the anchor time. + */ +export const lastYear = (date: Date, anchorTime: Date = new Date()): boolean => { + return date.getFullYear() === anchorTime.getFullYear() - 1; +}; + +/** + * @description Checks if a given date is exactly in same year with anchorTime but is before the anchorTime. xml: + * @param {Date} date the date to be compared + * @returns {Date} return the year start date + */ +export const yearToDate = (date: Date, anchorTime: Date = new Date()): boolean => { + const yearStart = new Date(anchorTime); + yearStart.setHours(0, 0, 0, 0); + yearStart.setMonth(0, 1); + const dateTime = date.getTime(); + return dateTime >= yearStart.getTime() && dateTime < anchorTime.getTime(); +}; diff --git a/packages/condition/src/condition/funcGenerator.ts b/packages/condition/src/condition/funcGenerator.ts new file mode 100644 index 0000000000..2e46d15422 --- /dev/null +++ b/packages/condition/src/condition/funcGenerator.ts @@ -0,0 +1,72 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { dateM1, dateM10, dateM11, dateM12, dateM2, dateM3, dateM4, dateM5, dateM6, dateM7, dateM8, dateM9, dateQ1, dateQ2, dateQ3, dateQ4 } from './dateGenerator'; +import { groupByDay, groupByHour, groupByMinute, groupByMonth, groupByYear } from './dateGroupGenerator'; +import { textEqual } from './textFuncGenerator'; +import type { CompareFunc, CompareType } from './types'; +import { DateCompareTypeEnum, DateGroupCompareTypeEnum, TextCompareTypeEnum } from './types'; + +export const compareFunctionGenerator = (compareType: CompareType): CompareFunc => { + switch (compareType) { + case TextCompareTypeEnum.textNotEqual: + return textEqual; + case DateCompareTypeEnum.Q1: + return dateQ1; + case DateCompareTypeEnum.Q2: + return dateQ2; + case DateCompareTypeEnum.Q3: + return dateQ3; + case DateCompareTypeEnum.Q4: + return dateQ4; + case DateCompareTypeEnum.M1: + return dateM1; + case DateCompareTypeEnum.M2: + return dateM2; + case DateCompareTypeEnum.M3: + return dateM3; + case DateCompareTypeEnum.M4: + return dateM4; + case DateCompareTypeEnum.M5: + return dateM5; + case DateCompareTypeEnum.M6: + return dateM6; + case DateCompareTypeEnum.M7: + return dateM7; + case DateCompareTypeEnum.M8: + return dateM8; + case DateCompareTypeEnum.M9: + return dateM9; + case DateCompareTypeEnum.M10: + return dateM10; + case DateCompareTypeEnum.M11: + return dateM11; + case DateCompareTypeEnum.M12: + return dateM12; + case DateGroupCompareTypeEnum.year: + return groupByYear; + case DateGroupCompareTypeEnum.month: + return groupByMonth; + case DateGroupCompareTypeEnum.day: + return groupByDay; + case DateGroupCompareTypeEnum.hour: + return groupByHour; + case DateGroupCompareTypeEnum.minute: + return groupByMinute; + } + return () => true; +}; + diff --git a/packages/condition/src/condition/logicGenerator.ts b/packages/condition/src/condition/logicGenerator.ts new file mode 100644 index 0000000000..7be0b47cfe --- /dev/null +++ b/packages/condition/src/condition/logicGenerator.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +type LogicFuncParams = (...args) => boolean; +export const logicAnd = (...args: LogicFuncParams[]) => { + +}; + +export const logicOr = () => { + +}; + +export const logicNot = () => { + +}; diff --git a/packages/condition/src/condition/textFuncGenerator.ts b/packages/condition/src/condition/textFuncGenerator.ts new file mode 100644 index 0000000000..626d0ad747 --- /dev/null +++ b/packages/condition/src/condition/textFuncGenerator.ts @@ -0,0 +1,56 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const textEqual = (compareValue: string, expectedValue: string) => { + return compareValue === expectedValue; +}; + +export const textNotEqual = (compareValue: string, expectedValue: string) => { + return compareValue !== expectedValue; +}; + +export const textContain = (compareValue: string, expectedValue: string) => { + return compareValue.includes(expectedValue); +}; + +export const textNotContain = (compareValue: string, expectedValue: string) => { + return !compareValue.includes(expectedValue); +}; + +export const textStartWith = (compareValue: string, expectedValue: string) => { + return compareValue.startsWith(expectedValue); +}; + +export const textEndWith = (compareValue: string, expectedValue: string) => { + return compareValue.endsWith(expectedValue); +}; + +export const textMatch = (compareValue: string, expectedValue: string) => { + return new RegExp(expectedValue).test(compareValue); +}; + +export const textNotMatch = (compareValue: string, expectedValue: string) => { + return !new RegExp(expectedValue).test(compareValue); +}; + +export const textEmpty = (compareValue: string) => { + return compareValue === ''; +}; + +export const textNotEmpty = (compareValue: string) => { + return compareValue !== ''; +}; + diff --git a/packages/condition/src/condition/topN.ts b/packages/condition/src/condition/topN.ts new file mode 100644 index 0000000000..bbca89bad2 --- /dev/null +++ b/packages/condition/src/condition/topN.ts @@ -0,0 +1,265 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @description Represents a heap data structure. + * A heap is a complete binary tree that satisfies the heap property. + * The heap property states that the parent node is always smaller or bigger than its children. + * The root node is the smallest or biggest element in the heap. + * The heap is used to find the kth largest or smallest element in a list. + * A array can be used to represent a heap. + * The root node is at index 0, and the left child of a node at index i is at index 2i + 1. + * The right child of a node at index i is at index 2i + 2. + * The parent node of a node at index i is at index (i - 1) / 2. + * @example + * const maxHeap = [5, 3, 4, 1, 2]; + * // const minHeap = [6, 8, 7, 10, 9]; + * // the root node in maxHeap is 5 + * // the root child is 3, 4, then the child of 3 is 1, 2 + */ +class Heap { + heap: number[]; + + /** + * Initializes a new instance of the Heap class. + */ + constructor() { + this.heap = []; + } + + /** + * Swaps the elements at the given indices in the heap. + * @param index1 The index of the first element. + * @param index2 The index of the second element. + */ + swap(index1: number, index2: number) { + const temp = this.heap[index1]; + this.heap[index1] = this.heap[index2]; + this.heap[index2] = temp; + } + + /** + * Returns the index of the parent node for the given index. + * @param index The index of the node. + * @returns The index of the parent node. + */ + getParentIndex(index: number) { + return Math.floor((index - 1) / 2); + } + + /** + * Returns the index of the left child node for the given index. + * @param index The index of the node. + * @returns The index of the left child node. + */ + getLeftIndex(index: number) { + return index * 2 + 1; + } + + /** + * Returns the index of the right child node for the given index. + * @param index The index of the node. + * @returns The index of the right child node. + */ + getRightIndex(index: number) { + return index * 2 + 2; + } + + /** + * Returns the number of elements in the heap. + * @returns The number of elements in the heap. + */ + size(): number { + return this.heap.length; + } + + /** + * Returns the minimum value in the heap without removing it. + * @returns The minimum value in the heap. + */ + peek(): number { + return this.heap[0]; + } + + /** + * @description Returns whether the heap includes the given value. + * @param {number} value The value to be checked. + * @returns {boolean} return true if the heap includes the given value + */ + include(value: number): boolean { + return this.heap.includes(value); + } +} + +/** + * @description Represents a min heap data structure. + * in MinHeap, the parent node is always smaller than its children. + * The root node is the smallest element in the heap. + * The min heap is used to find the kth largest element in a list. + */ +class MinHeap extends Heap { + /** + * Initializes a new instance of the MinHeap class. + */ + constructor() { + super(); + } + + /** + * Moves the element at the given index up the heap until it satisfies the min heap property. + * @param index The index of the element to be shifted up. + */ + shiftUp(index: number) { + if (index === 0) { + return; + } + const parentIndex = this.getParentIndex(index); + if (this.heap[parentIndex] > this.heap[index]) { + this.swap(parentIndex, index); + this.shiftUp(parentIndex); + } + } + + /** + * Moves the element at the given index down the heap until it satisfies the min heap property. + * @param index The index of the element to be shifted down. + */ + shiftDown(index: number) { + const leftIndex = this.getLeftIndex(index); + const rightIndex = this.getRightIndex(index); + if (this.heap[leftIndex] < this.heap[index]) { + this.swap(leftIndex, index); + this.shiftDown(leftIndex); + } + if (this.heap[rightIndex] < this.heap[index]) { + this.swap(rightIndex, index); + this.shiftDown(rightIndex); + } + } + + /** + * Inserts a new value into the min heap. + * @param value The value to be inserted. + */ + insert(value) { + this.heap.push(value); + this.shiftUp(this.heap.length - 1); + } + + /** + * Removes and returns the minimum value from the min heap. + */ + pop() { + this.heap[0] = this.heap.pop() as number; + this.shiftDown(0); + } +} + +/** + * Represents a max heap data structure. + */ +class MaxHeap extends Heap { + /** + * Initializes a new instance of the MaxHeap class. + */ + constructor() { + super(); + } + + /** + * Moves the element at the given index up the heap until it satisfies the max heap property. + * @param index The index of the element to be shifted up. + */ + shiftUp(index: number) { + if (index === 0) { + return; + } + const parentIndex = this.getParentIndex(index); + if (this.heap[parentIndex] < this.heap[index]) { + this.swap(parentIndex, index); + this.shiftUp(parentIndex); + } + } + + /** + * Moves the element at the given index down the heap until it satisfies the max heap property. + * @param index The index of the element to be shifted down. + */ + shiftDown(index: number) { + const leftIndex = this.getLeftIndex(index); + const rightIndex = this.getRightIndex(index); + if (this.heap[leftIndex] > this.heap[index]) { + this.swap(leftIndex, index); + this.shiftDown(leftIndex); + } + if (this.heap[rightIndex] > this.heap[index]) { + this.swap(rightIndex, index); + this.shiftDown(rightIndex); + } + } + + /** + * Inserts a new value into the max heap. + * @param value The value to be inserted. + */ + insert(value: number) { + this.heap.push(value); + this.shiftUp(this.heap.length - 1); + } + + /** + * Removes and returns the maximum value from the max heap. + */ + pop() { + this.heap[0] = this.heap.pop() as number; + this.shiftDown(0); + } +} + +/** + * Returns the kth largest element from the given list using a min heap. + * @param {number[]} list The list of numbers. + * @param {number} k The value of k. + * @returns The kth largest element. + */ +export const getLargestK = (list: number[], k: number) => { + const minHeap = new MinHeap(); + for (const item of list) { + minHeap.insert(item); + if (minHeap.size() > k) { + minHeap.pop(); + } + } + return minHeap.heap; +}; + +/** + * Returns the kth smallest element from the given list using a max heap. + * @param {number[]} list The list of numbers. + * @param {number} k The value of k. + * @returns The kth smallest element. + */ +export const getSmallestK = (list: number[], k: number) => { + const max = new MaxHeap(); + for (const item of list) { + max.insert(item); + if (max.size() > k) { + max.pop(); + } + } + return max.heap; +}; + diff --git a/packages/condition/src/condition/types.ts b/packages/condition/src/condition/types.ts new file mode 100644 index 0000000000..bdf87f96d9 --- /dev/null +++ b/packages/condition/src/condition/types.ts @@ -0,0 +1,362 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type CompareFunc = (compareValue: any, expectedValue: any) => boolean; +export type SqlFunc = () => string; +export type CompareType = DateCompareTypeEnum | TextCompareTypeEnum | NumberCompareTypeEnum | DynamicCompareTypeEnum | DateGroupCompareTypeEnum | LogicCompareTypeEnum; + +export enum DateCompareTypeEnum { + /** + * @description the same date + */ + dateEqual, + /** + * @description not the same date + */ + dateNotEqual, + /** + * @description old than expected date + */ + dateOlderThan, + /** + * @description old than or equal date + */ + dateOlderThanOrEqual, + /** + * @description before expected date + */ + dateNewerThan, + /** + * @description before or equal expected date + */ + dateNewerThanOrEqual, + /** + * @description a date is between two other expected date + */ + dateBetween, + /** + * @description a date is between two other expected date + */ + dateNotBetween, + /** + * @description The first quarter of a year + */ + Q1, + /** + * @description The second quarter of a year + */ + Q2, + /** + * @description The 3'th quarter of a year + */ + Q3, + /** + * @description The last quarter of a year + */ + Q4, + /** + * @description January + */ + M1, + /** + * @description February + */ + M2, + /** + * @description March + */ + M3, + /** + * @description April + */ + M4, + /** + * @description May + */ + M5, + /** + * @description June + */ + M6, + /** + * @description July + */ + M7, + /** + * @description August + */ + M8, + /** + * @description September + */ + M9, + /** + * @description October + */ + M10, + /** + * @description November + */ + M11, + /** + * @description December + */ + M12, + +} + +export enum TextCompareTypeEnum { + /** + * @description the string is equal to expect string + */ + textNotEqual = 'textNotEqual', + /** + * @description the string is equal to expect string + */ + textEqual = 'textEqual', + /** + * @description the string start with expected text + */ + BeginsWith = 'BeginsWith', + /** + * @description the string not start with expected text + */ + NotBeginsWith = 'NotBeginsWith', + /** + * @description the string end with expected text + */ + EndsWith = 'EndsWith', + /** + * @description the string not end with expected text + */ + NotEndsWith = 'NotEndsWith', + /** + * @description the string contain expected text + */ + Contains = 'Contains', + /** + * @description the string not contain expected text + */ + NotContains = 'NotContains', + +} + +export enum NumberCompareTypeEnum { + /** + *@description The given value is equal to the expected + */ + equal = 'equal', + /** + *@description a value is not equal to a expected value + */ + notEqual = 'notEqual', + /** + *@description a value is greater than to a expected value + */ + greaterThan = 'greaterThan', + /** + *@description a value is greater than or equal to a expected value + */ + greaterThanOrEqual = 'greaterThanOrEqual', + /** + *@description a value is less than a expected value + */ + lessThan = 'lessThan', + /** + *@description a value is less than than or equal to a expected value + */ + lessThanOrEqual = 'lessThanOrEqual', + /** + *@description closed interval , a value is greater than or equal to the smaller expected value, and less than or equal to the bigger value + */ + between = 'between', + /** + *@description open interval, a value is greater than or equal to the bigger expected value, and less than or equal to the smaller value + */ + notBetween = 'notBetween', +} + +export enum DynamicCompareTypeEnum { + /** + * @description the value is above the average value + */ + above = 'above', + /** + * @description the value is below the average value + */ + below = 'below', + /** + * @description the value is in the top 10 + */ + top10 = 'top10', + /** + *@description day after today + */ + tomorrow = 'tomorrow', + /** + *@description today + */ + today = 'today', + /** + *@description yesterday + */ + yesterday = 'yesterday', + /** + *@description next week + */ + nextWeek = 'nextWeek', + /** + *@description this week + */ + thisWeek = 'thisWeek', + /** + *@description last week + */ + lastWeek = 'lastWeek', + /** + *@description next month + */ + nextMonth = 'nextMonth', + /** + *@description this month + */ + thisMonth = 'thisMonth', + /** + *@description last month + */ + lastMonth = 'lastMonth', + /** + *@description next quarter + */ + nextQuarter = 'nextQuarte', + /** + *@description this quarter + */ + thisQuarter = 'thisQuarte', + /** + *@description last quarter + */ + lastQuarter = 'lastQuarte', + /** + *@description next year + */ + nextYear = 'nextYear', + /** + *@description this year + */ + thisYear = 'thisYear', + /** + *@description last year + */ + lastYear = 'lastYear', + /** + *@description the time area for this year begin to current + */ + yearToDate = 'yearToDate', +} + +export enum DateGroupCompareTypeEnum { + /** + * @description Group by day + */ + day = 'day', + /** + * @description Group by hour + */ + hour = 'hour', + /** + * @description Group by minute + */ + minute = 'minute', + /** + * @description Group by month + */ + month = 'month', + /** + * @description Group by second + */ + second = 'second', + /** + * @description Group by year + */ + year = 'year', + +} + +export enum LogicCompareTypeEnum { + /** + * @description return a opposite of expect condition + */ + not = 'not', + /** + * @description all expect conditions is true it will return true , otherwise it will be false + */ + and = 'and', + /** + * @description one of expect conditions is true , it will return true + */ + or = 'or', +} + +/** + * @description Represents the expected date grouping by day. + */ +export interface IDateGroupByDayExpected { + year: number; // The year value. + month: number; // The month value. + day: number; // The day value. + dateGroupCompareType: DateGroupCompareTypeEnum.day; // The date grouping type. +} + +/** + * @description Represents the expected date grouping by month. + */ +export interface IDateGroupByMonthExpected { + year: number; // The year value. + month: number; // The month value. + dateGroupCompareType: DateGroupCompareTypeEnum.month; // The date grouping type. +} + +/** + * @description Represents the expected date grouping by hour. + */ +export interface IDateGroupByHourExpected { + year: number; // The year value. + month: number; // The month value. + day: number; // The day value. + hour: number; // The hour value. + dateGroupCompareType: DateGroupCompareTypeEnum.hour; // The date grouping type. +} + +/** + * @description Represents the expected date grouping by minute. + */ +export interface IDateGroupByMinuteExpected { + year: number; // The year value. + month: number; // The month value. + day: number; // The day value. + hour: number; // The hour value. + minute: number; // The minute value. + dateGroupCompareType: DateGroupCompareTypeEnum.minute; // The date grouping type. +} + +/** + * @description Represents the expected date grouping by year. + */ +export interface IDateGroupByYearExpected { + year: number; // The year value. + dateGroupCompareType: DateGroupCompareTypeEnum.year; // The date grouping type. +} diff --git a/packages/condition/src/index.ts b/packages/condition/src/index.ts new file mode 100644 index 0000000000..53fcdcb950 --- /dev/null +++ b/packages/condition/src/index.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './condition/types'; +export * from './condition/funcGenerator'; +export * from './condition/topN'; +export * from './condition/dateGenerator'; diff --git a/packages/condition/src/sql/sqlStringGenerator.ts b/packages/condition/src/sql/sqlStringGenerator.ts new file mode 100644 index 0000000000..e9baaca9ec --- /dev/null +++ b/packages/condition/src/sql/sqlStringGenerator.ts @@ -0,0 +1,23 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { CompareType, SqlFunc } from '../condition/types'; + +export const compareFunctionGenerator = (compareType: CompareType): SqlFunc => { + return () => { + return ''; + }; +}; diff --git a/packages/condition/tsconfig.node.json b/packages/condition/tsconfig.node.json new file mode 100644 index 0000000000..e53dac8868 --- /dev/null +++ b/packages/condition/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": "@univerjs/shared/tsconfigs/node", + "include": ["vite.config.ts"] +} diff --git a/packages/condition/vite.config.ts b/packages/condition/vite.config.ts new file mode 100644 index 0000000000..67b2fff8f2 --- /dev/null +++ b/packages/condition/vite.config.ts @@ -0,0 +1,7 @@ +import createViteConfig from '@univerjs/shared/vite'; +import pkg from './package.json'; + +export default ({ mode }) => createViteConfig({}, { + mode, + pkg, +});