Browse Source

Merge branch 'develop' into feat/gui-v2-quick-import

pull/2795/head
Wing-Kam Wong 2 years ago
parent
commit
269b19bb44
  1. 18
      .all-contributorsrc
  2. 4
      README.md
  3. 8
      packages/nc-gui/components/project/auditTab/Audit.vue
  4. 38
      packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue
  5. 12
      packages/nc-gui/helpers/dateFormatHelper.js
  6. 15
      packages/nc-gui/helpers/formulaList.js
  7. 116
      packages/nc-gui/lang/fr.json
  8. 4
      packages/noco-docs/content/en/setup-and-usages/formulas.md
  9. 1
      packages/nocodb-sdk/src/lib/formulaHelpers.ts
  10. 1
      packages/nocodb/docker/index.js
  11. 2
      packages/nocodb/src/__tests__/graphql.test.ts
  12. 1
      packages/nocodb/src/__tests__/rest.test.ts
  13. 1
      packages/nocodb/src/__tests__/restv2.test.ts
  14. 15
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mssql.ts
  15. 14
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mysql.ts
  16. 15
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts
  17. 16
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/sqlite.ts
  18. 23
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/formulaFnHelper.ts
  19. 4
      packages/nocodb/src/lib/utils/projectAcl.ts
  20. 1
      packages/nocodb/src/lib/v1-legacy/nc.try.ts
  21. 2
      packages/nocodb/src/run/docker.ts
  22. 2
      packages/nocodb/src/run/dockerRunMysql.ts
  23. 2
      packages/nocodb/src/run/dockerRunPG.ts
  24. 2
      packages/nocodb/src/run/dockerRunPG_CyQuick.ts
  25. 2
      packages/nocodb/src/run/try.ts
  26. 1
      packages/nocodb/tests/mysql-sakila-db/02-mysql-sakila-insert-data.sql
  27. 25
      scripts/cypress/integration/common/3b_formula_column.js
  28. 6
      scripts/markdown/readme/languages/chinese.md

18
.all-contributorsrc

