Browse Source

Merge pull request #2706 from nocodb/enhancement/formula

feat: WEEKDAY formula
pull/2756/head
mertmit 2 years ago committed by GitHub
parent
commit
81dc97f5dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 38
      packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue
  2. 12
      packages/nc-gui/helpers/dateFormatHelper.js
  3. 15
      packages/nc-gui/helpers/formulaList.js
  4. 4
      packages/noco-docs/content/en/setup-and-usages/formulas.md
  5. 1
      packages/nocodb-sdk/src/lib/formulaHelpers.ts
  6. 15
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mssql.ts
  7. 14
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mysql.ts
  8. 15
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts
  9. 16
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/sqlite.ts
  10. 23
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/formulaFnHelper.ts
  11. 25
      scripts/cypress/integration/common/3b_formula_column.js

38
packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue

@ -100,6 +100,7 @@ import jsep from 'jsep';
import { UITypes, jsepCurlyHook } from 'nocodb-sdk';
import { getUIDTIcon } from '~/components/project/spreadsheet/helpers/uiTypes';
import formulaList, { formulas, formulaTypes } from '@/helpers/formulaList';
import { validateDateWithUnknownFormat } from '@/helpers/dateFormatHelper';
import { getWordUntilCaret, insertAtCursor } from '@/helpers';
import NcAutocompleteTree from '@/helpers/NcAutocompleteTree';
@ -108,7 +109,6 @@ export default {
props: ['nodes', 'column', 'meta', 'isSQLite', 'alias', 'value', 'sqlUi'],
data: () => ({
formula: {},
// formulas: ['AVERAGE()', 'COUNT()', 'COUNTA()', 'COUNTALL()', 'SUM()', 'MIN()', 'MAX()', 'AND()', 'OR()', 'TRUE()', 'FALSE()', 'NOT()', 'XOR()', 'ISERROR()', 'IF()', 'LEN()', 'MID()', 'LEFT()', 'RIGHT()', 'FIND()', 'CONCATENATE()', 'T()', 'VALUE()', 'ARRAYJOIN()', 'ARRAYUNIQUE()', 'ARRAYCOMPACT()', 'ARRAYFLATTEN()', 'ROUND()', 'ROUNDUP()', 'ROUNDDOWN()', 'INT()', 'EVEN()', 'ODD()', 'MOD()', 'LOG()', 'EXP()', 'POWER()', 'SQRT()', 'CEILING()', 'FLOOR()', 'ABS()', 'RECORD_ID()', 'CREATED_TIME()', 'ERROR()', 'BLANK()', 'YEAR()', 'MONTH()', 'DAY()', 'HOUR()', 'MINUTE()', 'SECOND()', 'TODAY()', 'NOW()', 'WORKDAY()', 'DATETIME_PARSE()', 'DATETIME_FORMAT()', 'SET_LOCALE()', 'SET_TIMEZONE()', 'DATESTR()', 'TIMESTR()', 'TONOW()', 'FROMNOW()', 'DATEADD()', 'WEEKDAY()', 'WEEKNUM()', 'DATETIME_DIFF()', 'WORKDAY_DIFF()', 'IS_BEFORE()', 'IS_SAME()', 'IS_AFTER()', 'REPLACE()', 'REPT()', 'LOWER()', 'UPPER()', 'TRIM()', 'SUBSTITUTE()', 'SEARCH()', 'SWITCH()', 'LAST_MODIFIED_TIME()', 'ENCODE_URL_COMPONENT()', 'REGEX_EXTRACT()', 'REGEX_MATCH()', 'REGEX_REPLACE()']
availableFunctions: formulaList,
availableBinOps: ['+', '-', '*', '/', '>', '<', '==', '<=', '>=', '!='],
autocomplete: false,
@ -260,7 +260,39 @@ export default {
if (parsedTree.callee.type === jsep.IDENTIFIER) {
const expectedType = formulas[parsedTree.callee.name].type;
if (expectedType === formulaTypes.NUMERIC) {
parsedTree.arguments.map(arg => this.validateAgainstType(arg, expectedType, null, typeErrors));
if (parsedTree.callee.name === 'WEEKDAY') {
// parsedTree.arguments[0] = date
this.validateAgainstType(
parsedTree.arguments[0],
formulaTypes.DATE,
v => {
if (!validateDateWithUnknownFormat(v)) {
typeErrors.add('The first parameter of WEEKDAY() should have date value');
}
},
typeErrors
);
// parsedTree.arguments[1] = startDayOfWeek (optional)
this.validateAgainstType(
parsedTree.arguments[1],
formulaTypes.STRING,
v => {
if (
typeof v !== 'string' ||
!['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'].includes(
v.toLowerCase()
)
) {
typeErrors.add(
'The second parameter of WEEKDAY() should have the value either "sunday", "monday", "tuesday", "wednesday", "thursday", "friday" or "saturday"'
);
}
},
typeErrors
);
} else {
parsedTree.arguments.map(arg => this.validateAgainstType(arg, expectedType, null, typeErrors));
}
} else if (expectedType === formulaTypes.DATE) {
if (parsedTree.callee.name === 'DATEADD') {
// parsedTree.arguments[0] = date
@ -268,7 +300,7 @@ export default {
parsedTree.arguments[0],
formulaTypes.DATE,
v => {
if (!(v instanceof Date)) {
if (!validateDateWithUnknownFormat(v)) {
typeErrors.add('The first parameter of DATEADD() should have date value');
}
},

12
packages/nc-gui/helpers/dateFormatHelper.js

@ -1,3 +1,7 @@
import dayjs from 'dayjs';
const customParseFormat = require('dayjs/plugin/customParseFormat');
dayjs.extend(customParseFormat);
export const dateFormat = [
'DD-MM-YYYY', 'MM-DD-YYYY', 'YYYY-MM-DD',
'DD/MM/YYYY', 'MM/DD/YYYY', 'YYYY/MM/DD',
@ -6,4 +10,12 @@ export const dateFormat = [
export function validateDateFormat(v) {
return dateFormat.includes(v)
}
export function validateDateWithUnknownFormat(v) {
let res = 0;
for (const format of dateFormat) {
res |= dayjs(v.toString(), format, true).isValid();
}
return res;
}

15
packages/nc-gui/helpers/formulaList.js

@ -471,6 +471,21 @@ const formulas = {
'URL("https://github.com/nocodb/nocodb")',
'URL({column1})'
]
},
WEEKDAY: {
type: formulaTypes.NUMERIC,
validation: {
args: {
min: 1,
max: 2,
}
},
description: 'Returns the day of the week as an integer between 0 and 6 inclusive starting from Monday by default',
syntax: 'WEEKDAY(date, [startDayOfWeek])',
examples: [
'WEEKDAY("2021-06-09")',
'WEEKDAY(NOW(), "sunday")'
]
}
}

4
packages/noco-docs/content/en/setup-and-usages/formulas.md

@ -90,6 +90,10 @@ Example: ({Column1} + ({Column2} * {Column3}) / (3 - $Column4$ ))
| | | `DATEADD(date, 1, 'month')` | Supposing {DATE_COL} is 2022-03-14 03:14. The result is 2022-04-14 03:14. | DateTime columns and negative values are supported. Example: `DATEADD(DATE_TIME_COL, -1, 'month')` |
| | | `DATEADD(date, 1, 'year')` | Supposing {DATE_COL} is 2022-03-14 03:14. The result is 2023-03-14 03:14. | DateTime columns and negative values are supported. Example: `DATEADD(DATE_TIME_COL, -1, 'year')` |
| | | `IF(NOW() < DATEADD(date,10,'day'), "true", "false")` | If the current date is less than {DATE_COL} plus 10 days, it returns true. Otherwise, it returns false. | DateTime columns and negative values are supported. |
| | | `IF(NOW() < DATEADD(date,10,'day'), "true", "false")` | If the current date is less than {DATE_COL} plus 10 days, it returns true. Otherwise, it returns false. | DateTime columns and negative values are supported. |
| **WEEKDAY** | `WEEKDAY(date, [startDayOfWeek])` | `WEEKDAY(NOW())` | If today is Monday, it returns 0 | Returns the day of the week as an integer between 0 and 6 inclusive starting from Monday by default. You can optionally change the start day of the week by specifying in the second argument |
| | | `WEEKDAY(NOW(), "sunday")` | If today is Monday, it returns 1 | Get the week day of NOW() with the first day set as sunday |
### Logical Operators
| Operator | Sample | Description |

1
packages/nocodb-sdk/src/lib/formulaHelpers.ts

@ -129,6 +129,7 @@ export function jsepTreeToFormula(node) {
'AVG',
'ADD',
'DATEADD',
'WEEKDAY',
'AND',
'OR',
'CONCAT',

15
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mssql.ts

@ -1,5 +1,7 @@
import dayjs from 'dayjs';
import { MapFnArgs } from '../mapFunctionName';
import commonFns from './commonFns';
import { getWeekdayByText } from '../helpers/formulaFnHelper';
const mssql = {
...commonFns,
@ -108,6 +110,19 @@ const mssql = {
END${colAlias}`
);
},
WEEKDAY: ({ fn, knex, pt, colAlias }: MapFnArgs) => {
// DATEPART(WEEKDAY, DATE): sunday = 1, monday = 2, ..., saturday = 7
// WEEKDAY() returns an index from 0 to 6 for Monday to Sunday
return knex.raw(
`(DATEPART(WEEKDAY, ${
pt.arguments[0].type === 'Literal'
? `'${dayjs(fn(pt.arguments[0])).format('YYYY-MM-DD')}'`
: fn(pt.arguments[0])
}) - 2 - ${getWeekdayByText(
pt?.arguments[1]?.value
)} % 7 + 7) % 7 ${colAlias}`
);
},
};
export default mssql;

14
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mysql.ts

@ -1,5 +1,7 @@
import dayjs from 'dayjs';
import { MapFnArgs } from '../mapFunctionName';
import commonFns from './commonFns';
import { getWeekdayByText } from '../helpers/formulaFnHelper';
const mysql2 = {
...commonFns,
@ -55,6 +57,18 @@ const mysql2 = {
END${colAlias}`
);
},
WEEKDAY: ({ fn, knex, pt, colAlias }: MapFnArgs) => {
// WEEKDAY() returns an index from 0 to 6 for Monday to Sunday
return knex.raw(
`(WEEKDAY(${
pt.arguments[0].type === 'Literal'
? `'${dayjs(fn(pt.arguments[0])).format('YYYY-MM-DD')}'`
: fn(pt.arguments[0])
}) - ${getWeekdayByText(
pt?.arguments[1]?.value
)} % 7 + 7) % 7 ${colAlias}`
);
},
};
export default mysql2;

15
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts

@ -1,5 +1,7 @@
import dayjs from 'dayjs';
import { MapFnArgs } from '../mapFunctionName';
import commonFns from './commonFns';
import { getWeekdayByText } from '../helpers/formulaFnHelper';
const pg = {
...commonFns,
@ -42,6 +44,19 @@ const pg = {
)}')::interval${colAlias}`
);
},
WEEKDAY: ({ fn, knex, pt, colAlias }: MapFnArgs) => {
// isodow: the day of the week as Monday (1) to Sunday (7)
// WEEKDAY() returns an index from 0 to 6 for Monday to Sunday
return knex.raw(
`(EXTRACT(ISODOW FROM ${
pt.arguments[0].type === 'Literal'
? `date '${dayjs(fn(pt.arguments[0])).format('YYYY-MM-DD')}'`
: fn(pt.arguments[0])
}) - 1 - ${getWeekdayByText(
pt?.arguments[1]?.value
)} % 7 + 7) % 7 ${colAlias}`
);
},
};
export default pg;

16
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/sqlite.ts

@ -1,5 +1,7 @@
import dayjs from 'dayjs';
import { MapFnArgs } from '../mapFunctionName';
import commonFns from './commonFns';
import { getWeekdayByText } from '../helpers/formulaFnHelper';
const sqlite3 = {
...commonFns,
@ -75,6 +77,20 @@ const sqlite3 = {
END${colAlias}`
);
},
WEEKDAY: ({ fn, knex, pt, colAlias }: MapFnArgs) => {
// strftime('%w', date) - day of week 0 - 6 with Sunday == 0
// WEEKDAY() returns an index from 0 to 6 for Monday to Sunday
return knex.raw(
`(strftime('%w', ${
pt.arguments[0].type === 'Literal'
? `'${dayjs(fn(pt.arguments[0])).format('YYYY-MM-DD')}'`
: fn(pt.arguments[0])
}) - 1 - ${getWeekdayByText(
pt?.arguments[1]?.value
)} % 7 + 7) % 7 ${colAlias}`
);
},
};
export default sqlite3;

23
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/formulaFnHelper.ts

@ -0,0 +1,23 @@
export function getWeekdayByText(v: string) {
return {
monday: 0,
tuesday: 1,
wednesday: 2,
thursday: 3,
friday: 4,
saturday: 5,
sunday: 6,
}[v?.toLowerCase() || 'monday'];
}
export function getWeekdayByIndex(idx: number): string {
return {
0: 'monday',
1: 'tuesday',
2: 'wednesday',
3: 'thursday',
4: 'friday',
5: 'saturday',
6: 'sunday',
}[idx || 0];
}

25
scripts/cypress/integration/common/3b_formula_column.js

@ -153,6 +153,8 @@ export const genTest = (apiType, dbType) => {
let RESULT_MATH_0 = [];
let RESULT_MATH_1 = [];
let RESULT_MATH_2 = [];
let RESULT_WEEKDAY_0 = [];
let RESULT_WEEKDAY_1 = [];
for (let i = 0; i < 10; i++) {
// CONCAT, LOWER, UPPER, TRIM
@ -184,6 +186,11 @@ export const genTest = (apiType, dbType) => {
Math.pow(cityId[i], 3) +
Math.sqrt(countryId[i])
);
// WEEKDAY: starts from Monday
RESULT_WEEKDAY_0[i] = 1;
// WEEKDAY: starts from Sunday
RESULT_WEEKDAY_1[i] = 2;
}
it("Formula: ADD, AVG, LEN", () => {
@ -194,9 +201,25 @@ export const genTest = (apiType, dbType) => {
rowValidation("NC_MATH_0", RESULT_MATH_0);
});
it("Formula: CONCAT, LOWER, UPPER, TRIM", () => {
it("Formula: WEEKDAY", () => {
editColumnByName(
"NC_MATH_0",
"NC_WEEKDAY_0",
`WEEKDAY("2022-07-19")`
);
rowValidation("NC_WEEKDAY_0", RESULT_WEEKDAY_0);
editColumnByName(
"NC_WEEKDAY_0",
"NC_WEEKDAY_1",
`WEEKDAY("2022-07-19", "sunday")`
);
rowValidation("NC_WEEKDAY_1", RESULT_WEEKDAY_1);
});
it("Formula: CONCAT, LOWER, UPPER, TRIM", () => {
editColumnByName(
"NC_WEEKDAY_1",
"NC_STR_1",
`CONCAT(UPPER({City}), LOWER({City}), TRIM(' trimmed '))`
);

Loading…
Cancel
Save