diff --git a/packages/nc-gui/components/cell/Checkbox.vue b/packages/nc-gui/components/cell/Checkbox.vue index 61fdeed407..94a87c5a0c 100644 --- a/packages/nc-gui/components/cell/Checkbox.vue +++ b/packages/nc-gui/components/cell/Checkbox.vue @@ -7,6 +7,7 @@ import { getMdiIcon, inject, parseProp, + useProject, useSelectedCellKeyupListener, } from '#imports' @@ -26,10 +27,7 @@ const emits = defineEmits() const active = inject(ActiveCellInj, ref(false)) -let vModel = $computed({ - get: () => !!props.modelValue && props.modelValue !== '0' && props.modelValue !== 0, - set: (val: boolean) => emits('update:modelValue', val), -}) +const { isMssql } = useProject() const column = inject(ColumnInj) @@ -48,6 +46,11 @@ const checkboxMeta = $computed(() => { } }) +let vModel = $computed({ + get: () => !!props.modelValue && props.modelValue !== '0' && props.modelValue !== 0, + set: (val: any) => emits('update:modelValue', isMssql(column?.value?.base_id) ? +val : val), +}) + function onClick(force?: boolean, event?: MouseEvent) { if ( (event?.target as HTMLElement)?.classList?.contains('nc-checkbox') || diff --git a/packages/nc-gui/components/cell/attachment/Image.vue b/packages/nc-gui/components/cell/attachment/Image.vue index 8c3669be0f..625096b106 100644 --- a/packages/nc-gui/components/cell/attachment/Image.vue +++ b/packages/nc-gui/components/cell/attachment/Image.vue @@ -1,4 +1,6 @@ + + diff --git a/packages/nc-gui/components/smartsheet/Form.vue b/packages/nc-gui/components/smartsheet/Form.vue index 6adeda9da5..05a26b2304 100644 --- a/packages/nc-gui/components/smartsheet/Form.vue +++ b/packages/nc-gui/components/smartsheet/Form.vue @@ -727,18 +727,20 @@ watch(view, (nextView) => { }, ]" > - + + +
{{ element.description }}
diff --git a/packages/nc-gui/components/smartsheet/TableDataCell.vue b/packages/nc-gui/components/smartsheet/TableDataCell.vue index b40604fd6b..a78d4751ff 100644 --- a/packages/nc-gui/components/smartsheet/TableDataCell.vue +++ b/packages/nc-gui/components/smartsheet/TableDataCell.vue @@ -11,7 +11,7 @@ provide(CurrentCellInj, el) diff --git a/packages/nc-gui/components/smartsheet/expanded-form/index.vue b/packages/nc-gui/components/smartsheet/expanded-form/index.vue index 0c1e5285c7..d1dc2d97f8 100644 --- a/packages/nc-gui/components/smartsheet/expanded-form/index.vue +++ b/packages/nc-gui/components/smartsheet/expanded-form/index.vue @@ -329,7 +329,7 @@ export default { -
@@ -343,7 +343,7 @@ export default { :active="true" @update:model-value="changedColumns.add(col.title)" /> -
+ diff --git a/packages/nc-gui/composables/useAttachment.ts b/packages/nc-gui/composables/useAttachment.ts index 009742da57..b5ccab8f0a 100644 --- a/packages/nc-gui/composables/useAttachment.ts +++ b/packages/nc-gui/composables/useAttachment.ts @@ -5,6 +5,7 @@ const useAttachment = () => { const getPossibleAttachmentSrc = (item: Record) => { const res: string[] = [] + if (item?.data) res.push(item.data) if (item?.path) res.push(`${appInfo.value.ncSiteUrl}/${item.path}`) if (item?.url) res.push(item.url) return res diff --git a/packages/nc-gui/lang/pt_BR.json b/packages/nc-gui/lang/pt_BR.json index 5e9ee29b93..27ba7fd2f5 100644 --- a/packages/nc-gui/lang/pt_BR.json +++ b/packages/nc-gui/lang/pt_BR.json @@ -698,7 +698,7 @@ "allowedSpecialCharList": "Lista de caracteres especiais permitidos" }, "invalidURL": "URL inválido", - "invalidEmail": "Invalid Email", + "invalidEmail": "E-mail inválido", "internalError": "Ocorreu algum erro interno", "templateGeneratorNotFound": "O Gerador de Modelos não pode ser encontrado!", "fileUploadFailed": "Falha no carregamento do ficheiro", diff --git a/packages/nc-gui/package-lock.json b/packages/nc-gui/package-lock.json index 5e1f791771..a1146155d2 100644 --- a/packages/nc-gui/package-lock.json +++ b/packages/nc-gui/package-lock.json @@ -110,7 +110,7 @@ } }, "../nocodb-sdk": { - "version": "0.106.0", + "version": "0.106.1", "license": "AGPL-3.0-or-later", "dependencies": { "axios": "^0.21.1", diff --git a/packages/nc-gui/pages/[projectType]/form/[viewId]/index/index.vue b/packages/nc-gui/pages/[projectType]/form/[viewId]/index/index.vue index b0b09b4a51..e67aeffe4a 100644 --- a/packages/nc-gui/pages/[projectType]/form/[viewId]/index/index.vue +++ b/packages/nc-gui/pages/[projectType]/form/[viewId]/index/index.vue @@ -146,7 +146,7 @@ const onDecode = async (scannedCodeValue: string) => {
-
+ {
-
+
diff --git a/packages/nc-gui/pages/[projectType]/form/[viewId]/index/survey.vue b/packages/nc-gui/pages/[projectType]/form/[viewId]/index/survey.vue index 90d850f719..1a696db7a9 100644 --- a/packages/nc-gui/pages/[projectType]/form/[viewId]/index/survey.vue +++ b/packages/nc-gui/pages/[projectType]/form/[viewId]/index/survey.vue @@ -283,7 +283,7 @@ onMounted(() => { />
-
+ { to make a line break
- +
diff --git a/packages/nc-lib-gui/package.json b/packages/nc-lib-gui/package.json index 77f00b9a2b..86bb3f89ca 100644 --- a/packages/nc-lib-gui/package.json +++ b/packages/nc-lib-gui/package.json @@ -1,6 +1,6 @@ { "name": "nc-lib-gui", - "version": "0.106.0", + "version": "0.106.1", "description": "NocoDB GUI", "author": { "name": "NocoDB", diff --git a/packages/noco-docs/content/en/developer-resources/rest-apis.md b/packages/noco-docs/content/en/developer-resources/rest-apis.md index 328932159d..b2e8aa693b 100644 --- a/packages/noco-docs/content/en/developer-resources/rest-apis.md +++ b/packages/noco-docs/content/en/developer-resources/rest-apis.md @@ -12,7 +12,7 @@ Once you've created the schemas, you can manipulate the data or invoke actions u Here's the overview of all APIs. For the details, please check out NocoDB API Documentation. -You may also interact with the API's resources via Swagger UI. +You may also interact with the API's resources via Swagger UI. Currently, the default value for {orgs} is noco. Users will be able to change it in the future release. diff --git a/packages/noco-docs/content/en/developer-resources/sdk.md b/packages/noco-docs/content/en/developer-resources/sdk.md index 53a5efee91..fa4a94ab22 100644 --- a/packages/noco-docs/content/en/developer-resources/sdk.md +++ b/packages/noco-docs/content/en/developer-resources/sdk.md @@ -9,7 +9,7 @@ menuTitle: 'NocoDB SDK' We provide SDK for users to integrate with their applications. Currently only SDK for Javascript is supported. -Note: The NocoDB SDK requires authorization token. If you haven't created one, please check out Accessing APIs for details. +Note: The NocoDB SDK requires authorization token. If you haven't created one, please check out Accessing APIs for details. ## SDK For Javascript @@ -57,7 +57,7 @@ const api = new Api({ Once you have configured `api`, you can call different types of APIs by `api..`. -For Tag and FunctionName, please check out the API table here. +For Tag and FunctionName, please check out the API table here. #### Example: Calling API - /api/v1/db/meta/projects/{projectId}/tables diff --git a/packages/noco-docs/content/en/getting-started/upgrading.md b/packages/noco-docs/content/en/getting-started/upgrading.md index 9ed66e642d..1eecf2bfd7 100644 --- a/packages/noco-docs/content/en/getting-started/upgrading.md +++ b/packages/noco-docs/content/en/getting-started/upgrading.md @@ -8,7 +8,7 @@ link: https://codesandbox.io/embed/vigorous-firefly-80kq5?hidenavigation=1&theme --- By default, if `NC_DB` is not specified upon -installation, then SQLite will be used to store metadata. We suggest users to separate the metadata and user data in different databases as pictured in our architecture. +installation, then SQLite will be used to store metadata. We suggest users to separate the metadata and user data in different databases as pictured in our architecture. ## Docker diff --git a/packages/noco-docs/content/en/index.md b/packages/noco-docs/content/en/index.md index d79a6c3020..4e1b575901 100644 --- a/packages/noco-docs/content/en/index.md +++ b/packages/noco-docs/content/en/index.md @@ -31,7 +31,7 @@ Also NocoDB's app store allows you to build business workflows on views with com ### App Store for Workflow Automations -We provide different integrations in three main categories. See App Store for details. +We provide different integrations in three main categories. See App Store for details. - ⚡  Chat : Slack, Discord, Mattermost, and etc - ⚡  Email : AWS SES, SMTP, MailerSend, and etc @@ -46,11 +46,11 @@ We provide the following ways to let users to invoke actions in a programmatic w ### Sync Schema -We allow you to sync schema changes if you have made changes outside NocoDB GUI. However, it has to be noted then you will have to bring your own schema migrations for moving from environment to others. See Sync Schema for details. +We allow you to sync schema changes if you have made changes outside NocoDB GUI. However, it has to be noted then you will have to bring your own schema migrations for moving from environment to others. See Sync Schema for details. ### Audit -We are keeping all the user operation logs under one place. See Audit for details. +We are keeping all the user operation logs under one place. See Audit for details. ## Why are we building this? Most internet businesses equip themselves with either spreadsheet or a database to solve their business needs. Spreadsheets are used by a Billion+ humans collaboratively every single day. However, we are way off working at similar speeds on databases which are way more powerful tools when it comes to computing. Attempts to solve this with SaaS offerings has meant horrible access controls, vendor lockin, data lockin, abrupt price changes & most importantly a glass ceiling on what's possible in future. diff --git a/packages/noco-docs/content/en/setup-and-usages/meta-management.md b/packages/noco-docs/content/en/setup-and-usages/meta-management.md index 3c6825bf80..266e1155e6 100644 --- a/packages/noco-docs/content/en/setup-and-usages/meta-management.md +++ b/packages/noco-docs/content/en/setup-and-usages/meta-management.md @@ -28,7 +28,7 @@ To access it, click the down arrow button next to Project Name on the top left s ## Sync Metadata -Go to `Data Sources`, click ``Sync Metadata``, you can see your metadata sync status. If it is out of sync, you can sync the schema. See Sync Schema for more.0 +Go to `Data Sources`, click ``Sync Metadata``, you can see your metadata sync status. If it is out of sync, you can sync the schema. See Sync Schema for more. ![image](https://user-images.githubusercontent.com/35857179/219833485-3bcaa6ec-88bc-47cc-b938-5abb4835dc31.png) diff --git a/packages/noco-docs/content/en/setup-and-usages/table-operations.md b/packages/noco-docs/content/en/setup-and-usages/table-operations.md index e6245bdea3..a41449729b 100644 --- a/packages/noco-docs/content/en/setup-and-usages/table-operations.md +++ b/packages/noco-docs/content/en/setup-and-usages/table-operations.md @@ -155,7 +155,7 @@ You can use Quick Import when you have data from external sources such as Airtab ### Import Airtable into an Existing Project -- See here +- See here ### Import CSV data into an Existing Project @@ -165,7 +165,7 @@ You can use Quick Import when you have data from external sources such as Airtab - **Use First Row as Headers**: If it is checked, the first row will be treated as header row. - **Import Data**: If it is checked, all data will be imported. Otherwise, only table will be created. ![image](https://user-images.githubusercontent.com/35857179/197454479-1ed18dce-1d0b-4ee3-88b3-9b6a132dea2a.png) -- You can revise the table name by double clicking it, column name and column type. By default, the first column will be chosen as Display Value and cannot be deleted. +- You can revise the table name by double clicking it, column name and column type. By default, the first column will be chosen as Display Value and cannot be deleted. ![image](https://user-images.githubusercontent.com/35857179/197454633-5b30323e-2b13-4c55-843a-948c093d373e.png) - Click `Import` to start importing process. The table will be created and the data will be imported. ![image](https://user-images.githubusercontent.com/35857179/197455547-2d93df5e-a7f0-4c88-af53-990067625967.png) @@ -178,7 +178,7 @@ You can use Quick Import when you have data from external sources such as Airtab - **Use First Row as Headers**: If it is checked, the first row will be treated as header row. - **Import Data**: If it is checked, all data will be imported. Otherwise, only table will be created. ![image](https://user-images.githubusercontent.com/35857179/197455788-8dd8a7d1-38f3-48c3-a05e-6ab0cf25045c.png) -- You can revise the table name, column name and column type. By default, the first column will be chosen as Display Value and cannot be deleted. +- You can revise the table name, column name and column type. By default, the first column will be chosen as Display Value and cannot be deleted. Note: If your Excel file contains multiple sheets, each sheet will be stored in a separate table. diff --git a/packages/nocodb-sdk/package-lock.json b/packages/nocodb-sdk/package-lock.json index fd8dd25b31..0bbd833db3 100644 --- a/packages/nocodb-sdk/package-lock.json +++ b/packages/nocodb-sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "nocodb-sdk", - "version": "0.106.0", + "version": "0.106.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nocodb-sdk", - "version": "0.106.0", + "version": "0.106.1", "license": "AGPL-3.0-or-later", "dependencies": { "axios": "^0.21.1", diff --git a/packages/nocodb-sdk/package.json b/packages/nocodb-sdk/package.json index bbbc59a37d..006bf3d277 100644 --- a/packages/nocodb-sdk/package.json +++ b/packages/nocodb-sdk/package.json @@ -1,6 +1,6 @@ { "name": "nocodb-sdk", - "version": "0.106.0", + "version": "0.106.1", "description": "NocoDB SDK", "main": "build/main/index.js", "typings": "build/main/index.d.ts", diff --git a/packages/nocodb-sdk/src/lib/Api.ts b/packages/nocodb-sdk/src/lib/Api.ts index 2407419a29..cdcd4d5800 100644 --- a/packages/nocodb-sdk/src/lib/Api.ts +++ b/packages/nocodb-sdk/src/lib/Api.ts @@ -7905,7 +7905,7 @@ export class Api< */ dataCreate: ( sharedViewUuid: string, - data: object, + data: any, params: RequestParams = {} ) => this.request< @@ -7918,7 +7918,7 @@ export class Api< path: `/api/v1/db/public/shared-view/${sharedViewUuid}/rows`, method: 'POST', body: data, - type: ContentType.Json, + type: ContentType.FormData, format: 'json', ...params, }), diff --git a/packages/nocodb/docker-compose.yml b/packages/nocodb/docker-compose.yml index 7fabf799aa..0922422094 100644 --- a/packages/nocodb/docker-compose.yml +++ b/packages/nocodb/docker-compose.yml @@ -1,4 +1,4 @@ -version: "2.1" +version: "2.2" services: # db55: @@ -96,8 +96,8 @@ services: # - 5495:5432 # volumes: # - ./pg-sakila-db:/docker-entrypoint-initdb.d - pg96: - image: postgres:9.6 + pg147: + image: postgres:14.7 restart: always environment: POSTGRES_PASSWORD: password @@ -337,13 +337,13 @@ services: xc-test-pg: image: node:12.22.1-slim depends_on: - pg96: + pg147: condition: service_healthy volumes: - ./:/home/app environment: NODE_ENV: test - DATABASE_URL: postgres://postgres:password@pg96:5432/postgres + DATABASE_URL: postgres://postgres:password@pg147:5432/postgres command: - /bin/bash - -c @@ -405,13 +405,13 @@ services: xc-test-gql-pg: image: node:12.22.1-slim depends_on: - pg96: + pg147: condition: service_healthy volumes: - ./:/home/app environment: NODE_ENV: test - DATABASE_URL: postgres://postgres:password@pg96:5432/postgres + DATABASE_URL: postgres://postgres:password@pg147:5432/postgres command: - /bin/bash - -c diff --git a/packages/nocodb/package-lock.json b/packages/nocodb/package-lock.json index a9243323a7..ad88e5c453 100644 --- a/packages/nocodb/package-lock.json +++ b/packages/nocodb/package-lock.json @@ -1,12 +1,12 @@ { "name": "nocodb", - "version": "0.106.0", + "version": "0.106.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nocodb", - "version": "0.106.0", + "version": "0.106.1", "license": "AGPL-3.0-or-later", "dependencies": { "@google-cloud/storage": "^5.7.2", @@ -67,7 +67,7 @@ "mysql2": "^2.2.5", "nanoid": "^3.1.20", "nc-help": "0.2.87", - "nc-lib-gui": "0.106.0", + "nc-lib-gui": "0.106.1", "nc-plugin": "0.1.2", "ncp": "^2.0.0", "nocodb-sdk": "file:../nocodb-sdk", @@ -156,7 +156,7 @@ } }, "../nocodb-sdk": { - "version": "0.106.0", + "version": "0.106.1", "license": "AGPL-3.0-or-later", "dependencies": { "axios": "^0.21.1", @@ -11330,9 +11330,9 @@ } }, "node_modules/nc-lib-gui": { - "version": "0.106.0", - "resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.106.0.tgz", - "integrity": "sha512-BZDheNWKi+iKo5MfOKaxmYT6piNQ3K6SgrH3lH5PhTGzjN9dM+xs0Y+Wgo8r8rjNQj6pNEB17IuMZK3amCntxQ==", + "version": "0.106.1", + "resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.106.1.tgz", + "integrity": "sha512-Wt7nV6RuHpo3Kdhwryjquhwbut6ZoYrLvmCDUbMg4zBLLF0fyvGCqbUBjSh2cYO5Xpwq0wHPAHz3xGyIqy+Sag==", "dependencies": { "express": "^4.17.1" } @@ -28042,9 +28042,9 @@ } }, "nc-lib-gui": { - "version": "0.106.0", - "resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.106.0.tgz", - "integrity": "sha512-BZDheNWKi+iKo5MfOKaxmYT6piNQ3K6SgrH3lH5PhTGzjN9dM+xs0Y+Wgo8r8rjNQj6pNEB17IuMZK3amCntxQ==", + "version": "0.106.1", + "resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.106.1.tgz", + "integrity": "sha512-Wt7nV6RuHpo3Kdhwryjquhwbut6ZoYrLvmCDUbMg4zBLLF0fyvGCqbUBjSh2cYO5Xpwq0wHPAHz3xGyIqy+Sag==", "requires": { "express": "^4.17.1" } diff --git a/packages/nocodb/package.json b/packages/nocodb/package.json index b9e5dee05b..b8a8fad556 100644 --- a/packages/nocodb/package.json +++ b/packages/nocodb/package.json @@ -1,6 +1,6 @@ { "name": "nocodb", - "version": "0.106.0", + "version": "0.106.1", "description": "NocoDB Backend", "main": "dist/bundle.js", "author": { @@ -109,7 +109,7 @@ "mysql2": "^2.2.5", "nanoid": "^3.1.20", "nc-help": "0.2.87", - "nc-lib-gui": "0.106.0", + "nc-lib-gui": "0.106.1", "nc-plugin": "0.1.2", "ncp": "^2.0.0", "nocodb-sdk": "file:../nocodb-sdk", diff --git a/packages/nocodb/src/lib/cache/CacheMgr.ts b/packages/nocodb/src/lib/cache/CacheMgr.ts index 258b1ff090..4a2303c4b9 100644 --- a/packages/nocodb/src/lib/cache/CacheMgr.ts +++ b/packages/nocodb/src/lib/cache/CacheMgr.ts @@ -4,7 +4,13 @@ export default abstract class CacheMgr { public abstract del(key: string): Promise; public abstract getAll(pattern: string): Promise; public abstract delAll(scope: string, pattern: string): Promise; - public abstract getList(scope: string, list: string[]): Promise; + public abstract getList( + scope: string, + list: string[] + ): Promise<{ + list: any[]; + isNoneList: boolean; + }>; public abstract setList( scope: string, subListKeys: string[], diff --git a/packages/nocodb/src/lib/cache/NocoCache.ts b/packages/nocodb/src/lib/cache/NocoCache.ts index 381760fef0..25fed59ea3 100644 --- a/packages/nocodb/src/lib/cache/NocoCache.ts +++ b/packages/nocodb/src/lib/cache/NocoCache.ts @@ -56,8 +56,15 @@ export default class NocoCache { public static async getList( scope: string, subKeys: string[] - ): Promise { - if (this.cacheDisabled) return Promise.resolve([]); + ): Promise<{ + list: any[]; + isNoneList: boolean; + }> { + if (this.cacheDisabled) + return Promise.resolve({ + list: [], + isNoneList: false, + }); return this.client.getList(scope, subKeys); } diff --git a/packages/nocodb/src/lib/cache/RedisCacheMgr.ts b/packages/nocodb/src/lib/cache/RedisCacheMgr.ts index b8deaae118..5fa6f24b69 100644 --- a/packages/nocodb/src/lib/cache/RedisCacheMgr.ts +++ b/packages/nocodb/src/lib/cache/RedisCacheMgr.ts @@ -114,7 +114,13 @@ export default class RedisCacheMgr extends CacheMgr { ); } - async getList(scope: string, subKeys: string[]): Promise { + async getList( + scope: string, + subKeys: string[] + ): Promise<{ + list: any[]; + isNoneList: boolean; + }> { // remove null from arrays subKeys = subKeys.filter((k) => k); // e.g. key = nc:::::list @@ -125,9 +131,22 @@ export default class RedisCacheMgr extends CacheMgr { // e.g. arr = ["nc:::", "nc:::"] const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || []; log(`RedisCacheMgr::getList: getting list with key ${key}`); - return Promise.all( - arr.map(async (k) => await this.get(k, CacheGetType.TYPE_OBJECT)) - ); + + const isNoneList = arr.length && arr[0] === 'NONE'; + + if (isNoneList) { + return Promise.resolve({ + list: [], + isNoneList, + }); + } + + return { + list: await Promise.all( + arr.map(async (k) => await this.get(k, CacheGetType.TYPE_OBJECT)) + ), + isNoneList, + }; } async setList( @@ -144,8 +163,8 @@ export default class RedisCacheMgr extends CacheMgr { ? `${this.prefix}:${scope}:list` : `${this.prefix}:${scope}:${subListKeys.join(':')}:list`; if (!list.length) { - log(`RedisCacheMgr::setList: List is empty for ${listKey}. Skipping ...`); - return Promise.resolve(true); + // Set NONE here so that it won't hit the DB on each page load + return this.set(listKey, ['NONE']); } // fetch existing list const listOfGetKeys = @@ -225,7 +244,11 @@ export default class RedisCacheMgr extends CacheMgr { ? `${this.prefix}:${scope}:list` : `${this.prefix}:${scope}:${subListKeys.join(':')}:list`; log(`RedisCacheMgr::appendToList: append key ${key} to ${listKey}`); - const list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || []; + let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || []; + if (list.length && list[0] === 'NONE') { + list = []; + await this.del(listKey); + } list.push(key); return this.set(listKey, list); } diff --git a/packages/nocodb/src/lib/cache/RedisMockCacheMgr.ts b/packages/nocodb/src/lib/cache/RedisMockCacheMgr.ts index a06ddd9fe5..831d650429 100644 --- a/packages/nocodb/src/lib/cache/RedisMockCacheMgr.ts +++ b/packages/nocodb/src/lib/cache/RedisMockCacheMgr.ts @@ -2,6 +2,7 @@ import debug from 'debug'; import Redis from 'ioredis-mock'; import { CacheDelDirection, CacheGetType, CacheScope } from '../utils/globals'; import CacheMgr from './CacheMgr'; + const log = debug('nc:cache'); export default class RedisMockCacheMgr extends CacheMgr { @@ -102,10 +103,9 @@ export default class RedisMockCacheMgr extends CacheMgr { `RedisMockCacheMgr::delAll: deleting all keys with pattern ${this.prefix}:${scope}:${pattern}` ); await Promise.all( - keys.map( - async (k) => - await this.deepDel(scope, k, CacheDelDirection.CHILD_TO_PARENT) - ) + keys.map(async (k) => { + await this.deepDel(scope, k, CacheDelDirection.CHILD_TO_PARENT); + }) ); return Promise.all( keys.map(async (k) => { @@ -114,7 +114,13 @@ export default class RedisMockCacheMgr extends CacheMgr { ); } - async getList(scope: string, subKeys: string[]): Promise { + async getList( + scope: string, + subKeys: string[] + ): Promise<{ + list: any[]; + isNoneList: boolean; + }> { // remove null from arrays subKeys = subKeys.filter((k) => k); // e.g. key = nc:::::list @@ -125,9 +131,21 @@ export default class RedisMockCacheMgr extends CacheMgr { // e.g. arr = ["nc:::", "nc:::"] const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || []; log(`RedisMockCacheMgr::getList: getting list with key ${key}`); - return Promise.all( - arr.map(async (k) => await this.get(k, CacheGetType.TYPE_OBJECT)) - ); + const isNoneList = arr.length && arr[0] === 'NONE'; + + if (isNoneList) { + return Promise.resolve({ + list: [], + isNoneList, + }); + } + + return { + list: await Promise.all( + arr.map(async (k) => await this.get(k, CacheGetType.TYPE_OBJECT)) + ), + isNoneList, + }; } async setList( @@ -144,10 +162,8 @@ export default class RedisMockCacheMgr extends CacheMgr { ? `${this.prefix}:${scope}:list` : `${this.prefix}:${scope}:${subListKeys.join(':')}:list`; if (!list.length) { - log( - `RedisMockCacheMgr::setList: List is empty for ${listKey}. Skipping ...` - ); - return Promise.resolve(true); + // Set NONE here so that it won't hit the DB on each page load + return this.set(listKey, ['NONE']); } // fetch existing list const listOfGetKeys = @@ -227,7 +243,11 @@ export default class RedisMockCacheMgr extends CacheMgr { ? `${this.prefix}:${scope}:list` : `${this.prefix}:${scope}:${subListKeys.join(':')}:list`; log(`RedisMockCacheMgr::appendToList: append key ${key} to ${listKey}`); - const list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || []; + let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || []; + if (list.length && list[0] === 'NONE') { + list = []; + await this.del(listKey); + } list.push(key); return this.set(listKey, list); } diff --git a/packages/nocodb/src/lib/controllers/publicControllers/publicData.ctl.ts b/packages/nocodb/src/lib/controllers/publicControllers/publicData.ctl.ts index 76d708e241..d68089001d 100644 --- a/packages/nocodb/src/lib/controllers/publicControllers/publicData.ctl.ts +++ b/packages/nocodb/src/lib/controllers/publicControllers/publicData.ctl.ts @@ -31,6 +31,7 @@ async function dataInsert(req: Request & { files: any[] }, res: Response) { password: req.headers?.['xc-password'] as string, body: req.body?.data, siteUrl: (req as any).ncSiteUrl, + // req.files is enriched by multer files: req.files, }); @@ -95,11 +96,11 @@ router.post( ); router.get( - '/api/v1/db/public/shared-view/:sharedViewUuid/rows/:rowId/mm/:colId', + '/api/v1/db/public/shared-view/:sharedViewUuid/rows/:rowId/mm/:columnId', catchError(publicMmList) ); router.get( - '/api/v1/db/public/shared-view/:sharedViewUuid/rows/:rowId/hm/:colId', + '/api/v1/db/public/shared-view/:sharedViewUuid/rows/:rowId/hm/:columnId', catchError(publicHmList) ); diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts index 453af0f141..426445c431 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts @@ -2117,12 +2117,10 @@ class BaseModelSqlv2 { datas.map((d) => this.model.mapAliasToColumn(d)) ); - transaction = await this.dbDriver.transaction(); - - // await this.beforeUpdateb(updateDatas, transaction); const prevData = []; const newData = []; const updatePkValues = []; + const toBeUpdated = []; const res = []; for (const d of updateDatas) { await this.validate(d); @@ -2133,11 +2131,17 @@ class BaseModelSqlv2 { } prevData.push(await this.readByPk(pkValues)); const wherePk = await this._wherePk(pkValues); - await transaction(this.tnPath).update(d).where(wherePk); res.push(wherePk); + toBeUpdated.push({ d, wherePk }); updatePkValues.push(pkValues); } + transaction = await this.dbDriver.transaction(); + + for (const o of toBeUpdated) { + await transaction(this.tnPath).update(o.d).where(o.wherePk); + } + await transaction.commit(); for (const pkValues of updatePkValues) { diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts index 50fd306e94..a8332b01b2 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts @@ -157,11 +157,13 @@ const pg = { builder: args.knex.raw( `CASE WHEN ${args.knex .raw( - `${args.pt.arguments - .map(async (ar) => - (await args.fn(ar, '', 'OR')).builder.toQuery() + `${( + await Promise.all( + args.pt.arguments.map(async (ar) => + (await args.fn(ar, '', 'OR')).builder.toQuery() + ) ) - .join(' OR ')}` + ).join(' OR ')}` ) .wrap('(', ')') .toQuery()} THEN TRUE ELSE FALSE END ${args.colAlias}` diff --git a/packages/nocodb/src/lib/models/Base.ts b/packages/nocodb/src/lib/models/Base.ts index 709f6598c0..bfd29c18fb 100644 --- a/packages/nocodb/src/lib/models/Base.ts +++ b/packages/nocodb/src/lib/models/Base.ts @@ -150,10 +150,12 @@ export default class Base implements BaseType { args: { projectId: string }, ncMeta = Noco.ncMeta ): Promise { - let baseDataList = await NocoCache.getList(CacheScope.BASE, [ + const cachedList = await NocoCache.getList(CacheScope.BASE, [ args.projectId, ]); - if (!baseDataList.length) { + let { list: baseDataList } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !baseDataList.length) { baseDataList = await ncMeta.metaList2( args.projectId, null, diff --git a/packages/nocodb/src/lib/models/Column.ts b/packages/nocodb/src/lib/models/Column.ts index 5550142f2c..51b4e60d47 100644 --- a/packages/nocodb/src/lib/models/Column.ts +++ b/packages/nocodb/src/lib/models/Column.ts @@ -474,8 +474,12 @@ export default class Column implements ColumnType { }, ncMeta = Noco.ncMeta ): Promise { - let columnsList = await NocoCache.getList(CacheScope.COLUMN, [fk_model_id]); - if (!columnsList.length) { + const cachedList = await NocoCache.getList(CacheScope.COLUMN, [ + fk_model_id, + ]); + let { list: columnsList } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !columnsList.length) { columnsList = await ncMeta.metaList2(null, null, MetaTable.COLUMNS, { condition: { fk_model_id, @@ -627,8 +631,10 @@ export default class Column implements ColumnType { // get lookup columns and delete { - let lookups = await NocoCache.getList(CacheScope.COL_LOOKUP, [id]); - if (!lookups.length) { + const cachedList = await NocoCache.getList(CacheScope.COL_LOOKUP, [id]); + let { list: lookups } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !lookups.length) { lookups = await ncMeta.metaList2(null, null, MetaTable.COL_LOOKUP, { condition: { fk_lookup_column_id: id }, }); @@ -640,8 +646,10 @@ export default class Column implements ColumnType { // get rollup column and delete { - let rollups = await NocoCache.getList(CacheScope.COL_ROLLUP, [id]); - if (!rollups.length) { + const cachedList = await NocoCache.getList(CacheScope.COL_ROLLUP, [id]); + let { list: rollups } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !rollups.length) { rollups = await ncMeta.metaList2(null, null, MetaTable.COL_ROLLUP, { condition: { fk_rollup_column_id: id }, }); @@ -652,10 +660,12 @@ export default class Column implements ColumnType { } { - let formulaColumns = await NocoCache.getList(CacheScope.COLUMN, [ + const cachedList = await NocoCache.getList(CacheScope.COLUMN, [ col.fk_model_id, ]); - if (!formulaColumns.length) { + let { list: formulaColumns } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !formulaColumns.length) { formulaColumns = await ncMeta.metaList2(null, null, MetaTable.COLUMNS, { condition: { fk_model_id: col.fk_model_id, @@ -682,33 +692,43 @@ export default class Column implements ColumnType { // if relation column check lookup and rollup and delete if (col.uidt === UITypes.LinkToAnotherRecord) { - // get lookup columns using relation and delete - let lookups = await NocoCache.getList(CacheScope.COL_LOOKUP, [id]); - if (!lookups.length) { - lookups = await ncMeta.metaList2(null, null, MetaTable.COL_LOOKUP, { - condition: { fk_relation_column_id: id }, - }); - } - for (const lookup of lookups) { - await Column.delete(lookup.fk_column_id, ncMeta); + { + // get lookup columns using relation and delete + const cachedList = await NocoCache.getList(CacheScope.COL_LOOKUP, [id]); + let { list: lookups } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !lookups.length) { + lookups = await ncMeta.metaList2(null, null, MetaTable.COL_LOOKUP, { + condition: { fk_relation_column_id: id }, + }); + } + for (const lookup of lookups) { + await Column.delete(lookup.fk_column_id, ncMeta); + } } - // get rollup columns using relation and delete - let rollups = await NocoCache.getList(CacheScope.COL_ROLLUP, [id]); - if (!rollups.length) { - rollups = await ncMeta.metaList2(null, null, MetaTable.COL_ROLLUP, { - condition: { fk_relation_column_id: id }, - }); - } - for (const rollup of rollups) { - await Column.delete(rollup.fk_column_id, ncMeta); + { + // get rollup columns using relation and delete + const cachedList = await NocoCache.getList(CacheScope.COL_ROLLUP, [id]); + let { list: rollups } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !rollups.length) { + rollups = await ncMeta.metaList2(null, null, MetaTable.COL_ROLLUP, { + condition: { fk_relation_column_id: id }, + }); + } + for (const rollup of rollups) { + await Column.delete(rollup.fk_column_id, ncMeta); + } } } // delete sorts { - let sorts = await NocoCache.getList(CacheScope.SORT, [id]); - if (!sorts.length) { + const cachedList = await NocoCache.getList(CacheScope.SORT, [id]); + let { list: sorts } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !sorts.length) { sorts = await ncMeta.metaList2(null, null, MetaTable.SORT, { condition: { fk_column_id: id, @@ -721,8 +741,10 @@ export default class Column implements ColumnType { } // delete filters { - let filters = await NocoCache.getList(CacheScope.FILTER_EXP, [id]); - if (!filters.length) { + const cachedList = await NocoCache.getList(CacheScope.FILTER_EXP, [id]); + let { list: filters } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !filters.length) { filters = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP, { condition: { fk_column_id: id, diff --git a/packages/nocodb/src/lib/models/Filter.ts b/packages/nocodb/src/lib/models/Filter.ts index abb9b21b1a..54d5d3c4e6 100644 --- a/packages/nocodb/src/lib/models/Filter.ts +++ b/packages/nocodb/src/lib/models/Filter.ts @@ -326,10 +326,12 @@ export default class Filter implements FilterType { public async getChildren(ncMeta = Noco.ncMeta): Promise { if (this.children) return this.children; if (!this.is_group) return null; - let childFilters = await NocoCache.getList(CacheScope.FILTER_EXP, [ + const cachedList = await NocoCache.getList(CacheScope.FILTER_EXP, [ this.id, ]); - if (!childFilters.length) { + let { list: childFilters } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !childFilters.length) { childFilters = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP, { condition: { fk_parent_id: this.id, @@ -369,10 +371,12 @@ export default class Filter implements FilterType { }, ncMeta = Noco.ncMeta ): Promise { - let filters = await NocoCache.getList(CacheScope.FILTER_EXP, [ + const cachedList = await NocoCache.getList(CacheScope.FILTER_EXP, [ viewId || hookId, ]); - if (!filters.length) { + let { list: filters } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !filters.length) { filters = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP, { condition: viewId ? { fk_view_id: viewId } : { fk_hook_id: hookId }, orderBy: { @@ -480,8 +484,10 @@ export default class Filter implements FilterType { { viewId }: { viewId: any }, ncMeta = Noco.ncMeta ) { - let filterObjs = await NocoCache.getList(CacheScope.FILTER_EXP, [viewId]); - if (!filterObjs.length) { + const cachedList = await NocoCache.getList(CacheScope.FILTER_EXP, [viewId]); + let { list: filterObjs } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !filterObjs.length) { filterObjs = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP, { condition: { fk_view_id: viewId }, orderBy: { @@ -499,8 +505,10 @@ export default class Filter implements FilterType { { hookId }: { hookId: any }, ncMeta = Noco.ncMeta ) { - let filterObjs = await NocoCache.getList(CacheScope.FILTER_EXP, [hookId]); - if (!filterObjs.length) { + const cachedList = await NocoCache.getList(CacheScope.FILTER_EXP, [hookId]); + let { list: filterObjs } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !filterObjs.length) { filterObjs = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP, { condition: { fk_hook_id: hookId }, orderBy: { @@ -520,8 +528,12 @@ export default class Filter implements FilterType { }, ncMeta = Noco.ncMeta ) { - let filterObjs = await NocoCache.getList(CacheScope.FILTER_EXP, [parentId]); - if (!filterObjs.length) { + const cachedList = await NocoCache.getList(CacheScope.FILTER_EXP, [ + parentId, + ]); + let { list: filterObjs } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !filterObjs.length) { filterObjs = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP, { condition: { fk_parent_id: parentId, @@ -546,11 +558,13 @@ export default class Filter implements FilterType { }, ncMeta = Noco.ncMeta ) { - let filterObjs = await NocoCache.getList(CacheScope.FILTER_EXP, [ + const cachedList = await NocoCache.getList(CacheScope.FILTER_EXP, [ hookId, parentId, ]); - if (!filterObjs.length) { + let { list: filterObjs } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !filterObjs.length) { filterObjs = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP, { condition: { fk_parent_id: parentId, diff --git a/packages/nocodb/src/lib/models/FormViewColumn.ts b/packages/nocodb/src/lib/models/FormViewColumn.ts index 69795004d1..06ff209fda 100644 --- a/packages/nocodb/src/lib/models/FormViewColumn.ts +++ b/packages/nocodb/src/lib/models/FormViewColumn.ts @@ -100,13 +100,11 @@ export default class FormViewColumn implements FormColumnType { await NocoCache.set(`${CacheScope.FORM_VIEW_COLUMN}:${fk_column_id}`, id); // if cache is not present skip pushing it into the list to avoid unexpected behaviour - if ( - ( - await NocoCache.getList(CacheScope.FORM_VIEW_COLUMN, [ - column.fk_view_id, - ]) - )?.length - ) + const { list } = await NocoCache.getList(CacheScope.FORM_VIEW_COLUMN, [ + column.fk_view_id, + ]); + + if (list?.length) await NocoCache.appendToList( CacheScope.FORM_VIEW_COLUMN, [column.fk_view_id], @@ -119,10 +117,12 @@ export default class FormViewColumn implements FormColumnType { viewId: string, ncMeta = Noco.ncMeta ): Promise { - let viewColumns = await NocoCache.getList(CacheScope.FORM_VIEW_COLUMN, [ + const cachedList = await NocoCache.getList(CacheScope.FORM_VIEW_COLUMN, [ viewId, ]); - if (!viewColumns.length) { + let { list: viewColumns } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !viewColumns.length) { viewColumns = await ncMeta.metaList2( null, null, diff --git a/packages/nocodb/src/lib/models/GalleryViewColumn.ts b/packages/nocodb/src/lib/models/GalleryViewColumn.ts index 6e744e0281..d20bb881e5 100644 --- a/packages/nocodb/src/lib/models/GalleryViewColumn.ts +++ b/packages/nocodb/src/lib/models/GalleryViewColumn.ts @@ -79,13 +79,11 @@ export default class GalleryViewColumn { ); // if cache is not present skip pushing it into the list to avoid unexpected behaviour - if ( - ( - await NocoCache.getList(CacheScope.GALLERY_VIEW_COLUMN, [ - column.fk_view_id, - ]) - )?.length - ) + const { list } = await NocoCache.getList(CacheScope.GALLERY_VIEW_COLUMN, [ + column.fk_view_id, + ]); + + if (list?.length) await NocoCache.appendToList( CacheScope.GALLERY_VIEW_COLUMN, [column.fk_view_id], @@ -99,10 +97,12 @@ export default class GalleryViewColumn { viewId: string, ncMeta = Noco.ncMeta ): Promise { - let views = await NocoCache.getList(CacheScope.GALLERY_VIEW_COLUMN, [ + const cachedList = await NocoCache.getList(CacheScope.GALLERY_VIEW_COLUMN, [ viewId, ]); - if (!views.length) { + let { list: views } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !views.length) { views = await ncMeta.metaList2( null, null, diff --git a/packages/nocodb/src/lib/models/GridViewColumn.ts b/packages/nocodb/src/lib/models/GridViewColumn.ts index 1c81d33e34..8bd0497f56 100644 --- a/packages/nocodb/src/lib/models/GridViewColumn.ts +++ b/packages/nocodb/src/lib/models/GridViewColumn.ts @@ -24,8 +24,12 @@ export default class GridViewColumn implements GridColumnType { viewId: string, ncMeta = Noco.ncMeta ): Promise { - let views = await NocoCache.getList(CacheScope.GRID_VIEW_COLUMN, [viewId]); - if (!views.length) { + const cachedList = await NocoCache.getList(CacheScope.GRID_VIEW_COLUMN, [ + viewId, + ]); + let { list: views } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !views.length) { views = await ncMeta.metaList2(null, null, MetaTable.GRID_VIEW_COLUMNS, { condition: { fk_view_id: viewId, @@ -99,13 +103,10 @@ export default class GridViewColumn implements GridColumnType { await NocoCache.set(`${CacheScope.GRID_VIEW_COLUMN}:${fk_column_id}`, id); // if cache is not present skip pushing it into the list to avoid unexpected behaviour - if ( - ( - await NocoCache.getList(CacheScope.GRID_VIEW_COLUMN, [ - column.fk_view_id, - ]) - )?.length - ) + const { list } = await NocoCache.getList(CacheScope.GRID_VIEW_COLUMN, [ + column.fk_view_id, + ]); + if (list.length) await NocoCache.appendToList( CacheScope.GRID_VIEW_COLUMN, [column.fk_view_id], diff --git a/packages/nocodb/src/lib/models/Hook.ts b/packages/nocodb/src/lib/models/Hook.ts index 1b0e0761a3..aeef9e96a0 100644 --- a/packages/nocodb/src/lib/models/Hook.ts +++ b/packages/nocodb/src/lib/models/Hook.ts @@ -85,8 +85,12 @@ export default class Hook implements HookType { }, ncMeta = Noco.ncMeta ) { - let hooks = await NocoCache.getList(CacheScope.HOOK, [param.fk_model_id]); - if (!hooks.length) { + const cachedList = await NocoCache.getList(CacheScope.HOOK, [ + param.fk_model_id, + ]); + let { list: hooks } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !hooks.length) { hooks = await ncMeta.metaList(null, null, MetaTable.HOOKS, { condition: { fk_model_id: param.fk_model_id, diff --git a/packages/nocodb/src/lib/models/HookFilter.ts b/packages/nocodb/src/lib/models/HookFilter.ts index b9b66fb8f3..4f3bdb2488 100644 --- a/packages/nocodb/src/lib/models/HookFilter.ts +++ b/packages/nocodb/src/lib/models/HookFilter.ts @@ -205,10 +205,12 @@ export default class Filter { public async getChildren(ncMeta = Noco.ncMeta): Promise { if (this.children) return this.children; if (!this.is_group) return null; - let childFilters = await NocoCache.getList(CacheScope.FILTER_EXP, [ + const cachedList = await NocoCache.getList(CacheScope.FILTER_EXP, [ this.id, ]); - if (!childFilters.length) { + let { list: childFilters } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !childFilters.length) { childFilters = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP, { condition: { fk_parent_id: this.id, @@ -243,8 +245,10 @@ export default class Filter { }, ncMeta = Noco.ncMeta ): Promise { - let filters = await NocoCache.getList(CacheScope.FILTER_EXP, [viewId]); - if (!filters.length) { + const cachedList = await NocoCache.getList(CacheScope.FILTER_EXP, [viewId]); + let { list: filters } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !filters.length) { filters = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP, { condition: { fk_view_id: viewId }, }); @@ -327,8 +331,10 @@ export default class Filter { { viewId }: { viewId: any }, ncMeta = Noco.ncMeta ) { - let filterObjs = await NocoCache.getList(CacheScope.FILTER_EXP, [viewId]); - if (!filterObjs.length) { + const cachedList = await NocoCache.getList(CacheScope.FILTER_EXP, [viewId]); + let { list: filterObjs } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !filterObjs.length) { filterObjs = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP, { condition: { fk_view_id: viewId }, }); diff --git a/packages/nocodb/src/lib/models/KanbanViewColumn.ts b/packages/nocodb/src/lib/models/KanbanViewColumn.ts index 71e58954dd..cea673e14d 100644 --- a/packages/nocodb/src/lib/models/KanbanViewColumn.ts +++ b/packages/nocodb/src/lib/models/KanbanViewColumn.ts @@ -85,10 +85,12 @@ export default class KanbanViewColumn implements KanbanColumnType { viewId: string, ncMeta = Noco.ncMeta ): Promise { - let views = await NocoCache.getList(CacheScope.KANBAN_VIEW_COLUMN, [ + const cachedList = await NocoCache.getList(CacheScope.KANBAN_VIEW_COLUMN, [ viewId, ]); - if (!views.length) { + let { list: views } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !views.length) { views = await ncMeta.metaList2( null, null, diff --git a/packages/nocodb/src/lib/models/MapViewColumn.ts b/packages/nocodb/src/lib/models/MapViewColumn.ts index a5781bf1df..3d38af9e5d 100644 --- a/packages/nocodb/src/lib/models/MapViewColumn.ts +++ b/packages/nocodb/src/lib/models/MapViewColumn.ts @@ -67,10 +67,10 @@ export default class MapViewColumn { await NocoCache.set(`${CacheScope.MAP_VIEW_COLUMN}:${fk_column_id}`, id); // if cache is not present skip pushing it into the list to avoid unexpected behaviour - if ( - (await NocoCache.getList(CacheScope.MAP_VIEW_COLUMN, [column.fk_view_id])) - ?.length - ) + const { list } = await NocoCache.getList(CacheScope.MAP_VIEW_COLUMN, [ + column.fk_view_id, + ]); + if (list?.length) await NocoCache.appendToList( CacheScope.MAP_VIEW_COLUMN, [column.fk_view_id], @@ -84,8 +84,12 @@ export default class MapViewColumn { viewId: string, ncMeta = Noco.ncMeta ): Promise { - let views = await NocoCache.getList(CacheScope.MAP_VIEW_COLUMN, [viewId]); - if (!views.length) { + const cachedList = await NocoCache.getList(CacheScope.MAP_VIEW_COLUMN, [ + viewId, + ]); + let { list: views } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !views.length) { views = await ncMeta.metaList2(null, null, MetaTable.MAP_VIEW_COLUMNS, { condition: { fk_view_id: viewId, diff --git a/packages/nocodb/src/lib/models/Model.ts b/packages/nocodb/src/lib/models/Model.ts index bd69c4df9b..abfea84ef9 100644 --- a/packages/nocodb/src/lib/models/Model.ts +++ b/packages/nocodb/src/lib/models/Model.ts @@ -160,13 +160,10 @@ export default class Model implements TableType { }, ncMeta = Noco.ncMeta ): Promise { - let modelList = []; - if (base_id) { - await NocoCache.getList(CacheScope.MODEL, [project_id, base_id]); - } else { - await NocoCache.getList(CacheScope.MODEL, [project_id]); - } - if (!modelList.length) { + const cachedList = await NocoCache.getList(CacheScope.MODEL, [project_id]); + let { list: modelList } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !modelList.length) { modelList = await ncMeta.metaList2( project_id, base_id, @@ -183,15 +180,7 @@ export default class Model implements TableType { model.meta = parseMetaProp(model); } - if (base_id) { - await NocoCache.setList( - CacheScope.MODEL, - [project_id, base_id], - modelList - ); - } else { - await NocoCache.setList(CacheScope.MODEL, [project_id], modelList); - } + await NocoCache.setList(CacheScope.MODEL, [project_id], modelList); } modelList.sort( (a, b) => @@ -211,11 +200,13 @@ export default class Model implements TableType { }, ncMeta = Noco.ncMeta ): Promise { - let modelList = await NocoCache.getList(CacheScope.MODEL, [ + const cachedList = await NocoCache.getList(CacheScope.MODEL, [ project_id, db_alias, ]); - if (!modelList.length) { + let { list: modelList } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !modelList.length) { modelList = await ncMeta.metaList2( project_id, db_alias, diff --git a/packages/nocodb/src/lib/models/ModelRoleVisibility.ts b/packages/nocodb/src/lib/models/ModelRoleVisibility.ts index 7813a94171..724ecaf07b 100644 --- a/packages/nocodb/src/lib/models/ModelRoleVisibility.ts +++ b/packages/nocodb/src/lib/models/ModelRoleVisibility.ts @@ -24,10 +24,13 @@ export default class ModelRoleVisibility implements ModelRoleVisibilityType { } static async list(projectId): Promise { - let data = await NocoCache.getList(CacheScope.MODEL_ROLE_VISIBILITY, [ - projectId, - ]); - if (!data.length) { + const cachedList = await NocoCache.getList( + CacheScope.MODEL_ROLE_VISIBILITY, + [projectId] + ); + let { list: data } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !data.length) { data = await Noco.ncMeta.metaList2( projectId, null, diff --git a/packages/nocodb/src/lib/models/Plugin.ts b/packages/nocodb/src/lib/models/Plugin.ts index 5d88dc529e..1740853253 100644 --- a/packages/nocodb/src/lib/models/Plugin.ts +++ b/packages/nocodb/src/lib/models/Plugin.ts @@ -43,8 +43,10 @@ export default class Plugin implements PluginType { } static async list(ncMeta = Noco.ncMeta) { - let pluginList = await NocoCache.getList(CacheScope.PLUGIN, []); - if (!pluginList.length) { + const cachedList = await NocoCache.getList(CacheScope.PLUGIN, []); + let { list: pluginList } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !pluginList.length) { pluginList = await ncMeta.metaList2(null, null, MetaTable.PLUGIN); await NocoCache.setList(CacheScope.PLUGIN, [], pluginList); } diff --git a/packages/nocodb/src/lib/models/Project.ts b/packages/nocodb/src/lib/models/Project.ts index a26ac98a08..b0058dc1ef 100644 --- a/packages/nocodb/src/lib/models/Project.ts +++ b/packages/nocodb/src/lib/models/Project.ts @@ -79,8 +79,10 @@ export default class Project implements ProjectType { ncMeta = Noco.ncMeta ): Promise { // todo: pagination - let projectList = await NocoCache.getList(CacheScope.PROJECT, []); - if (!projectList.length) { + const cachedList = await NocoCache.getList(CacheScope.PROJECT, []); + let { list: projectList } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !projectList.length) { projectList = await ncMeta.metaList2(null, null, MetaTable.PROJECT, { xcCondition: { _or: [ diff --git a/packages/nocodb/src/lib/models/ProjectUser.ts b/packages/nocodb/src/lib/models/ProjectUser.ts index 1090a10c2e..c8171d045d 100644 --- a/packages/nocodb/src/lib/models/ProjectUser.ts +++ b/packages/nocodb/src/lib/models/ProjectUser.ts @@ -187,10 +187,12 @@ export default class ProjectUser { } // remove project from user project list cache - let cachedProjectList = await NocoCache.getList(CacheScope.USER_PROJECT, [ + const cachedList = await NocoCache.getList(CacheScope.USER_PROJECT, [ userId, ]); - if (cachedProjectList?.length) { + let { list: cachedProjectList } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && cachedProjectList?.length) { cachedProjectList = cachedProjectList.filter((p) => p.id !== projectId); await NocoCache.setList( CacheScope.USER_PROJECT, @@ -221,11 +223,13 @@ export default class ProjectUser { ncMeta = Noco.ncMeta ): Promise { // todo: pagination - let projectList = await NocoCache.getList(CacheScope.USER_PROJECT, [ + const cachedList = await NocoCache.getList(CacheScope.USER_PROJECT, [ userId, ]); + let { list: projectList } = cachedList; + const { isNoneList } = cachedList; - if (projectList.length) { + if (!isNoneList && projectList.length) { return projectList; } diff --git a/packages/nocodb/src/lib/models/SelectOption.ts b/packages/nocodb/src/lib/models/SelectOption.ts index 4575168375..b1523aabbc 100644 --- a/packages/nocodb/src/lib/models/SelectOption.ts +++ b/packages/nocodb/src/lib/models/SelectOption.ts @@ -69,10 +69,12 @@ export default class SelectOption implements SelectOptionType { } public static async read(fk_column_id: string, ncMeta = Noco.ncMeta) { - let options = await NocoCache.getList(CacheScope.COL_SELECT_OPTION, [ + const cachedList = await NocoCache.getList(CacheScope.COL_SELECT_OPTION, [ fk_column_id, ]); - if (!options.length) { + let { list: options } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !options.length) { options = await ncMeta.metaList2( null, //, null, //model.db_alias, diff --git a/packages/nocodb/src/lib/models/Sort.ts b/packages/nocodb/src/lib/models/Sort.ts index 375fa3d09e..7a020cde03 100644 --- a/packages/nocodb/src/lib/models/Sort.ts +++ b/packages/nocodb/src/lib/models/Sort.ts @@ -114,8 +114,10 @@ export default class Sort { ncMeta = Noco.ncMeta ): Promise { if (!viewId) return null; - let sortList = await NocoCache.getList(CacheScope.SORT, [viewId]); - if (!sortList.length) { + const cachedList = await NocoCache.getList(CacheScope.SORT, [viewId]); + let { list: sortList } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !sortList.length) { sortList = await ncMeta.metaList2(null, null, MetaTable.SORT, { condition: { fk_view_id: viewId }, orderBy: { diff --git a/packages/nocodb/src/lib/models/View.ts b/packages/nocodb/src/lib/models/View.ts index 2e7f9374f1..6236d8c107 100644 --- a/packages/nocodb/src/lib/models/View.ts +++ b/packages/nocodb/src/lib/models/View.ts @@ -205,8 +205,10 @@ export default class View implements ViewType { } public static async list(modelId: string, ncMeta = Noco.ncMeta) { - let viewsList = await NocoCache.getList(CacheScope.VIEW, [modelId]); - if (!viewsList.length) { + const cachedList = await NocoCache.getList(CacheScope.VIEW, [modelId]); + let { list: viewsList } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !viewsList.length) { viewsList = await ncMeta.metaList2(null, null, MetaTable.VIEWS, { condition: { fk_model_id: modelId, @@ -1123,8 +1125,10 @@ export default class View implements ViewType { ); // get existing cache - const dataList = await NocoCache.getList(scope, [viewId]); - if (dataList?.length) { + const cachedList = await NocoCache.getList(scope, [viewId]); + const { list: dataList } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && dataList?.length) { for (const o of dataList) { if (!ignoreColdIds?.length || !ignoreColdIds.includes(o.fk_column_id)) { // set data @@ -1209,7 +1213,9 @@ export default class View implements ViewType { } // get existing cache - const dataList = await NocoCache.getList(scope, [viewId]); + const cachedList = await NocoCache.getList(scope, [viewId]); + const { list: dataList } = cachedList; + const { isNoneList } = cachedList; const colsEssentialForView = view.type === ViewTypes.MAP @@ -1218,7 +1224,7 @@ export default class View implements ViewType { const mergedIgnoreColdIds = [...ignoreColdIds, ...colsEssentialForView]; - if (dataList?.length) { + if (!isNoneList && dataList?.length) { for (const o of dataList) { if ( !mergedIgnoreColdIds?.length || @@ -1257,8 +1263,10 @@ export default class View implements ViewType { } static async shareViewList(tableId, ncMeta = Noco.ncMeta) { - let sharedViews = await NocoCache.getList(CacheScope.VIEW, [tableId]); - if (!sharedViews.length) { + const cachedList = await NocoCache.getList(CacheScope.VIEW, [tableId]); + let { list: sharedViews } = cachedList; + const { isNoneList } = cachedList; + if (!isNoneList && !sharedViews.length) { sharedViews = await ncMeta.metaList2(null, null, MetaTable.VIEWS, { xcCondition: { fk_model_id: { diff --git a/packages/nocodb/src/lib/plugins/vultr/index.ts b/packages/nocodb/src/lib/plugins/vultr/index.ts index 2bf072c226..773841132b 100644 --- a/packages/nocodb/src/lib/plugins/vultr/index.ts +++ b/packages/nocodb/src/lib/plugins/vultr/index.ts @@ -20,12 +20,12 @@ const config: XcPluginConfig = { type: XcType.SingleLineText, required: true, }, - { + { key: 'hostname', label: 'Host Name', placeholder: 'e.g.: ewr1.vultrobjects.com', type: XcType.SingleLineText, - required: true + required: true, }, { key: 'access_key', diff --git a/packages/nocodb/src/lib/services/attachment.svc.ts b/packages/nocodb/src/lib/services/attachment.svc.ts index 75e349b3bc..6db7d5fb32 100644 --- a/packages/nocodb/src/lib/services/attachment.svc.ts +++ b/packages/nocodb/src/lib/services/attachment.svc.ts @@ -30,8 +30,8 @@ export async function upload(param: { // if `url` is null, then it is local attachment if (!url) { - // then store the attachement path only - // url will be constructued in `useAttachmentCell` + // then store the attachment path only + // url will be constructed in `useAttachmentCell` attachmentPath = `download/${filePath.join('/')}/${fileName}`; } diff --git a/packages/nocodb/src/lib/services/dbData/index.ts b/packages/nocodb/src/lib/services/dbData/index.ts index 6bacb1f1b0..e7221b527e 100644 --- a/packages/nocodb/src/lib/services/dbData/index.ts +++ b/packages/nocodb/src/lib/services/dbData/index.ts @@ -131,9 +131,7 @@ export async function getDataList(param: { count = await baseModel.count(listArgs); } catch (e) { console.log(e); - NcError.internalServerError( - 'Internal Server Error, check server log for more details' - ); + NcError.internalServerError('Please check server log for more details'); } return new PagedResponseImpl(data, { @@ -642,9 +640,7 @@ export async function dataReadByViewId(param: { ); } catch (e) { console.log(e); - NcError.internalServerError( - 'Internal Server Error, check server log for more details' - ); + NcError.internalServerError('Please check server log for more details'); } } diff --git a/packages/nocodb/src/lib/services/public/publicData.svc.ts b/packages/nocodb/src/lib/services/public/publicData.svc.ts index 0a29b7201b..c3c8975e9d 100644 --- a/packages/nocodb/src/lib/services/public/publicData.svc.ts +++ b/packages/nocodb/src/lib/services/public/publicData.svc.ts @@ -69,10 +69,7 @@ export async function dataList(param: { count = await baseModel.count(listArgs); } catch (e) { console.log(e); - // show empty result instead of throwing error here - // e.g. search some text in a numeric field - - NcError.internalServerError('Please try after some time'); + NcError.internalServerError('Please check server log for more details'); } return new PagedResponseImpl(data, { ...param.query, count }); @@ -235,7 +232,7 @@ export async function dataInsert(param: { const fieldName = file?.fieldname?.replace(/^_|\[\d*]$/g, ''); const filePath = sanitizeUrlPath([ - 'v1', + 'noco', project.title, model.title, fieldName, @@ -243,18 +240,25 @@ export async function dataInsert(param: { if (fieldName in fields && fields[fieldName].uidt === UITypes.Attachment) { attachments[fieldName] = attachments[fieldName] || []; - const fileName = `${nanoid(6)}_${file.originalname}`; - let url = await storageAdapter.fileCreate( + const fileName = `${nanoid(18)}${path.extname(file.originalname)}`; + + const url = await storageAdapter.fileCreate( slash(path.join('nc', 'uploads', ...filePath, fileName)), file ); + let attachmentPath; + + // if `url` is null, then it is local attachment if (!url) { - url = `${param.siteUrl}/download/${filePath.join('/')}/${fileName}`; + // then store the attachment path only + // url will be constructed in `useAttachmentCell` + attachmentPath = `download/${filePath.join('/')}/${fileName}`; } attachments[fieldName].push({ - url, + ...(url ? { url } : {}), + ...(attachmentPath ? { path: attachmentPath } : {}), title: file.originalname, mimetype: file.mimetype, size: file.size, @@ -287,6 +291,7 @@ export async function relDataList(param: { } const column = await Column.get({ colId: param.columnId }); + const colOptions = await column.getColOptions(); const model = await colOptions.getRelatedTable(); @@ -299,25 +304,27 @@ export async function relDataList(param: { dbDriver: await NcConnectionMgrv2.get(base), }); - const { ast } = await getAst({ + const { ast, dependencyFields } = await getAst({ query: param.query, model, extractOnlyPrimaries: true, }); let data = []; + let count = 0; + try { data = data = await nocoExecute( ast, - await baseModel.list(param.query), + await baseModel.list(dependencyFields), {}, - param.query + dependencyFields ); - count = await baseModel.count(param.query); + count = await baseModel.count(dependencyFields); } catch (e) { - // show empty result instead of throwing error here - // e.g. search some text in a numeric field + console.log(e); + NcError.internalServerError('Please check server log for more details'); } return new PagedResponseImpl(data, { ...param.query, count }); diff --git a/packages/nocodb/src/schema/swagger.json b/packages/nocodb/src/schema/swagger.json index 1fcfc3e4eb..b4e9f6c9c5 100644 --- a/packages/nocodb/src/schema/swagger.json +++ b/packages/nocodb/src/schema/swagger.json @@ -10886,20 +10886,7 @@ }, "requestBody": { "content": { - "application/json": { - "schema": { - "type": "object", - "description": "Data Object where the key is column and the value is the data value" - }, - "examples": { - "Example 1": { - "value": { - "col1": "foo", - "col2": "bar" - } - } - } - } + "multipart/form-data": {} }, "description": "" }, diff --git a/tests/playwright/pages/Dashboard/common/Cell/index.ts b/tests/playwright/pages/Dashboard/common/Cell/index.ts index bede598f4e..1b287cb595 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/index.ts +++ b/tests/playwright/pages/Dashboard/common/Cell/index.ts @@ -264,9 +264,11 @@ export class CellPageObject extends BasePage { columnHeader, count, value, + verifyChildList = false, }: CellProps & { count?: number; value: string[]; + verifyChildList?: boolean; }) { // const count = value.length; const cell = await this.get({ index, columnHeader }); @@ -281,6 +283,27 @@ export class CellPageObject extends BasePage { for (let i = 0; i < value.length; ++i) { await expect(await chips.nth(i).locator('.name')).toHaveText(value[i]); } + + if (verifyChildList) { + // open child list + await this.get({ index, columnHeader }).hover(); + const arrow_expand = await this.get({ index, columnHeader }).locator('.nc-arrow-expand'); + + // arrow expand doesn't exist for bt columns + if (await arrow_expand.count()) { + await arrow_expand.click(); + + // wait for child list to open + await this.rootPage.waitForSelector('.nc-modal-child-list:visible'); + + // verify child list count & contents + const childList = await this.rootPage.locator('.ant-card:visible'); + expect(await childList.count()).toBe(count); + + // close child list + await this.rootPage.locator('.nc-modal-child-list').locator('button.ant-modal-close:visible').click(); + } + } } async unlinkVirtualCell({ index, columnHeader }: CellProps) { diff --git a/tests/playwright/scripts/docker-compose-pg.yml b/tests/playwright/scripts/docker-compose-pg.yml index 03965794e9..9fab10ee8c 100644 --- a/tests/playwright/scripts/docker-compose-pg.yml +++ b/tests/playwright/scripts/docker-compose-pg.yml @@ -1,8 +1,8 @@ -version: "2.1" +version: "2.2" services: - pg96: - image: postgres:9.6 + pg147: + image: postgres:14.7 restart: always environment: POSTGRES_PASSWORD: password diff --git a/tests/playwright/scripts/docker-compose-playwright-pg.yml b/tests/playwright/scripts/docker-compose-playwright-pg.yml index 4998de8d00..046efbb48a 100644 --- a/tests/playwright/scripts/docker-compose-playwright-pg.yml +++ b/tests/playwright/scripts/docker-compose-playwright-pg.yml @@ -1,8 +1,8 @@ version: "2.1" services: - pg96: - image: postgres:15 + pg147: + image: postgres:14.7 restart: always environment: POSTGRES_PASSWORD: password diff --git a/tests/playwright/tests/columnFormula.spec.ts b/tests/playwright/tests/columnFormula.spec.ts index 8fda28aae3..a285e6a9da 100644 --- a/tests/playwright/tests/columnFormula.spec.ts +++ b/tests/playwright/tests/columnFormula.spec.ts @@ -122,6 +122,18 @@ const formulaDataByDbType = (context: NcContext) => [ formula: `NOW()`, result: ['1', '1', '1', '1', '1'], }, + { + formula: `OR(true, false)`, + result: isPg(context) ? ['true', 'true', 'true', 'true', 'true'] : ['1', '1', '1', '1', '1'], + }, + { + formula: `AND(false, false)`, + result: isPg(context) ? ['false', 'false', 'false', 'false', 'false'] : ['0', '0', '0', '0', '0'], + }, + { + formula: `IF((SEARCH({Address List}, "Parkway") != 0), "2.0","WRONG")`, + result: ['WRONG', 'WRONG', 'WRONG', '2.0', '2.0'], + }, ]; test.describe('Virtual Columns', () => { diff --git a/tests/playwright/tests/megaTable.spec.ts b/tests/playwright/tests/megaTable.spec.ts index 578e121103..ffa3976579 100644 --- a/tests/playwright/tests/megaTable.spec.ts +++ b/tests/playwright/tests/megaTable.spec.ts @@ -157,4 +157,67 @@ test.describe.serial('Test table', () => { await page.reload(); }); + + test.skip('mega table - LinkToAnotherRecord', async ({ page }) => { + let table_1, table_2; + const columns = []; + + // a Primary key column & display column + columns.push( + { + column_name: 'Id', + title: 'Id', + uidt: UITypes.ID, + }, + { + column_name: 'SingleLineText', + title: 'SingleLineText', + uidt: UITypes.SingleLineText, + pv: true, + } + ); + + const project = await api.project.read(context.project.id); + // eslint-disable-next-line prefer-const + table_1 = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { + table_name: 'table_1', + title: 'table_1', + columns: columns, + }); + // eslint-disable-next-line prefer-const + table_2 = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { + table_name: 'table_2', + title: 'table_2', + columns: columns, + }); + + const rows = []; + for (let i = 0; i < 1000; i++) { + rows.push({ + Id: i + 1, + SingleLineText: `SingleLineText${i + 1}`, + }); + } + await api.dbTableRow.bulkCreate('noco', context.project.id, table_1.id, rows); + await api.dbTableRow.bulkCreate('noco', context.project.id, table_2.id, rows); + + await api.dbTableColumn.create(table_2.id, { + uidt: UITypes.LinkToAnotherRecord, + title: 'LinkToAnotherRecord', + column_name: 'LinkToAnotherRecord', + parentId: table_1.id, + childId: table_2.id, + type: 'hm', + }); + + // // nested add : hm + // for (let i = 1; i <= 1000; i++) { + // await api.dbTableRow.nestedAdd('noco', project.title, table_1.table_name, i, 'hm', 'LinkToAnotherRecord', `${i}`); + // } + + // nested add : bt + for (let i = 1; i <= 1000; i++) { + await api.dbTableRow.nestedAdd('noco', project.title, table_2.table_name, i, 'bt', 'table_1', `${i}`); + } + }); }); diff --git a/tests/playwright/tests/viewGridShare.spec.ts b/tests/playwright/tests/viewGridShare.spec.ts index b83bdde221..ec932816e1 100644 --- a/tests/playwright/tests/viewGridShare.spec.ts +++ b/tests/playwright/tests/viewGridShare.spec.ts @@ -90,7 +90,7 @@ test.describe('Shared view', () => { // verify virtual records for (const record of expectedVirtualRecordsByDb) { - await sharedPage.grid.cell.verifyVirtualCell(record); + await sharedPage.grid.cell.verifyVirtualCell({ ...record, verifyChildList: true }); } /**