@ -819,6 +819,24 @@
"contributions": [ "contributions": [
"translation" "translation"
] ]
},
{
"login": "systemctls",
"name": "jinxm",
"avatar_url": "https://avatars.githubusercontent.com/u/37177191?v=4",
"profile": "https://github.com/systemctls",
"contributions": [
"code"
]
},
{
"login": "yohanboniface",
"name": "Yohan Boniface",
"avatar_url": "https://avatars.githubusercontent.com/u/146023?v=4",
"profile": "https://yohanboniface.me",
"contributions": [
"translation"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

4
README.md

@ -445,6 +445,10 @@ Our mission is to provide the most powerful no-code interface for databases whic
<td align="center"><a href="https://cornernewclub.fr"><img src="https://avatars.githubusercontent.com/u/56829191?v=4?s=100" width="100px;" alt=""/><br /><sub><b>quentin</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=QuentinDstl" title="Code">💻</a></td> <td align="center"><a href="https://cornernewclub.fr"><img src="https://avatars.githubusercontent.com/u/56829191?v=4?s=100" width="100px;" alt=""/><br /><sub><b>quentin</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=QuentinDstl" title="Code">💻</a></td>
<td align="center"><a href="http://cande.me"><img src="https://avatars.githubusercontent.com/u/5407915?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cande</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=cande1gut" title="Code">💻</a></td> <td align="center"><a href="http://cande.me"><img src="https://avatars.githubusercontent.com/u/5407915?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cande</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=cande1gut" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/seokjunjin"><img src="https://avatars.githubusercontent.com/u/46950889?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Seokjun Jin</b></sub></a><br /><a href="#translation-seokjunjin" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/seokjunjin"><img src="https://avatars.githubusercontent.com/u/46950889?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Seokjun Jin</b></sub></a><br /><a href="#translation-seokjunjin" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/systemctls"><img src="https://avatars.githubusercontent.com/u/37177191?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jinxm</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=systemctls" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://yohanboniface.me"><img src="https://avatars.githubusercontent.com/u/146023?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yohan Boniface</b></sub></a><br /><a href="#translation-yohanboniface" title="Translation">🌍</a></td>
</tr> </tr>
</table> </table>

8
packages/nc-gui/components/project/auditTab/Audit.vue

@ -8,7 +8,7 @@
{{ $t('general.reload') }} {{ $t('general.reload') }}
</v-btn> </v-btn>
</v-toolbar> </v-toolbar>
<v-container class="h-100 d-flex flex-column"> <v-container class="d-flex flex-column tableScroll">
<v-simple-table v-if="audits" dense style="max-width: 1000px; overflow: auto" class="mx-auto flex-grow-1"> <v-simple-table v-if="audits" dense style="max-width: 1000px; overflow: auto" class="mx-auto flex-grow-1">
<thead> <thead>
<tr> <tr>
@ -101,4 +101,8 @@ export default {
}; };
</script> </script>
<style scoped></style> <style scoped lang="scss">
.tableScroll {
height: calc(90vh - 150px);
}
</style>

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 { UITypes, jsepCurlyHook } from 'nocodb-sdk';
import { getUIDTIcon } from '~/components/project/spreadsheet/helpers/uiTypes'; import { getUIDTIcon } from '~/components/project/spreadsheet/helpers/uiTypes';
import formulaList, { formulas, formulaTypes } from '@/helpers/formulaList'; import formulaList, { formulas, formulaTypes } from '@/helpers/formulaList';
import { validateDateWithUnknownFormat } from '@/helpers/dateFormatHelper';
import { getWordUntilCaret, insertAtCursor } from '@/helpers'; import { getWordUntilCaret, insertAtCursor } from '@/helpers';
import NcAutocompleteTree from '@/helpers/NcAutocompleteTree'; import NcAutocompleteTree from '@/helpers/NcAutocompleteTree';
@ -108,7 +109,6 @@ export default {
props: ['nodes', 'column', 'meta', 'isSQLite', 'alias', 'value', 'sqlUi'], props: ['nodes', 'column', 'meta', 'isSQLite', 'alias', 'value', 'sqlUi'],
data: () => ({ data: () => ({
formula: {}, 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, availableFunctions: formulaList,
availableBinOps: ['+', '-', '*', '/', '>', '<', '==', '<=', '>=', '!='], availableBinOps: ['+', '-', '*', '/', '>', '<', '==', '<=', '>=', '!='],
autocomplete: false, autocomplete: false,
@ -260,7 +260,39 @@ export default {
if (parsedTree.callee.type === jsep.IDENTIFIER) { if (parsedTree.callee.type === jsep.IDENTIFIER) {
const expectedType = formulas[parsedTree.callee.name].type; const expectedType = formulas[parsedTree.callee.name].type;
if (expectedType === formulaTypes.NUMERIC) { 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) { } else if (expectedType === formulaTypes.DATE) {
if (parsedTree.callee.name === 'DATEADD') { if (parsedTree.callee.name === 'DATEADD') {
// parsedTree.arguments[0] = date // parsedTree.arguments[0] = date
@ -268,7 +300,7 @@ export default {
parsedTree.arguments[0], parsedTree.arguments[0],
formulaTypes.DATE, formulaTypes.DATE,
v => { v => {
if (!(v instanceof Date)) { if (!validateDateWithUnknownFormat(v)) {
typeErrors.add('The first parameter of DATEADD() should have date value'); 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 = [ export const dateFormat = [
'DD-MM-YYYY', 'MM-DD-YYYY', 'YYYY-MM-DD', 'DD-MM-YYYY', 'MM-DD-YYYY', 'YYYY-MM-DD',
'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) { export function validateDateFormat(v) {
return dateFormat.includes(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("https://github.com/nocodb/nocodb")',
'URL({column1})' '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")'
]
} }
} }

116
packages/nc-gui/lang/fr.json

@ -6,7 +6,7 @@
"close": "Fermer", "close": "Fermer",
"yes": "Oui", "yes": "Oui",
"no": "Non", "no": "Non",
"ok": "d'accord", "ok": "D'accord",
"and": "Et", "and": "Et",
"or": "Ou", "or": "Ou",
"add": "Ajouter", "add": "Ajouter",
@ -67,8 +67,8 @@
"columns": "Colonnes", "columns": "Colonnes",
"page": "Page", "page": "Page",
"pages": "Pages", "pages": "Pages",
"record": "Entrée", "record": "Ligne",
"records": "Entrées", "records": "Lignes",
"webhook": "Webhook", "webhook": "Webhook",
"webhooks": "Webhooks", "webhooks": "Webhooks",
"view": "Vue", "view": "Vue",
@ -89,7 +89,7 @@
"creator": "Créateur", "creator": "Créateur",
"editor": "Éditeur", "editor": "Éditeur",
"commenter": "Commentateur", "commenter": "Commentateur",
"viewer": "Spectateur" "viewer": "Lecture seule"
} }
}, },
"datatype": { "datatype": {
@ -104,44 +104,44 @@
"Collaborator": "Collaborateur", "Collaborator": "Collaborateur",
"Date": "Date", "Date": "Date",
"Year": "Année", "Year": "Année",
"Time": "Temps", "Time": "Heure",
"PhoneNumber": "Numéro de téléphone", "PhoneNumber": "Numéro de téléphone",
"Email": "Couriel", "Email": "Courriel",
"URL": "Lien URL", "URL": "URL",
"Number": "Nombre", "Number": "Nombre",
"Decimal": "Décimal", "Decimal": "Décimal",
"Currency": "Devise", "Currency": "Devise",
"Percent": "Pour cent", "Percent": "Pourcentage",
"Duration": "Durée", "Duration": "Durée",
"Rating": "Évaluation", "Rating": "Évaluation",
"Formula": "Formule", "Formula": "Formule",
"Rollup": "Synthèse", "Rollup": "Synthèse",
"Count": "Compteur", "Count": "Compteur",
"Lookup": "Chercher", "Lookup": "Lookup",
"DateTime": "Heure", "DateTime": "Date et heure",
"CreateTime": "Date de création", "CreateTime": "Date de création",
"LastModifiedTime": "Dernière modification", "LastModifiedTime": "Dernière modification",
"AutoNumber": "Numérotation automatique", "AutoNumber": "Numérotation automatique",
"Barcode": "Code barre", "Barcode": "Code-barres",
"Button": "Bouton", "Button": "Bouton",
"Password": "Mot de passe", "Password": "Mot de passe",
"relationProperties": { "relationProperties": {
"noAction": "Pas d'action", "noAction": "Pas d'action",
"cascade": "Chevauchement", "cascade": "Cascade",
"restrict": "Limité", "restrict": "Limiter",
"setNull": "Laisser vide", "setNull": "Laisser vide",
"setDefault": "Définir par defaut" "setDefault": "Définir comme défaut"
} }
}, },
"filterOperation": { "filterOperation": {
"isEqual": "est égale", "isEqual": "est égale",
"isNotEqual": "n'est pas égale", "isNotEqual": "n'est pas égale",
"isLike": "est comme", "isLike": "contient",
"isNot like": "N'est pas comme", "isNot like": "ne contient pas",
"isEmpty": "est vide", "isEmpty": "est vide",
"isNotEmpty": "n'est pas vide", "isNotEmpty": "n'est pas vide",
"isNull": "est null", "isNull": "est null",
"isNotNull": "est non nulle" "isNotNull": "est non null"
}, },
"title": { "title": {
"newProj": "Nouveau projet", "newProj": "Nouveau projet",
@ -152,7 +152,7 @@
"personalView": "Vue personnelle", "personalView": "Vue personnelle",
"appStore": "Magasin d'applications", "appStore": "Magasin d'applications",
"teamAndAuth": "Équipe & Authentification", "teamAndAuth": "Équipe & Authentification",
"rolesUserMgmt": "Rôles & Gestion utilisateurs", "rolesUserMgmt": "Gestion des utilisateurs & rôles",
"userMgmt": "Gestion des utilisateurs", "userMgmt": "Gestion des utilisateurs",
"apiTokenMgmt": "Gestion des jetons API", "apiTokenMgmt": "Gestion des jetons API",
"rolesMgmt": "Gestion des rôles", "rolesMgmt": "Gestion des rôles",
@ -170,9 +170,9 @@
"headCreateProject": "Créer un projet | Nocodb", "headCreateProject": "Créer un projet | Nocodb",
"headLogin": "Connexion | Nocodb", "headLogin": "Connexion | Nocodb",
"resetPassword": "Réinitialiser le mot de passe", "resetPassword": "Réinitialiser le mot de passe",
"teamAndSettings": "Team & Settings", "teamAndSettings": "Équipe & paramètres",
"apiDocs": "API Docs", "apiDocs": "API Docs",
"importFromAirtable": "Import From Airtable" "importFromAirtable": "Importer depuis Airtable"
}, },
"labels": { "labels": {
"notifyVia": "Notifier via", "notifyVia": "Notifier via",
@ -192,7 +192,7 @@
"port": "Numéro de port", "port": "Numéro de port",
"username": "Utilisateur", "username": "Utilisateur",
"password": "Mot de passe", "password": "Mot de passe",
"schemaName": "Schema name", "schemaName": "Nom du schéma",
"action": "Action", "action": "Action",
"actions": "Actions", "actions": "Actions",
"operation": "Opération", "operation": "Opération",
@ -215,7 +215,7 @@
"aggregateFunction": "Fonction agrégée", "aggregateFunction": "Fonction agrégée",
"dbCreateIfNotExists": "Base de données : la créer si elle n'existe pas", "dbCreateIfNotExists": "Base de données : la créer si elle n'existe pas",
"clientKey": "Clé client", "clientKey": "Clé client",
"clientCert": "Certificat Client", "clientCert": "Certificat client",
"serverCA": "Serveur d'authentification", "serverCA": "Serveur d'authentification",
"requriedCa": "Authentification requise", "requriedCa": "Authentification requise",
"requriedIdentity": "Identité requise", "requriedIdentity": "Identité requise",
@ -234,7 +234,7 @@
"followNocodb": "Suivre NocoDB" "followNocodb": "Suivre NocoDB"
}, },
"docReference": "Référence de document", "docReference": "Référence de document",
"selectUserRole": "Sélectionnez le rôle d'utilisateur", "selectUserRole": "Sélectionner le rôle d'utilisateur",
"childTable": "Table enfant", "childTable": "Table enfant",
"childColumn": "Colonne enfant", "childColumn": "Colonne enfant",
"onUpdate": "Mise à jour en cours", "onUpdate": "Mise à jour en cours",
@ -265,7 +265,7 @@
"translate": "Aider à la traduction", "translate": "Aider à la traduction",
"account": { "account": {
"authToken": "Copier le jeton d'authentification", "authToken": "Copier le jeton d'authentification",
"swagger": "APIS SWAGGER DOC", "swagger": "Doc Swagger de l'API",
"projInfo": "Copier les informations du projet", "projInfo": "Copier les informations du projet",
"themes": "Thèmes" "themes": "Thèmes"
}, },
@ -286,14 +286,14 @@
"newUser": "Nouvel utilisateur", "newUser": "Nouvel utilisateur",
"editUser": "Modifier l'utilisateur", "editUser": "Modifier l'utilisateur",
"deleteUser": "Supprimer l'utilisateur du projet", "deleteUser": "Supprimer l'utilisateur du projet",
"resendInvite": "Renvoyer une invitation par e-mail", "resendInvite": "Renvoyer une invitation par courriel",
"copyInviteURL": "Copier l'URL d'invitation", "copyInviteURL": "Copier l'URL d'invitation",
"newRole": "Nouveau rôle", "newRole": "Nouveau rôle",
"reloadRoles": "Actualiser les rôles", "reloadRoles": "Actualiser les rôles",
"nextPage": "Page suivante", "nextPage": "Page suivante",
"prevPage": "Page précédente", "prevPage": "Page précédente",
"nextRecord": "Prochain enregistrement", "nextRecord": "Ligne suivante",
"previousRecord": "Précédent enregistrement", "previousRecord": "Ligne précédente",
"copyApiURL": "Copier l'URL de l'API", "copyApiURL": "Copier l'URL de l'API",
"createTable": "Créer un tableau", "createTable": "Créer un tableau",
"refreshTable": "Actualiser le tableau", "refreshTable": "Actualiser le tableau",
@ -332,16 +332,16 @@
"copyUrl": "Copier le lien", "copyUrl": "Copier le lien",
"openTab": "Ouvrir nouvel onglet", "openTab": "Ouvrir nouvel onglet",
"iFrame": "Copier le code HTML intégré", "iFrame": "Copier le code HTML intégré",
"addWebhook": "Ajout de webhook", "addWebhook": "Ajout un webhook",
"newToken": "Ajout de nouveau jeton", "newToken": "Ajout un nouveau jeton",
"exportZip": "Exporter un zip", "exportZip": "Exporter un zip",
"importZip": "Importer un zip", "importZip": "Importer un zip",
"metaSync": "Synchroniser maintenant", "metaSync": "Synchroniser maintenant",
"settings": "Paramètres", "settings": "Paramètres",
"previewAs": "Aperçu", "previewAs": "Aperçu",
"resetReview": "Réinitiliser l'aperçu", "resetReview": "Réinitialiser l'aperçu",
"testDbConn": "Tester la connexion à la base de données", "testDbConn": "Tester la connexion à la base de données",
"removeDbFromEnv": "Supprimer la base de données de l'environement", "removeDbFromEnv": "Supprimer la base de données de l'environnement",
"editConnJson": "Éditer le JSON de connexion", "editConnJson": "Éditer le JSON de connexion",
"sponsorUs": "Nous Parrainer", "sponsorUs": "Nous Parrainer",
"sendEmail": "ENVOYER UN EMAIL" "sendEmail": "ENVOYER UN EMAIL"
@ -367,7 +367,7 @@
"sqlMigration": "Recharger des migrations", "sqlMigration": "Recharger des migrations",
"updateRestart": "Mettre à jour et redémarrer", "updateRestart": "Mettre à jour et redémarrer",
"cancelReturn": "Annuler et revenir en arrière", "cancelReturn": "Annuler et revenir en arrière",
"exportMetadata": "Exportez toutes les métadonnées des méta-tables vers le répertoire Meta.", "exportMetadata": "Exporter toutes les métadonnées des méta-tables vers le répertoire Meta.",
"importMetadata": "Importer toutes les métadonnées du répertoire Meta en méta-tables.", "importMetadata": "Importer toutes les métadonnées du répertoire Meta en méta-tables.",
"clearMetadata": "Effacer toutes les métadonnées des méta-tables.", "clearMetadata": "Effacer toutes les métadonnées des méta-tables.",
"clientKey": "Selectionner un fichier .key", "clientKey": "Selectionner un fichier .key",
@ -377,7 +377,7 @@
"placeholder": { "placeholder": {
"projName": "Saisir le nom du projet", "projName": "Saisir le nom du projet",
"password": { "password": {
"enter": "Saisie le mot de passe", "enter": "Saisir le mot de passe",
"current": "Mot de passe actuel", "current": "Mot de passe actuel",
"new": "Nouveau mot de passe", "new": "Nouveau mot de passe",
"save": "Enregistrer le mot de passe", "save": "Enregistrer le mot de passe",
@ -395,13 +395,13 @@
"msg": { "msg": {
"info": { "info": {
"footerInfo": "Lignes par page", "footerInfo": "Lignes par page",
"upload": "Sélectionnez un fichier à téléverser", "upload": "Sélectionner un fichier à téléverser",
"upload_sub": "ou glisser-déposer un fichier", "upload_sub": "ou glisser-déposer un fichier",
"excelSupport": "Pris en charge: .xls, .xlsx, .xlsm, .ods, .ots", "excelSupport": "Pris en charge: .xls, .xlsx, .xlsm, .ods, .ots",
"excelURL": "Entrez l'URL du fichier Excel", "excelURL": "Définir l'URL du fichier Excel",
"csvURL": "Enter CSV file URL", "csvURL": "Définir l'URL du CSV",
"footMsg": "Nombre de lignes à analyser pour déduire le type de données", "footMsg": "Nombre de lignes à analyser pour déduire le type de données",
"excelImport": "Les feuilles sont disponibles pour l'importation", "excelImport": "Les tableaux sont disponibles pour l'import",
"exportMetadata": "Voulez-vous exporter des métadonnées des méta-tables?", "exportMetadata": "Voulez-vous exporter des métadonnées des méta-tables?",
"importMetadata": "Voulez-vous importer des métadonnées des méta-tables?", "importMetadata": "Voulez-vous importer des métadonnées des méta-tables?",
"clearMetadata": "Voulez-vous effacer les métadonnées des méta-tables?", "clearMetadata": "Voulez-vous effacer les métadonnées des méta-tables?",
@ -412,12 +412,12 @@
"deleteProject": "Voulez-vous supprimer le projet ?", "deleteProject": "Voulez-vous supprimer le projet ?",
"shareBasePrivate": "Générer une base partagée en lecture seule", "shareBasePrivate": "Générer une base partagée en lecture seule",
"shareBasePublic": "Toute personne avec ce lien peut consulter", "shareBasePublic": "Toute personne avec ce lien peut consulter",
"userInviteNoSMTP": "On dirait que vous n'avez pas encore configuré Mailer!\nMerci de copier-coller le lien d'invitation ci-dessous et l'envoyer à", "userInviteNoSMTP": "On dirait que vous n'avez pas encore configuré Mailer! \\ n Merci de copier-coller le lien d'invitation ci-dessous et l'envoyer à",
"dragDropHide": "Glisser et déposer des champs ici pour masquer", "dragDropHide": "Glisser et déposer des champs ici pour les masquer",
"formInput": "Entrer le libelé du formulaire", "formInput": "Entrer le libellé du formulaire",
"formHelpText": "Ajouter du texte d'assitance", "formHelpText": "Ajouter un texte d'aide",
"onlyCreator": "Visible uniquement pour les créateurs", "onlyCreator": "Visible uniquement pour les créateurs",
"formDesc": "Ajouter un formulaire de description", "formDesc": "Ajouter une description du formulaire",
"beforeEnablePwd": "Restreindre l’accès à l’aide d’un mot de passe", "beforeEnablePwd": "Restreindre l’accès à l’aide d’un mot de passe",
"afterEnablePwd": "L’accès est restreint par un mot de passe", "afterEnablePwd": "L’accès est restreint par un mot de passe",
"privateLink": "Cette vue est partagée avec un lien privé", "privateLink": "Cette vue est partagée avec un lien privé",
@ -437,20 +437,20 @@
"personalView": "Seulement vous pouvez modifier la configuration de la vue. Les autres vues personnelles des collaborateurs sont cachées par défaut.", "personalView": "Seulement vous pouvez modifier la configuration de la vue. Les autres vues personnelles des collaborateurs sont cachées par défaut.",
"ownerDesc": "Peut ajouter / supprimer des créateurs. Et éditer des structures de base de données complètes et des champs.", "ownerDesc": "Peut ajouter / supprimer des créateurs. Et éditer des structures de base de données complètes et des champs.",
"creatorDesc": "Peut éditer complètement la structure de la base de données et les valeurs.", "creatorDesc": "Peut éditer complètement la structure de la base de données et les valeurs.",
"editorDesc": "Peut éditer des enregistrements mais ne peut pas modifier la structure de la base de données / des champs.", "editorDesc": "Peut éditer des lignes mais ne peut pas modifier la structure de la base de données / des champs.",
"commenterDesc": "Peut voir et commenter les archives mais ne peut rien éditer", "commenterDesc": "Peut voir et commenter les archives mais ne peut rien éditer",
"viewerDesc": "Peut voir les enregistrements mais ne peut rien éditer", "viewerDesc": "Peut voir les données mais ne peut rien éditer",
"addUser": "Ajouter un nouvel utilisateur", "addUser": "Ajouter un nouvel utilisateur",
"staticRoleInfo": "Les rôles définis du système ne peuvent pas être modifiés", "staticRoleInfo": "Les rôles définis sur le système ne peuvent pas être modifiés",
"exportZip": "Exporter le meta projet dans un fichier Zip et le télécharger.", "exportZip": "Exporter le meta projet dans un fichier Zip et le télécharger.",
"importZip": "Importer le fichier ZIP du meta projet et redémarrer.", "importZip": "Importer le fichier ZIP du meta projet et redémarrer.",
"importText": "Importer un projet NocoDB à partir d'un fichier ZIP de métadonnées", "importText": "Importer un projet NocoDB à partir d'un fichier ZIP de métadonnées",
"metaNoChange": "Aucun changement identifié", "metaNoChange": "Aucun changement identifié",
"sqlMigration": "Les migrations de schéma seront créées automatiquement. Créez une table et rafraîchissez cette page.", "sqlMigration": "Les migrations de schéma seront créées automatiquement. Créez une table et rafraîchissez cette page.",
"dbConnectionStatus": "Environnement validé", "dbConnectionStatus": "Environnement validé",
"dbConnected": "Connexion réussi", "dbConnected": "Connexion réussie",
"notifications": { "notifications": {
"no_new": "Pas de nouvelles notifications", "no_new": "Pas de nouvelle notification",
"clear": "Effacer" "clear": "Effacer"
}, },
"sponsor": { "sponsor": {
@ -459,14 +459,14 @@
}, },
"loginMsg": "Se connecter à NocoDB", "loginMsg": "Se connecter à NocoDB",
"passwordRecovery": { "passwordRecovery": {
"message_1": "Veuillez fournir l'adresse e-mail que vous avez utilisée lorsque vous vous êtes inscrit.", "message_1": "Veuillez fournir l'adresse mail que vous avez utilisée lorsque vous vous êtes inscrit.",
"message_2": "Nous vous enverrons un email avec un lien pour réinitialiser votre mot de passe.", "message_2": "Nous vous enverrons un courriel avec un lien pour réinitialiser votre mot de passe.",
"success": "Veuillez vérifier votre email pour réinitialiser le mot de passe" "success": "Veuillez vérifier votre email pour réinitialiser le mot de passe"
}, },
"signUp": { "signUp": {
"superAdmin": "Vous serez le 'super admin'", "superAdmin": "Vous serez le 'super admin'",
"alreadyHaveAccount": "Avez-vous déjà un compte ?", "alreadyHaveAccount": "Avez-vous déjà un compte ?",
"workEmail": "Saisir votre adresse email professionnel", "workEmail": "Saisir votre adresse mail professionnelle",
"enterPassword": "Saisir votre mot de passe", "enterPassword": "Saisir votre mot de passe",
"forgotPassword": "Mot de passe oublié ?", "forgotPassword": "Mot de passe oublié ?",
"dontHaveAccount": "Vous n'avez pas de compte ?" "dontHaveAccount": "Vous n'avez pas de compte ?"
@ -479,13 +479,13 @@
"calendar": "Ajouter une vue Calendrier" "calendar": "Ajouter une vue Calendrier"
}, },
"tablesMetadataInSync": "Les métadonnées de tables sont en synchronisation", "tablesMetadataInSync": "Les métadonnées de tables sont en synchronisation",
"addMultipleUsers": "Vous pouvez ajouter plusieurs courriels séparés de virgule (,)", "addMultipleUsers": "Vous pouvez ajouter plusieurs courriels séparés par des virgules (,)",
"enterTableName": "Entrez le nom de la table", "enterTableName": "Entrer le nom de la table",
"addDefaultColumns": "Ajouter des colonnes par défaut", "addDefaultColumns": "Ajouter des colonnes par défaut",
"tableNameInDb": "Nom de la table comme enregistré dans la base de données" "tableNameInDb": "Nom de la table tel qu'enregistré dans la base de données"
}, },
"error": { "error": {
"searchProject": "Votre recherche pour {search} n'a renvoyée aucun résultat", "searchProject": "Votre recherche pour {search} n'a renvoyé aucun résultat",
"invalidChar": "Caractère invalide dans le chemin du dossier.", "invalidChar": "Caractère invalide dans le chemin du dossier.",
"invalidDbCredentials": "Identifiants de base de données invalides.", "invalidDbCredentials": "Identifiants de base de données invalides.",
"unableToConnectToDb": "Connexion impossible à la base de données, merci de vérifier que la base de données est démarrée et accessible.", "unableToConnectToDb": "Connexion impossible à la base de données, merci de vérifier que la base de données est démarrée et accessible.",
@ -500,9 +500,9 @@
} }
}, },
"toast": { "toast": {
"exportMetadata": "Les métadonnées de projet sont exportée avec succès", "exportMetadata": "Les métadonnées de projet sont exportées avec succès",
"importMetadata": "Les métadonnées du projet sont importée avec succès", "importMetadata": "Les métadonnées du projet sont importées avec succès",
"clearMetadata": "Les métadonnées du projet sont effacée avec succès", "clearMetadata": "Les métadonnées du projet sont effacées avec succès",
"stopProject": "Projet arrêté avec succès", "stopProject": "Projet arrêté avec succès",
"startProject": "Projet démarré avec succès", "startProject": "Projet démarré avec succès",
"restartProject": "Projet redémarré avec succès", "restartProject": "Projet redémarré avec succès",
@ -510,7 +510,7 @@
"authToken": "Auth Token copié dans le presse-papier", "authToken": "Auth Token copié dans le presse-papier",
"projInfo": "Informations de projet copiées dans le presse-papier", "projInfo": "Informations de projet copiées dans le presse-papier",
"inviteUrlCopy": "URL d'invitation copiée dans le presse-papier", "inviteUrlCopy": "URL d'invitation copiée dans le presse-papier",
"createView": "Vue créé avec succès", "createView": "Vue créée avec succès",
"formEmailSMTP": "Veuillez activer le plugin SMTP dans l'App Store pour permettre la notification par courrier électronique", "formEmailSMTP": "Veuillez activer le plugin SMTP dans l'App Store pour permettre la notification par courrier électronique",
"collabView": "Vous êtes bien dans la vue collaborative", "collabView": "Vous êtes bien dans la vue collaborative",
"lockedView": "Vous êtes bien dans la vue vérouillée", "lockedView": "Vous êtes bien dans la vue vérouillée",

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, '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')` | | | | `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. |
| | | `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 ### Logical Operators
| Operator | Sample | Description | | Operator | Sample | Description |

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

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

1
packages/nocodb/docker/index.js

@ -2,6 +2,7 @@ const Noco = require("../build/main/lib/Noco").default;
const express = require('express'); const express = require('express');
const cors = require('cors'); const cors = require('cors');
const server = express(); const server = express();
server.enable('trust proxy');
server.use(cors()); server.use(cors());

2
packages/nocodb/src/__tests__/graphql.test.ts

@ -63,6 +63,8 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
this.timeout(10000); this.timeout(10000);
(async () => { (async () => {
const server = express(); const server = express();
server.enable('trust proxy');
server.use(await Noco.init({})); server.use(await Noco.init({}));
app = server; app = server;

1
packages/nocodb/src/__tests__/rest.test.ts

@ -81,6 +81,7 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
(async () => { (async () => {
const server = express(); const server = express();
server.enable('trust proxy');
server.use(await Noco.init()); server.use(await Noco.init());
app = server; app = server;

1
packages/nocodb/src/__tests__/restv2.test.ts

@ -95,6 +95,7 @@ describe('Noco v2 Tests', () => {
} catch {} } catch {}
const server = express(); const server = express();
server.enable('trust proxy');
server.use(await Noco.init()); server.use(await Noco.init());
app = server; app = server;

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 { MapFnArgs } from '../mapFunctionName';
import commonFns from './commonFns'; import commonFns from './commonFns';
import { getWeekdayByText } from '../helpers/formulaFnHelper';
const mssql = { const mssql = {
...commonFns, ...commonFns,
@ -108,6 +110,19 @@ const mssql = {
END${colAlias}` 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; 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 { MapFnArgs } from '../mapFunctionName';
import commonFns from './commonFns'; import commonFns from './commonFns';
import { getWeekdayByText } from '../helpers/formulaFnHelper';
const mysql2 = { const mysql2 = {
...commonFns, ...commonFns,
@ -55,6 +57,18 @@ const mysql2 = {
END${colAlias}` 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; 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 { MapFnArgs } from '../mapFunctionName';
import commonFns from './commonFns'; import commonFns from './commonFns';
import { getWeekdayByText } from '../helpers/formulaFnHelper';
const pg = { const pg = {
...commonFns, ...commonFns,
@ -42,6 +44,19 @@ const pg = {
)}')::interval${colAlias}` )}')::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; 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 { MapFnArgs } from '../mapFunctionName';
import commonFns from './commonFns'; import commonFns from './commonFns';
import { getWeekdayByText } from '../helpers/formulaFnHelper';
const sqlite3 = { const sqlite3 = {
...commonFns, ...commonFns,
@ -75,6 +77,20 @@ const sqlite3 = {
END${colAlias}` 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; 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];
}

4
packages/nocodb/src/lib/utils/projectAcl.ts

@ -11,6 +11,7 @@ export default {
// project // project
projectGet: true, projectGet: true,
projectList: true, projectList: true,
projectCost: true,
//table //table
tableList: true, tableList: true,
tableGet: true, tableGet: true,
@ -168,6 +169,7 @@ export default {
xcTableAndViewList: true, xcTableAndViewList: true,
xcVirtualTableList: true, xcVirtualTableList: true,
projectList: true, projectList: true,
projectCost: true,
PROJECT_READ_BY_WEB: true, PROJECT_READ_BY_WEB: true,
tableXcModelGet: true, tableXcModelGet: true,
@ -218,6 +220,7 @@ export default {
xcTableAndViewList: true, xcTableAndViewList: true,
xcVirtualTableList: true, xcVirtualTableList: true,
projectList: true, projectList: true,
projectCost: true,
PROJECT_READ_BY_WEB: true, PROJECT_READ_BY_WEB: true,
tableXcModelGet: true, tableXcModelGet: true,
@ -252,6 +255,7 @@ export default {
pluginUpdate: true, pluginUpdate: true,
projectCreate: true, projectCreate: true,
projectList: true, projectList: true,
projectCost: true,
handleAxiosCall: true, handleAxiosCall: true,
testConnection: true, testConnection: true,
projectCreateByWeb: true, projectCreateByWeb: true,

1
packages/nocodb/src/lib/v1-legacy/nc.try.ts

@ -8,6 +8,7 @@ import Noco from '../Noco';
export default async function (dbUrl): Promise<void> { export default async function (dbUrl): Promise<void> {
const server = express(); const server = express();
server.use(cors()); server.use(cors());
server.enable('trust proxy');
server.set('view engine', 'ejs'); server.set('view engine', 'ejs');

2
packages/nocodb/src/run/docker.ts

@ -5,6 +5,8 @@ import Noco from '../lib/Noco';
process.env.NC_VERSION = '0009044'; process.env.NC_VERSION = '0009044';
const server = express(); const server = express();
server.enable('trust proxy');
server.use( server.use(
cors({ cors({
exposedHeaders: 'xc-db-response', exposedHeaders: 'xc-db-response',

2
packages/nocodb/src/run/dockerRunMysql.ts

@ -5,6 +5,8 @@ import Noco from '../lib/Noco';
process.env.NC_VERSION = '0009044'; process.env.NC_VERSION = '0009044';
const server = express(); const server = express();
server.enable('trust proxy');
server.use( server.use(
cors({ cors({
exposedHeaders: 'xc-db-response', exposedHeaders: 'xc-db-response',

2
packages/nocodb/src/run/dockerRunPG.ts

@ -5,6 +5,8 @@ import Noco from '../lib/Noco';
process.env.NC_VERSION = '0009044'; process.env.NC_VERSION = '0009044';
const server = express(); const server = express();
server.enable('trust proxy');
server.use( server.use(
cors({ cors({
exposedHeaders: 'xc-db-response', exposedHeaders: 'xc-db-response',

2
packages/nocodb/src/run/dockerRunPG_CyQuick.ts

@ -5,6 +5,8 @@ import Noco from '../lib/Noco';
process.env.NC_VERSION = '0009044'; process.env.NC_VERSION = '0009044';
const server = express(); const server = express();
server.enable('trust proxy');
server.use( server.use(
cors({ cors({
exposedHeaders: 'xc-db-response', exposedHeaders: 'xc-db-response',

2
packages/nocodb/src/run/try.ts

@ -11,6 +11,8 @@ process.env.NC_DB = url;
(async () => { (async () => {
const server = express(); const server = express();
server.enable('trust proxy');
server.use(cors()); server.use(cors());
server.set('view engine', 'ejs'); server.set('view engine', 'ejs');
const app = new Noco(); const app = new Noco();

1
packages/nocodb/tests/mysql-sakila-db/02-mysql-sakila-insert-data.sql

@ -162,3 +162,4 @@ COMMIT;
SET SQL_MODE=@OLD_SQL_MODE; SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));

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_0 = [];
let RESULT_MATH_1 = []; let RESULT_MATH_1 = [];
let RESULT_MATH_2 = []; let RESULT_MATH_2 = [];
let RESULT_WEEKDAY_0 = [];
let RESULT_WEEKDAY_1 = [];
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
// CONCAT, LOWER, UPPER, TRIM // CONCAT, LOWER, UPPER, TRIM
@ -184,6 +186,11 @@ export const genTest = (apiType, dbType) => {
Math.pow(cityId[i], 3) + Math.pow(cityId[i], 3) +
Math.sqrt(countryId[i]) 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", () => { it("Formula: ADD, AVG, LEN", () => {
@ -194,9 +201,25 @@ export const genTest = (apiType, dbType) => {
rowValidation("NC_MATH_0", RESULT_MATH_0); rowValidation("NC_MATH_0", RESULT_MATH_0);
}); });
it("Formula: CONCAT, LOWER, UPPER, TRIM", () => { it("Formula: WEEKDAY", () => {
editColumnByName( editColumnByName(
"NC_MATH_0", "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", "NC_STR_1",
`CONCAT(UPPER({City}), LOWER({City}), TRIM(' trimmed '))` `CONCAT(UPPER({City}), LOWER({City}), TRIM(' trimmed '))`
); );

6
scripts/markdown/readme/languages/chinese.md

@ -226,7 +226,13 @@ docker-compose up -d
git clone https://github.com/nocodb/nocodb git clone https://github.com/nocodb/nocodb
cd nocodb cd nocodb
``` ```
## 构建SDK
```shell
cd packages/nocodb-sdk
npm install
npm run build
```
## 本地运行后端 ## 本地运行后端
```shell ```shell

Loading…
Cancel
Save