Browse Source

Merge branch 'develop' of https://github.com/nocodb/nocodb into geodata-prototyping-restart

pull/4723/head
flisowna 2 years ago
parent
commit
4360c1ad31
  1. 5
      .github/workflows/ci-cd.yml
  2. 1
      .github/workflows/dco-check.yml
  3. 7
      .github/workflows/release-nocodb.yml
  4. 6
      .gitignore
  5. 23
      markdown/readme/languages/chinese.md
  6. 24
      markdown/readme/languages/german.md
  7. 2174
      package-lock.json
  8. 19
      package.json
  9. 12
      packages/nc-cli/package-lock.json
  10. 2
      packages/nc-gui/app.vue
  11. 5
      packages/nc-gui/assets/style.scss
  12. 1
      packages/nc-gui/components.d.ts
  13. 4
      packages/nc-gui/components/account/UserList.vue
  14. 4
      packages/nc-gui/components/account/UsersModal.vue
  15. 44
      packages/nc-gui/components/cell/MultiSelect.vue
  16. 36
      packages/nc-gui/components/cell/SingleSelect.vue
  17. 7
      packages/nc-gui/components/smartsheet/Gallery.vue
  18. 7
      packages/nc-gui/components/smartsheet/Kanban.vue
  19. 9
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue
  20. 15
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  21. 3
      packages/nc-gui/components/smartsheet/header/CellIcon.ts
  22. 27
      packages/nc-gui/components/smartsheet/toolbar/SearchData.vue
  23. 9
      packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue
  24. 21
      packages/nc-gui/components/virtual-cell/Lookup.vue
  25. 12
      packages/nc-gui/composables/useColumnCreateStore.ts
  26. 34
      packages/nc-gui/composables/useFieldQuery.ts
  27. 19
      packages/nc-gui/composables/useSmartsheetStore.ts
  28. 2
      packages/nc-gui/lang/ar.json
  29. 3
      packages/nc-gui/lang/bn_IN.json
  30. 2
      packages/nc-gui/lang/da.json
  31. 2
      packages/nc-gui/lang/de.json
  32. 2
      packages/nc-gui/lang/es.json
  33. 2
      packages/nc-gui/lang/fa.json
  34. 2
      packages/nc-gui/lang/fi.json
  35. 2
      packages/nc-gui/lang/fr.json
  36. 2
      packages/nc-gui/lang/he.json
  37. 2
      packages/nc-gui/lang/hi.json
  38. 2
      packages/nc-gui/lang/hr.json
  39. 2
      packages/nc-gui/lang/id.json
  40. 2
      packages/nc-gui/lang/it.json
  41. 2
      packages/nc-gui/lang/ja.json
  42. 2
      packages/nc-gui/lang/ko.json
  43. 2
      packages/nc-gui/lang/lv.json
  44. 2
      packages/nc-gui/lang/nl.json
  45. 2
      packages/nc-gui/lang/no.json
  46. 2
      packages/nc-gui/lang/pl.json
  47. 2
      packages/nc-gui/lang/pt.json
  48. 2
      packages/nc-gui/lang/pt_BR.json
  49. 2
      packages/nc-gui/lang/ru.json
  50. 2
      packages/nc-gui/lang/sl.json
  51. 2
      packages/nc-gui/lang/sv.json
  52. 2
      packages/nc-gui/lang/th.json
  53. 2
      packages/nc-gui/lang/tr.json
  54. 74
      packages/nc-gui/lang/uk.json
  55. 76
      packages/nc-gui/lang/vi.json
  56. 2
      packages/nc-gui/lang/zh-Hans.json
  57. 298
      packages/nc-gui/lang/zh-Hant.json
  58. 2
      packages/nc-gui/layouts/default.vue
  59. 2
      packages/nc-gui/package-lock.json
  60. 1
      packages/nc-gui/pages/account/index.vue
  61. 5
      packages/nc-gui/pages/account/index/users.vue
  62. 30
      packages/nc-gui/pages/account/index/users/[[nestedPage]].vue
  63. 22
      packages/nc-gui/pages/index/index/[projectId].vue
  64. 19
      packages/nc-gui/pages/index/index/create.vue
  65. 156
      packages/nc-gui/pages/index/index/index.vue
  66. 5
      packages/nc-gui/pages/index/index/user.vue
  67. 2
      packages/nc-gui/windi.config.ts
  68. 2
      packages/nc-lib-gui/package.json
  69. 12
      packages/nc-plugin/package-lock.json
  70. 224
      packages/noco-docs/content/en/engineering/playwright.md
  71. 3
      packages/noco-docs/content/en/engineering/translation.md
  72. 161
      packages/noco-docs/content/en/engineering/unit-testing.md
  73. 12
      packages/noco-docs/package-lock.json
  74. 4
      packages/nocodb-sdk/package-lock.json
  75. 2
      packages/nocodb-sdk/package.json
  76. 15
      packages/nocodb/docker-compose.yml
  77. 357
      packages/nocodb/package-lock.json
  78. 13
      packages/nocodb/package.json
  79. 2
      packages/nocodb/src/lib/db/sql-client/lib/KnexClient.ts
  80. 473
      packages/nocodb/src/lib/db/sql-client/lib/mssql/MssqlClient.ts
  81. 4
      packages/nocodb/src/lib/db/sql-client/lib/pg/PgClient.ts
  82. 26
      packages/nocodb/src/lib/db/sql-data-mapper/lib/BaseModel.ts
  83. 17
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSql.ts
  84. 65
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts
  85. 3
      packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts
  86. 4
      packages/nocodb/src/lib/meta/api/index.ts
  87. 4
      packages/nocodb/src/lib/meta/api/orgLicenseApis.ts
  88. 3
      packages/nocodb/src/lib/meta/api/publicApis/publicDataApis.ts
  89. 7
      packages/nocodb/src/lib/meta/api/swagger/helpers/getSwaggerColumnMetas.ts
  90. 21
      packages/nocodb/src/lib/meta/api/swagger/helpers/swagger-base.json
  91. 2
      packages/nocodb/src/lib/meta/api/swagger/swaggerApis.ts
  92. 12
      packages/nocodb/src/lib/meta/api/sync/helpers/job.ts
  93. 2
      packages/nocodb/src/lib/meta/api/testApis.ts
  94. 9
      packages/nocodb/src/lib/meta/helpers/populateSamplePayload.ts
  95. 6
      packages/nocodb/src/lib/models/SelectOption.ts
  96. 18
      packages/nocodb/src/lib/services/test/TestResetService/index.ts
  97. 10
      packages/nocodb/src/lib/utils/projectAcl.ts
  98. 53
      scripts/cypress/cypress.json
  99. 67
      scripts/cypress/docker-compose-cypress.yml
  100. 17
      scripts/cypress/docker-compose-pg-cy-quick.yml
  101. Some files were not shown because too many files have changed in this diff Show More

5
.github/workflows/ci-cd.yml

@ -8,7 +8,6 @@ on:
branches: [develop] branches: [develop]
paths: paths:
- "packages/nc-gui/**" - "packages/nc-gui/**"
- "scripts/cypress/**"
- "packages/nocodb/**" - "packages/nocodb/**"
- ".github/workflows/ci-cd.yml" - ".github/workflows/ci-cd.yml"
pull_request: pull_request:
@ -16,7 +15,6 @@ on:
branches: [develop] branches: [develop]
paths: paths:
- "packages/nc-gui/**" - "packages/nc-gui/**"
- "scripts/cypress/**"
- "packages/nocodb/**" - "packages/nocodb/**"
- ".github/workflows/ci-cd.yml" - ".github/workflows/ci-cd.yml"
@ -60,9 +58,6 @@ jobs:
- name: Install dependencies - name: Install dependencies
working-directory: ./packages/nocodb working-directory: ./packages/nocodb
run: npm install run: npm install
- name: setup mysql
working-directory: ./
run: docker-compose -f ./scripts/cypress/docker-compose-cypress.yml up -d
- name: run unit tests - name: run unit tests
working-directory: ./packages/nocodb working-directory: ./packages/nocodb
run: npm run test:unit run: npm run test:unit

1
.github/workflows/dco-check.yml

@ -10,7 +10,6 @@ on:
- "packages/nc-lib-gui/**" - "packages/nc-lib-gui/**"
- "packages/nc-plugin/**" - "packages/nc-plugin/**"
- "packages/nocodb/**" - "packages/nocodb/**"
- "scripts/cypress/**"
jobs: jobs:
commits_check_job: commits_check_job:

7
.github/workflows/release-nocodb.yml

@ -117,12 +117,7 @@ jobs:
secrets: secrets:
GH_TOKEN: "${{ secrets.GH_TOKEN }}" GH_TOKEN: "${{ secrets.GH_TOKEN }}"
# Change nocodb-sdk back to local path
update-sdk-path:
needs: publish-docs
uses: ./.github/workflows/update-sdk-path.yml
# Sync changes to develop # Sync changes to develop
sync-to-develop: sync-to-develop:
needs: update-sdk-path needs: close-issues
uses: ./.github/workflows/sync-to-develop.yml uses: ./.github/workflows/sync-to-develop.yml

6
.gitignore vendored

@ -84,12 +84,6 @@ mongod
/packages/nocodb/docker/main.js.LICENSE.txt /packages/nocodb/docker/main.js.LICENSE.txt
/packages/nocodb/noco_log.db /packages/nocodb/noco_log.db
# Cypress
#=========
shared.json
/scripts/Cypress/screenshots
/scripts/exp/
# NC_DBs # NC_DBs
#========= #=========
nc_minimal_dbs/ nc_minimal_dbs/

23
markdown/readme/languages/chinese.md

@ -255,29 +255,6 @@ npm run dev
> nocodb/packages/nocodb 包括 nc-lib-gui,它是 npm 源中托管的 nc-gui 的预构建版本。如果您只想修改后端,则可以在本地启动后端后在浏览器中访问 localhost:8000/dashboard > nocodb/packages/nocodb 包括 nc-lib-gui,它是 npm 源中托管的 nc-gui 的预构建版本。如果您只想修改后端,则可以在本地启动后端后在浏览器中访问 localhost:8000/dashboard
## 本地运行 Cypress 测试
```shell
# 安装依赖 (cypress)
npm install
# 使用 docker compose 运行带有所需数据库的 mysql 数据库
docker-compose -f ./scripts/cypress/docker-compose-cypress.yml up
# 使用以下命令运行后端 api
npm run start:api
# 使用以下命令运行前端 Web UI
npm run start:web
# 等待 3000 和 8080 端口都可用后
# 使用以下命令运行 cypress 测试
npm run cypress:run
# 或运行以下命令以使用 GUI 运行它
npm run cypress:open
```
# 贡献 # 贡献
参见[贡献指南](https://github.com/nocodb/nocodb/blob/master/.github/CONTRIBUTING.md). 参见[贡献指南](https://github.com/nocodb/nocodb/blob/master/.github/CONTRIBUTING.md).

24
markdown/readme/languages/german.md

@ -236,30 +236,6 @@ npm run dev
> nocodb/packages/nocodb enthält nc-lib-gui, die entwickelte Version von nc-gui, die in der npm-Registry gehostet wird. Sie können localhost:8000/dashboard im Browser aufrufen, nachdem Sie das Backend lokal gestartet haben, wenn Sie nur das Backend ändern möchten. > nocodb/packages/nocodb enthält nc-lib-gui, die entwickelte Version von nc-gui, die in der npm-Registry gehostet wird. Sie können localhost:8000/dashboard im Browser aufrufen, nachdem Sie das Backend lokal gestartet haben, wenn Sie nur das Backend ändern möchten.
## Cypress-Tests lokal ausführen
```shell
# install dependencies (cypress)
npm install
# MySQL-Datenbank mit der benötigten Datenbank mit Docker Compose ausführen
docker-compose -f ./scripts/cypress/docker-compose-cypress.yml up
# Backend API mit folgendem Befehl ausführen
npm run start:api
# Frontend Web-UI mit folgendem Befehl ausführen
npm run start:web
# Warten, bis die beiden Ports 3000 und 8000 verfügbar sind,
# dann Cypress Test mit diesem Befehl ausführen
npm run cypress:run
# Oder diesen Befehl ausführen, um die GUI auszuführen
npm run cypress:open
```
# Beiträge # Beiträge
Siehe [Contribution Guide](https://github.com/nocodb/nocodb/blob/master/.github/CONTRIBUTING.md). Siehe [Contribution Guide](https://github.com/nocodb/nocodb/blob/master/.github/CONTRIBUTING.md).

2174
package-lock.json generated

File diff suppressed because it is too large Load Diff

19
package.json

@ -16,10 +16,6 @@
}, },
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"devDependencies": { "devDependencies": {
"@4tw/cypress-drag-drop": "^2.0.0",
"cypress": "^9.2.0",
"cypress-file-upload": "^5.0.8",
"cypress-iframe": "^1.0.1",
"fs": "0.0.1-security", "fs": "0.0.1-security",
"lerna": "^3.20.1", "lerna": "^3.20.1",
"husky": "^8.0.0", "husky": "^8.0.0",
@ -39,25 +35,16 @@
"lint:staged:playwright": "cd ./tests/playwright; npx lint-staged; cd -", "lint:staged:playwright": "cd ./tests/playwright; npx lint-staged; cd -",
"build:common": "cd ./packages/nocodb-sdk; npm install; npm run build", "build:common": "cd ./packages/nocodb-sdk; npm install; npm run build",
"install:common": "cd ./packages/nocodb; npm install ../nocodb-sdk; cd ../nc-gui; npm install ../nocodb-sdk", "install:common": "cd ./packages/nocodb; npm install ../nocodb-sdk; cd ../nc-gui; npm install ../nocodb-sdk",
"start:api": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_DISABLE_CACHE=true NC_DISABLE_TELE=true npm run watch:run:cypress",
"start:xcdb-api": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk;npm install; NC_EXPORT_MAX_TIMEOUT=60000 NC_DISABLE_CACHE=true NC_DISABLE_TELE=true NC_INFLECTION=camelize DATABASE_URL=sqlite:../../../scripts/cypress/fixtures/sqlite-sakila/sakila.db npm run watch:run:cypress",
"start:api:cache": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk;npm install; NC_EXPORT_MAX_TIMEOUT=60000 NC_DISABLE_TELE=true npm run watch:run:cypress",
"start:api:cache:pg": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_DISABLE_TELE=true npm run watch:run:cypress:pg",
"start:api:cache:pg:cyquick": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_DISABLE_TELE=true npm run watch:run:cypress:pg:cyquick",
"start:xcdb-api:cache": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_EXPORT_MAX_TIMEOUT=60000 NC_DISABLE_TELE=true NC_INFLECTION=camelize DATABASE_URL=sqlite:../../../scripts/cypress/fixtures/sqlite-sakila/sakila.db npm run watch:run:cypress",
"start:web": "npm run build:common ; cd ./packages/nc-gui; npm install ../nocodb-sdk; npm install; ANT_MESSAGE_DURATION=0.5 npm run dev", "start:web": "npm run build:common ; cd ./packages/nc-gui; npm install ../nocodb-sdk; npm install; ANT_MESSAGE_DURATION=0.5 npm run dev",
"cypress:run": "cypress run --config-file ./scripts/cypress/cypress.json",
"cypress:open": "cypress open --config-file ./scripts/cypress/cypress.json",
"cypress:clear": "cypress cache clear",
"test:travis": "git log --pretty=format:'%h' -n 1 --skip 1 | xargs lerna run test:travis --since", "test:travis": "git log --pretty=format:'%h' -n 1 --skip 1 | xargs lerna run test:travis --since",
"lerna:install": "git log --pretty=format:'%h' -n 1 --skip 1 | xargs lerna bootstrap --ignore nc-cli --since", "lerna:install": "git log --pretty=format:'%h' -n 1 --skip 1 | xargs lerna bootstrap --ignore nc-cli --since",
"updated:xc-migrator": "lerna run publish --scope xc-migrator && lerna run xc && lerna publish && npm install -f xc-cli", "updated:xc-migrator": "lerna run publish --scope xc-migrator && lerna run xc && lerna publish && npm install -f xc-cli",
"doc": "lerna run doc", "doc": "lerna run doc",
"install:local:dep": "cd packages/nc-lib-gui;npm uninstall -S xc-lib;rm package-lock.json; npm i ../../../xc-lib-private; cd ../xc-instant;npm uninstall -S xc-lib xc-lib-gui;npm i ../../../xc-lib-private;npm i ../xc-lib-gui", "install:local:dep": "cd packages/nc-lib-gui;npm uninstall -S xc-lib;rm package-lock.json; npm i ../../../xc-lib-private; cd ../xc-instant;npm uninstall -S xc-lib xc-lib-gui;npm i ../../../xc-lib-private;npm i ../xc-lib-gui",
"install:npm:dep": "cd packages/nc-lib-gui;npm uninstall -S xc-lib; npm i -S xc-lib@latest; cd ../xc-instant;npm uninstall -S xc-lib xc-lib-gui;npm i -S xc-lib@latest xc-lib-gui@latest;npm i ../xc-lib-gui", "install:npm:dep": "cd packages/nc-lib-gui;npm uninstall -S xc-lib; npm i -S xc-lib@latest; cd ../xc-instant;npm uninstall -S xc-lib xc-lib-gui;npm i -S xc-lib@latest xc-lib-gui@latest;npm i ../xc-lib-gui",
"start:pg": "docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d", "prepare": "husky install",
"stop:pg": "docker-compose -f ./scripts/cypress/docker-compose-pg.yml down", "start:pg": "docker-compose -f ./tests/playwright/scripts/docker-compose-pg.yml up -d",
"prepare": "husky install" "stop:pg": "docker-compose -f ./tests/playwright/scripts/docker-compose-pg.yml down"
}, },
"dependencies": { "dependencies": {
"express": "^4.18.1", "express": "^4.18.1",

12
packages/nc-cli/package-lock.json generated

@ -9465,9 +9465,9 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.0.4", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
}, },
@ -22773,9 +22773,9 @@
"dev": true "dev": true
}, },
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }

2
packages/nc-gui/app.vue

@ -36,7 +36,7 @@ if (typeof window !== 'undefined') {
<template> <template>
<a-config-provider> <a-config-provider>
<NuxtLayout :name="disableBaseLayout ? false : 'base'"> <NuxtLayout :name="disableBaseLayout ? false : 'base'">
<NuxtPage :key="key" /> <NuxtPage :key="key" :transition="false" />
</NuxtLayout> </NuxtLayout>
</a-config-provider> </a-config-provider>
</template> </template>

5
packages/nc-gui/assets/style.scss

@ -281,3 +281,8 @@ a {
.ant-modal { .ant-modal {
@apply !top-[50px]; @apply !top-[50px];
} }
.ant-select-item-option-active:not(.ant-select-item-option-disabled) {
@apply bg-primary bg-opacity-20;
}

1
packages/nc-gui/components.d.ts vendored

@ -125,7 +125,6 @@ declare module '@vue/runtime-core' {
MdiBugOutline: typeof import('~icons/mdi/bug-outline')['default'] MdiBugOutline: typeof import('~icons/mdi/bug-outline')['default']
MdiCalculator: typeof import('~icons/mdi/calculator')['default'] MdiCalculator: typeof import('~icons/mdi/calculator')['default']
MdiCalendarMonth: typeof import('~icons/mdi/calendar-month')['default'] MdiCalendarMonth: typeof import('~icons/mdi/calendar-month')['default']
MdiCancel: typeof import('~icons/mdi/cancel')['default']
MdiCardsHeart: typeof import('~icons/mdi/cards-heart')['default'] MdiCardsHeart: typeof import('~icons/mdi/cards-heart')['default']
MdiCellphoneMessage: typeof import('~icons/mdi/cellphone-message')['default'] MdiCellphoneMessage: typeof import('~icons/mdi/cellphone-message')['default']
MdiChat: typeof import('~icons/mdi/chat')['default'] MdiChat: typeof import('~icons/mdi/chat')['default']

4
packages/nc-gui/components/account/UserList.vue

@ -29,7 +29,9 @@ const searchText = ref<string>('')
const pagination = reactive({ const pagination = reactive({
total: 0, total: 0,
pageSize: 10, pageSize: 10,
position: ['bottomCenter'],
}) })
const loadUsers = async (page = currentPage, limit = currentLimit) => { const loadUsers = async (page = currentPage, limit = currentLimit) => {
currentPage = page currentPage = page
try { try {
@ -158,7 +160,7 @@ const copyPasswordResetUrl = async (user: User) => {
<a-table <a-table
:row-key="(record) => record.id" :row-key="(record) => record.id"
:data-source="users" :data-source="users"
:pagination="{ position: ['bottomCenter'] }" :pagination="pagination"
:loading="isLoading" :loading="isLoading"
size="small" size="small"
@change="loadUsers($event.current)" @change="loadUsers($event.current)"

4
packages/nc-gui/components/account/UsersModal.vue

@ -4,13 +4,13 @@ import {
Form, Form,
computed, computed,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
isEmail,
message, message,
ref, ref,
useCopy, useCopy,
useDashboard, useDashboard,
useI18n, useI18n,
useNuxtApp, useNuxtApp,
validateEmail,
} from '#imports' } from '#imports'
import type { User } from '~/lib' import type { User } from '~/lib'
import { Role } from '~/lib' import { Role } from '~/lib'
@ -52,7 +52,7 @@ const validators = computed(() => {
callback('Email is required') callback('Email is required')
return return
} }
const invalidEmails = (value || '').split(/\s*,\s*/).filter((e: string) => !isEmail(e)) const invalidEmails = (value || '').split(/\s*,\s*/).filter((e: string) => !validateEmail(e))
if (invalidEmails.length > 0) { if (invalidEmails.length > 0) {
callback(`${invalidEmails.length > 1 ? ' Invalid emails:' : 'Invalid email:'} ${invalidEmails.join(', ')} `) callback(`${invalidEmails.length > 1 ? ' Invalid emails:' : 'Invalid email:'} ${invalidEmails.join(', ')} `)
} else { } else {

44
packages/nc-gui/components/cell/MultiSelect.vue

@ -33,8 +33,6 @@ const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { isMysql } = useProject()
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
const readOnly = inject(ReadonlyInj)! const readOnly = inject(ReadonlyInj)!
@ -59,6 +57,8 @@ const { $api } = useNuxtApp()
const { getMeta } = useMetas() const { getMeta } = useMetas()
const { isPg, isMysql } = useProject()
// a variable to keep newly created options value // a variable to keep newly created options value
// temporary until it's add the option to column meta // temporary until it's add the option to column meta
const tempSelectedOptsState = reactive<string[]>([]) const tempSelectedOptsState = reactive<string[]>([])
@ -171,8 +171,19 @@ useSelectedCellKeyupListener(active, (e) => {
isOpen.value = true isOpen.value = true
} }
break break
case 'ArrowUp':
case 'ArrowDown':
case 'ArrowRight':
case 'ArrowLeft':
case 'Delete':
// skip
break
default: default:
isOpen.value = true // toggle only if char key pressed
if (e.key?.length === 1) {
e.stopPropagation()
isOpen.value = true
}
break break
} }
}) })
@ -195,9 +206,28 @@ async function addIfMissingAndSave() {
}) })
column.value.colOptions = { options: newOptions.map(({ value: _, ...rest }) => rest) } column.value.colOptions = { options: newOptions.map(({ value: _, ...rest }) => rest) }
await $api.dbTableColumn.update((column.value as { fk_column_id?: string })?.fk_column_id || (column.value?.id as string), { const updatedColMeta = { ...column.value }
...column.value,
}) // todo: refactor and avoid repetition
if (updatedColMeta.cdf) {
// Postgres returns default value wrapped with single quotes & casted with type so we have to get value between single quotes to keep it unified for all databases
if (isPg.value) {
updatedColMeta.cdf = updatedColMeta.cdf.substring(
updatedColMeta.cdf.indexOf(`'`) + 1,
updatedColMeta.cdf.lastIndexOf(`'`),
)
}
// Mysql escapes single quotes with backslash so we keep quotes but others have to unescaped
if (!isMysql.value) {
updatedColMeta.cdf = updatedColMeta.cdf.replace(/''/g, "'")
}
}
await $api.dbTableColumn.update(
(column.value as { fk_column_id?: string })?.fk_column_id || (column.value?.id as string),
updatedColMeta,
)
activeOptCreateInProgress.value-- activeOptCreateInProgress.value--
if (!activeOptCreateInProgress.value) { if (!activeOptCreateInProgress.value) {
@ -241,8 +271,8 @@ const onTagClick = (e: Event, onClose: Function) => {
class="w-full" class="w-full"
:bordered="false" :bordered="false"
clear-icon clear-icon
show-search
:show-arrow="!readOnly" :show-arrow="!readOnly"
:show-search="active || editable"
:open="isOpen && (active || editable)" :open="isOpen && (active || editable)"
:disabled="readOnly" :disabled="readOnly"
:class="{ '!ml-[-8px]': readOnly }" :class="{ '!ml-[-8px]': readOnly }"

36
packages/nc-gui/components/cell/SingleSelect.vue

@ -49,6 +49,8 @@ const searchVal = ref()
const { getMeta } = useMetas() const { getMeta } = useMetas()
const { isPg, isMysql } = useProject()
// a variable to keep newly created option value // a variable to keep newly created option value
// temporary until it's add the option to column meta // temporary until it's add the option to column meta
const tempSelectedOptState = ref<string>() const tempSelectedOptState = ref<string>()
@ -102,6 +104,13 @@ useSelectedCellKeyupListener(active, (e) => {
isOpen.value = true isOpen.value = true
} }
break break
default:
// toggle only if char key pressed
if (e.key?.length === 1) {
e.stopPropagation()
isOpen.value = true
}
break
} }
}) })
@ -120,9 +129,28 @@ async function addIfMissingAndSave() {
}) })
column.value.colOptions = { options: options.value.map(({ value: _, ...rest }) => rest) } column.value.colOptions = { options: options.value.map(({ value: _, ...rest }) => rest) }
await $api.dbTableColumn.update((column.value as { fk_column_id?: string })?.fk_column_id || (column.value?.id as string), { const updatedColMeta = { ...column.value }
...column.value,
}) // todo: refactor and avoid repetition
if (updatedColMeta.cdf) {
// Postgres returns default value wrapped with single quotes & casted with type so we have to get value between single quotes to keep it unified for all databases
if (isPg.value) {
updatedColMeta.cdf = updatedColMeta.cdf.substring(
updatedColMeta.cdf.indexOf(`'`) + 1,
updatedColMeta.cdf.lastIndexOf(`'`),
)
}
// Mysql escapes single quotes with backslash so we keep quotes but others have to unescaped
if (!isMysql.value) {
updatedColMeta.cdf = updatedColMeta.cdf.replace(/''/g, "'")
}
}
await $api.dbTableColumn.update(
(column.value as { fk_column_id?: string })?.fk_column_id || (column.value?.id as string),
updatedColMeta,
)
vModel.value = newOptValue vModel.value = newOptValue
await getMeta(column.value.fk_model_id!, true) await getMeta(column.value.fk_model_id!, true)
} catch (e) { } catch (e) {
@ -161,7 +189,7 @@ const toggleMenu = (e: Event) => {
:disabled="readOnly" :disabled="readOnly"
:show-arrow="!readOnly && (active || editable || vModel === null)" :show-arrow="!readOnly && (active || editable || vModel === null)"
:dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen ? 'active' : ''}`" :dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen ? 'active' : ''}`"
:show-search="active || editable" show-search
@select="isOpen = false" @select="isOpen = false"
@keydown.stop @keydown.stop
@search="search" @search="search"

7
packages/nc-gui/components/smartsheet/Gallery.vue

@ -80,7 +80,12 @@ const isRowEmpty = (record: any, col: any) => {
const attachments = (record: any): Attachment[] => { const attachments = (record: any): Attachment[] => {
try { try {
return coverImageColumn?.title && record.row[coverImageColumn.title] ? JSON.parse(record.row[coverImageColumn.title]) : [] if (coverImageColumn?.title && record.row[coverImageColumn.title]) {
return typeof record.row[coverImageColumn.title] === 'string'
? JSON.parse(record.row[coverImageColumn.title])
: record.row[coverImageColumn.title]
}
return []
} catch (e) { } catch (e) {
return [] return []
} }

7
packages/nc-gui/components/smartsheet/Kanban.vue

@ -119,7 +119,12 @@ reloadViewDataHook?.on(async () => {
const attachments = (record: any): Attachment[] => { const attachments = (record: any): Attachment[] => {
try { try {
return coverImageColumn?.title && record.row[coverImageColumn.title] ? JSON.parse(record.row[coverImageColumn.title]) : [] if (coverImageColumn?.title && record.row[coverImageColumn.title]) {
return typeof record.row[coverImageColumn.title] === 'string'
? JSON.parse(record.row[coverImageColumn.title])
: record.row[coverImageColumn.title]
}
return []
} catch (e) { } catch (e) {
return [] return []
} }

9
packages/nc-gui/components/smartsheet/column/SelectOptions.vue

@ -174,13 +174,12 @@ watch(inputs, () => {
/> />
</div> </div>
</template> </template>
<template #footer>
<div v-if="validateInfos?.['colOptions.options']?.help?.[0]?.[0]" class="text-error text-[10px] my-2">
{{ validateInfos['colOptions.options'].help[0][0] }}
</div>
</template>
</Draggable> </Draggable>
</div> </div>
<div v-if="validateInfos?.['colOptions.options']?.help?.[0]?.[0]" class="text-error text-[10px] mb-1 mt-2">
{{ validateInfos['colOptions.options'].help[0][0] }}
</div>
<a-button type="dashed" class="w-full caption mt-2" @click="addNewOption()"> <a-button type="dashed" class="w-full caption mt-2" @click="addNewOption()">
<div class="flex items-center"> <div class="flex items-center">
<MdiPlus /> <MdiPlus />

15
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -122,11 +122,13 @@ if (isKanban.value) {
} }
} }
const cellWrapperEl = (wrapperEl: HTMLElement) => { const cellWrapperEl = ref<HTMLElement>()
nextTick(() => {
;(wrapperEl?.querySelector('input,select,textarea') as HTMLInputElement)?.focus() onMounted(() => {
setTimeout(() => {
;(cellWrapperEl.value?.querySelector('input,select,textarea') as HTMLInputElement)?.focus()
}) })
} })
</script> </script>
<script lang="ts"> <script lang="ts">
@ -163,7 +165,10 @@ export default {
<LazySmartsheetHeaderCell v-else :column="col" /> <LazySmartsheetHeaderCell v-else :column="col" />
<div :ref="i ? null : cellWrapperEl" class="!bg-white rounded px-1 min-h-[35px] flex items-center mt-2"> <div
:ref="i ? null : (el) => (cellWrapperEl = el)"
class="!bg-white rounded px-1 min-h-[35px] flex items-center mt-2"
>
<LazySmartsheetVirtualCell v-if="isVirtualCol(col)" v-model="row.row[col.title]" :row="row" :column="col" /> <LazySmartsheetVirtualCell v-if="isVirtualCol(col)" v-model="row.row[col.title]" :row="row" :column="col" />
<LazySmartsheetCell <LazySmartsheetCell

3
packages/nc-gui/components/smartsheet/header/CellIcon.ts

@ -20,7 +20,6 @@ import {
isJSON, isJSON,
isPercent, isPercent,
isPhoneNumber, isPhoneNumber,
isPrimary,
isRating, isRating,
isSet, isSet,
isSingleSelect, isSingleSelect,
@ -58,7 +57,7 @@ import SpecificDBTypeIcon from '~icons/mdi/database-settings'
import DurationIcon from '~icons/mdi/timer-outline' import DurationIcon from '~icons/mdi/timer-outline'
const renderIcon = (column: ColumnType, abstractType: any) => { const renderIcon = (column: ColumnType, abstractType: any) => {
if (isPrimary(column)) { if (isPrimaryKey(column)) {
return KeyIcon return KeyIcon
} else if (isJSON(column)) { } else if (isJSON(column)) {
return JSONIcon return JSONIcon

27
packages/nc-gui/components/smartsheet/toolbar/SearchData.vue

@ -1,9 +1,22 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ReloadViewDataHookInj, computed, inject, onClickOutside, ref, useSmartsheetStoreOrThrow } from '#imports' import {
ActiveViewInj,
ReloadViewDataHookInj,
computed,
inject,
onClickOutside,
ref,
useFieldQuery,
useSmartsheetStoreOrThrow,
} from '#imports'
const reloadData = inject(ReloadViewDataHookInj)! const reloadData = inject(ReloadViewDataHookInj)!
const { search, meta } = useSmartsheetStoreOrThrow() const { meta } = useSmartsheetStoreOrThrow()
const activeView = inject(ActiveViewInj, ref())
const { search, loadFieldQuery } = useFieldQuery(activeView)
const isDropdownOpen = ref(false) const isDropdownOpen = ref(false)
@ -18,6 +31,16 @@ const columns = computed(() =>
})), })),
) )
watch(
() => activeView.value?.id,
(n, o) => {
if (n !== o) {
loadFieldQuery(activeView)
}
},
{ immediate: true },
)
function onPressEnter() { function onPressEnter() {
reloadData.trigger() reloadData.trigger()
} }

9
packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue

@ -150,11 +150,16 @@ const emailField = (inputEl: typeof Input) => {
wrap-class-name="nc-modal-invite-user-and-share-base" wrap-class-name="nc-modal-invite-user-and-share-base"
@cancel="emit('closed')" @cancel="emit('closed')"
> >
<div class="flex flex-col"> <div class="flex flex-col" data-testid="invite-user-and-share-base-modal">
<div class="flex flex-row justify-between items-center pb-1.5 mb-2 border-b-1 w-full"> <div class="flex flex-row justify-between items-center pb-1.5 mb-2 border-b-1 w-full">
<a-typography-title class="select-none" :level="4"> {{ $t('activity.share') }}: {{ project.title }} </a-typography-title> <a-typography-title class="select-none" :level="4"> {{ $t('activity.share') }}: {{ project.title }} </a-typography-title>
<a-button type="text" class="!rounded-md mr-1 -mt-1.5" @click="emit('closed')"> <a-button
type="text"
class="!rounded-md mr-1 -mt-1.5"
data-testid="invite-user-and-share-base-modal-close-btn"
@click="emit('closed')"
>
<template #icon> <template #icon>
<MaterialSymbolsCloseRounded class="flex mx-auto" /> <MaterialSymbolsCloseRounded class="flex mx-auto" />
</template> </template>

21
packages/nc-gui/components/virtual-cell/Lookup.vue

@ -27,14 +27,6 @@ const meta = inject(MetaInj, ref())
const cellValue = inject(CellValueInj, ref()) const cellValue = inject(CellValueInj, ref())
const arrValue = computed(() => {
if (!cellValue.value) return []
if (Array.isArray(cellValue.value)) return cellValue.value
return [cellValue.value]
})
const relationColumn = computed( const relationColumn = computed(
() => () =>
meta.value?.columns?.find((c) => c.id === (column.value?.colOptions as LookupType)?.fk_relation_column_id) as meta.value?.columns?.find((c) => c.id === (column.value?.colOptions as LookupType)?.fk_relation_column_id) as
@ -66,6 +58,19 @@ const lookupColumn = computed(
| undefined, | undefined,
) )
const arrValue = computed(() => {
if (!cellValue.value) return []
// if lookup column is Attachment and relation type is Belongs to wrap the value in an array
// since the attachment component expects an array or JSON string array
if (lookupColumn.value?.uidt === UITypes.Attachment && relationColumn.value?.colOptions?.type === RelationTypes.BELONGS_TO)
return [cellValue.value]
if (Array.isArray(cellValue.value)) return cellValue.value
return [cellValue.value]
})
provide(MetaInj, lookupTableMeta) provide(MetaInj, lookupTableMeta)
provide(CellUrlDisableOverlayInj, ref(true)) provide(CellUrlDisableOverlayInj, ref(true))

12
packages/nc-gui/composables/useColumnCreateStore.ts

@ -195,9 +195,15 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
try { try {
if (!(await validate())) return if (!(await validate())) return
} catch (e) { } catch (e) {
console.log(e) const errorMsgs = e.errorFields
console.trace() ?.map((e: any) => e.errors?.join(', '))
message.error(t('msg.error.formValidationFailed')) .filter(Boolean)
.join(', ')
if (errorMsgs) {
message.error(errorMsgs)
} else {
message.error(t('msg.error.formValidationFailed'))
}
return return
} }

34
packages/nc-gui/composables/useFieldQuery.ts

@ -0,0 +1,34 @@
import type { Ref } from 'vue'
import type { ViewType } from 'nocodb-sdk'
import { useState } from '#imports'
export function useFieldQuery(view: Ref<ViewType | undefined>) {
// initial search object
const emptyFieldQueryObj = {
field: '',
query: '',
}
// mapping view id (key) to corresponding emptyFieldQueryObj (value)
const searchMap = useState<Record<string, { field: string; query: string }>>('field-query-search-map', () => ({}))
// the fieldQueryObj under the current view
const search = useState<{ field: string; query: string }>('field-query-search', () => emptyFieldQueryObj)
// map current view id to emptyFieldQueryObj
if (view?.value?.id) {
searchMap.value[view!.value!.id] = search.value
}
// retrieve the fieldQueryObj of the given view id
// if it is not found in `searchMap`, init with emptyFieldQueryObj
const loadFieldQuery = (view: Ref<ViewType | undefined>) => {
if (!view.value?.id) return
if (!(view!.value!.id in searchMap.value)) {
searchMap.value[view!.value!.id!] = emptyFieldQueryObj
}
search.value = searchMap.value[view!.value!.id!]
}
return { search, loadFieldQuery }
}

19
packages/nc-gui/composables/useSmartsheetStore.ts

@ -1,7 +1,7 @@
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import type { FilterType, KanbanType, SortType, TableType, ViewType } from 'nocodb-sdk' import type { FilterType, KanbanType, SortType, TableType, ViewType } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { computed, reactive, ref, unref, useInjectionState, useNuxtApp, useProject } from '#imports' import { computed, ref, unref, useFieldQuery, useInjectionState, useNuxtApp, useProject } from '#imports'
const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState( const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState(
( (
@ -17,12 +17,7 @@ const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState(
const cellRefs = ref<HTMLTableDataCellElement[]>([]) const cellRefs = ref<HTMLTableDataCellElement[]>([])
// state const { search } = useFieldQuery(view)
// todo: move to grid view store
const search = reactive({
field: '',
query: '',
})
// getters // getters
const isLocked = computed(() => view.value?.lock_type === 'locked') const isLocked = computed(() => view.value?.lock_type === 'locked')
@ -36,21 +31,20 @@ const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState(
const xWhere = computed(() => { const xWhere = computed(() => {
let where let where
const col = const col =
(meta.value as TableType)?.columns?.find(({ id }) => id === search.field) || (meta.value as TableType)?.columns?.find(({ id }) => id === search.value.field) ||
(meta.value as TableType)?.columns?.find((v) => v.pv) (meta.value as TableType)?.columns?.find((v) => v.pv)
if (!col) return if (!col) return
if (!search.query.trim()) return if (!search.value.query.trim()) return
if (['text', 'string'].includes(sqlUi.value.getAbstractType(col)) && col.dt !== 'bigint') { if (['text', 'string'].includes(sqlUi.value.getAbstractType(col)) && col.dt !== 'bigint') {
where = `(${col.title},like,%${search.query.trim()}%)` where = `(${col.title},like,%${search.value.query.trim()}%)`
} else { } else {
where = `(${col.title},eq,${search.query.trim()})` where = `(${col.title},eq,${search.value.query.trim()})`
} }
return where return where
}) })
const isSqlView = computed(() => (meta.value as TableType)?.type === 'view') const isSqlView = computed(() => (meta.value as TableType)?.type === 'view')
const sorts = ref<SortType[]>(unref(initialSorts) ?? []) const sorts = ref<SortType[]>(unref(initialSorts) ?? [])
const nestedFilters = ref<FilterType[]>(unref(initialFilters) ?? []) const nestedFilters = ref<FilterType[]>(unref(initialFilters) ?? [])
@ -59,7 +53,6 @@ const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState(
meta, meta,
isLocked, isLocked,
$api, $api,
search,
xWhere, xWhere,
isPkAvail, isPkAvail,
isForm, isForm,

2
packages/nc-gui/lang/ar.json

@ -368,6 +368,8 @@
"setPrimary": "تعيين كقيمة أساسية", "setPrimary": "تعيين كقيمة أساسية",
"addRow": "إضافة صف جديد", "addRow": "إضافة صف جديد",
"saveRow": "حفظ الصف", "saveRow": "حفظ الصف",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "إدراج صف جديد", "insertRow": "إدراج صف جديد",
"deleteRow": "حذف الصف", "deleteRow": "حذف الصف",
"deleteSelectedRow": "حذف الصفوف المحددة", "deleteSelectedRow": "حذف الصفوف المحددة",

3
packages/nc-gui/lang/bn_IN.json

@ -368,6 +368,8 @@
"setPrimary": "পথমিক মন হিট করন", "setPrimary": "পথমিক মন হিট করন",
"addRow": "নতন সিত করন", "addRow": "নতন সিত করন",
"saveRow": "সিরকষণ করন", "saveRow": "সিরকষণ করন",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "নতন সিন", "insertRow": "নতন সিন",
"deleteRow": "সিন", "deleteRow": "সিন",
"deleteSelectedRow": "নিিত সিিন", "deleteSelectedRow": "নিিত সিিন",
@ -601,7 +603,6 @@
"tableDeleted": "Deleted table successfully", "tableDeleted": "Deleted table successfully",
"generatePublicShareableReadonlyBase": "Generate publicly shareable readonly base", "generatePublicShareableReadonlyBase": "Generate publicly shareable readonly base",
"deleteViewConfirmation": "Are you sure you want to delete this view?", "deleteViewConfirmation": "Are you sure you want to delete this view?",
"deleteTokenConfirmation": "Are you sure you want to delete this token?",
"deleteTableConfirmation": "Do you want to delete the table", "deleteTableConfirmation": "Do you want to delete the table",
"showM2mTables": "Show M2M Tables", "showM2mTables": "Show M2M Tables",
"deleteKanbanStackConfirmation": "Deleting this stack will also remove the select option `{stackToBeDeleted}` from the `{groupingField}`. The records will move to the uncategorized stack." "deleteKanbanStackConfirmation": "Deleting this stack will also remove the select option `{stackToBeDeleted}` from the `{groupingField}`. The records will move to the uncategorized stack."

2
packages/nc-gui/lang/da.json

@ -368,6 +368,8 @@
"setPrimary": "Indstil som primær værdi", "setPrimary": "Indstil som primær værdi",
"addRow": "Tilføj ny række", "addRow": "Tilføj ny række",
"saveRow": "Gem ro", "saveRow": "Gem ro",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Indsæt ny række", "insertRow": "Indsæt ny række",
"deleteRow": "DELETE ROW.", "deleteRow": "DELETE ROW.",
"deleteSelectedRow": "Slet de valgte rækker", "deleteSelectedRow": "Slet de valgte rækker",

2
packages/nc-gui/lang/de.json

@ -369,6 +369,8 @@
"setPrimary": "Als Primärwert festlegen", "setPrimary": "Als Primärwert festlegen",
"addRow": "Neue Zeile hinzufügen", "addRow": "Neue Zeile hinzufügen",
"saveRow": "Zeile speichern", "saveRow": "Zeile speichern",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Neue Zeile einfügen", "insertRow": "Neue Zeile einfügen",
"deleteRow": "Zeile löschen", "deleteRow": "Zeile löschen",
"deleteSelectedRow": "Ausgewählte Zeilen löschen", "deleteSelectedRow": "Ausgewählte Zeilen löschen",

2
packages/nc-gui/lang/es.json

@ -368,6 +368,8 @@
"setPrimary": "Establecido como clave primaria", "setPrimary": "Establecido como clave primaria",
"addRow": "Añadir nueva fila", "addRow": "Añadir nueva fila",
"saveRow": "Grabar la fila", "saveRow": "Grabar la fila",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Insertar nueva fila", "insertRow": "Insertar nueva fila",
"deleteRow": "Borrar fila", "deleteRow": "Borrar fila",
"deleteSelectedRow": "Eliminar filas seleccionadas", "deleteSelectedRow": "Eliminar filas seleccionadas",

2
packages/nc-gui/lang/fa.json

@ -368,6 +368,8 @@
"setPrimary": "تنظیم به عنوان مقدار اولیه", "setPrimary": "تنظیم به عنوان مقدار اولیه",
"addRow": "اضافه کردن ردیف جدید", "addRow": "اضافه کردن ردیف جدید",
"saveRow": "دخیره ردیف", "saveRow": "دخیره ردیف",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "وارد کردن ردیف جدید", "insertRow": "وارد کردن ردیف جدید",
"deleteRow": "حذف ردیف جدید", "deleteRow": "حذف ردیف جدید",
"deleteSelectedRow": "حذف ردیفهای انتخاب شده", "deleteSelectedRow": "حذف ردیفهای انتخاب شده",

2
packages/nc-gui/lang/fi.json

@ -368,6 +368,8 @@
"setPrimary": "Aseta ensisijainen arvo", "setPrimary": "Aseta ensisijainen arvo",
"addRow": "Lisää uusi rivi", "addRow": "Lisää uusi rivi",
"saveRow": "Tallenna rivi", "saveRow": "Tallenna rivi",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Lisää uusi rivi", "insertRow": "Lisää uusi rivi",
"deleteRow": "Poista rivi", "deleteRow": "Poista rivi",
"deleteSelectedRow": "Poista valitut rivit", "deleteSelectedRow": "Poista valitut rivit",

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

@ -368,6 +368,8 @@
"setPrimary": "Définir comme valeur primaire", "setPrimary": "Définir comme valeur primaire",
"addRow": "Ajouter une nouvelle ligne", "addRow": "Ajouter une nouvelle ligne",
"saveRow": "Enregistrer la ligne", "saveRow": "Enregistrer la ligne",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Insérer une nouvelle ligne", "insertRow": "Insérer une nouvelle ligne",
"deleteRow": "Supprimer la ligne", "deleteRow": "Supprimer la ligne",
"deleteSelectedRow": "Supprimer les lignes sélectionnées", "deleteSelectedRow": "Supprimer les lignes sélectionnées",

2
packages/nc-gui/lang/he.json

@ -368,6 +368,8 @@
"setPrimary": "להגדיר כערך ראשי", "setPrimary": "להגדיר כערך ראשי",
"addRow": "הוסף שורה חדשה", "addRow": "הוסף שורה חדשה",
"saveRow": "שמור שורה", "saveRow": "שמור שורה",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "הכנס שורה חדשה", "insertRow": "הכנס שורה חדשה",
"deleteRow": "מחק שורה", "deleteRow": "מחק שורה",
"deleteSelectedRow": "מחק את השורות שנבחרו", "deleteSelectedRow": "מחק את השורות שנבחרו",

2
packages/nc-gui/lang/hi.json

@ -368,6 +368,8 @@
"setPrimary": "पथमिक मय कप मट कर", "setPrimary": "पथमिक मय कप मट कर",
"addRow": "नई पि", "addRow": "नई पि",
"saveRow": "पि सह", "saveRow": "पि सह",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "नई पि", "insertRow": "नई पि",
"deleteRow": "पि हट", "deleteRow": "पि हट",
"deleteSelectedRow": "चयनित पि हट", "deleteSelectedRow": "चयनित पि हट",

2
packages/nc-gui/lang/hr.json

@ -368,6 +368,8 @@
"setPrimary": "Postavite kao primarnu vrijednost", "setPrimary": "Postavite kao primarnu vrijednost",
"addRow": "Dodaj novi red", "addRow": "Dodaj novi red",
"saveRow": "Spremanje retka", "saveRow": "Spremanje retka",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Umetnite novi red", "insertRow": "Umetnite novi red",
"deleteRow": "Brisanje retka", "deleteRow": "Brisanje retka",
"deleteSelectedRow": "Izbrišite odabrane retke", "deleteSelectedRow": "Izbrišite odabrane retke",

2
packages/nc-gui/lang/id.json

@ -368,6 +368,8 @@
"setPrimary": "Tetapkan sebagai nilai utama", "setPrimary": "Tetapkan sebagai nilai utama",
"addRow": "Tambahkan baris baru", "addRow": "Tambahkan baris baru",
"saveRow": "Hemat Baris", "saveRow": "Hemat Baris",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Masukkan baris baru.", "insertRow": "Masukkan baris baru.",
"deleteRow": "Hapus Baris", "deleteRow": "Hapus Baris",
"deleteSelectedRow": "Hapus baris yang dipilih", "deleteSelectedRow": "Hapus baris yang dipilih",

2
packages/nc-gui/lang/it.json

@ -368,6 +368,8 @@
"setPrimary": "Impostare come valore primario", "setPrimary": "Impostare come valore primario",
"addRow": "Aggiungi nuova riga", "addRow": "Aggiungi nuova riga",
"saveRow": "Salva riga", "saveRow": "Salva riga",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Inserisci nuova riga", "insertRow": "Inserisci nuova riga",
"deleteRow": "Elimina riga.", "deleteRow": "Elimina riga.",
"deleteSelectedRow": "Elimina righe selezionate", "deleteSelectedRow": "Elimina righe selezionate",

2
packages/nc-gui/lang/ja.json

@ -368,6 +368,8 @@
"setPrimary": "プライマリ値として設定", "setPrimary": "プライマリ値として設定",
"addRow": "行を追加", "addRow": "行を追加",
"saveRow": "行を保存", "saveRow": "行を保存",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "行を挿入", "insertRow": "行を挿入",
"deleteRow": "行を削除", "deleteRow": "行を削除",
"deleteSelectedRow": "選択行を削除", "deleteSelectedRow": "選択行を削除",

2
packages/nc-gui/lang/ko.json

@ -368,6 +368,8 @@
"setPrimary": "Primary value로 설정", "setPrimary": "Primary value로 설정",
"addRow": "행 추가", "addRow": "행 추가",
"saveRow": "행 저장", "saveRow": "행 저장",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "행 삽입", "insertRow": "행 삽입",
"deleteRow": "행 삭제", "deleteRow": "행 삭제",
"deleteSelectedRow": "선택한 행 삭제", "deleteSelectedRow": "선택한 행 삭제",

2
packages/nc-gui/lang/lv.json

@ -368,6 +368,8 @@
"setPrimary": "Uzstādīt kā primāro atslēgu", "setPrimary": "Uzstādīt kā primāro atslēgu",
"addRow": "Pievienot ierakstu", "addRow": "Pievienot ierakstu",
"saveRow": "Saglabāt ierakstu", "saveRow": "Saglabāt ierakstu",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Pievienot jaunu ierakstu", "insertRow": "Pievienot jaunu ierakstu",
"deleteRow": "Dzēst ierakstu", "deleteRow": "Dzēst ierakstu",
"deleteSelectedRow": "Dzēst izvēlētos ierakstus", "deleteSelectedRow": "Dzēst izvēlētos ierakstus",

2
packages/nc-gui/lang/nl.json

@ -368,6 +368,8 @@
"setPrimary": "Instellen als primaire waarde", "setPrimary": "Instellen als primaire waarde",
"addRow": "Nieuwe rij toevoegen", "addRow": "Nieuwe rij toevoegen",
"saveRow": "Sla rij op", "saveRow": "Sla rij op",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Voeg nieuwe rij toe", "insertRow": "Voeg nieuwe rij toe",
"deleteRow": "Verwijder rij", "deleteRow": "Verwijder rij",
"deleteSelectedRow": "Verwijder geselecteerde rijen", "deleteSelectedRow": "Verwijder geselecteerde rijen",

2
packages/nc-gui/lang/no.json

@ -368,6 +368,8 @@
"setPrimary": "Sett som primærverdi", "setPrimary": "Sett som primærverdi",
"addRow": "Legg til ny rad", "addRow": "Legg til ny rad",
"saveRow": "Lagre rad", "saveRow": "Lagre rad",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Sett inn ny rad", "insertRow": "Sett inn ny rad",
"deleteRow": "Slett rad", "deleteRow": "Slett rad",
"deleteSelectedRow": "Slett utvalgte rader", "deleteSelectedRow": "Slett utvalgte rader",

2
packages/nc-gui/lang/pl.json

@ -368,6 +368,8 @@
"setPrimary": "Ustaw jako wartość podstawowa", "setPrimary": "Ustaw jako wartość podstawowa",
"addRow": "Dodaj nowy rząd", "addRow": "Dodaj nowy rząd",
"saveRow": "Zapisz wiersz", "saveRow": "Zapisz wiersz",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Wstaw nowy rząd", "insertRow": "Wstaw nowy rząd",
"deleteRow": "Usuń rząd", "deleteRow": "Usuń rząd",
"deleteSelectedRow": "Usuń wybrane wiersze", "deleteSelectedRow": "Usuń wybrane wiersze",

2
packages/nc-gui/lang/pt.json

@ -368,6 +368,8 @@
"setPrimary": "Definido como valor primário", "setPrimary": "Definido como valor primário",
"addRow": "Adicionar nova linha", "addRow": "Adicionar nova linha",
"saveRow": "Salvar linha", "saveRow": "Salvar linha",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Insira a nova linha", "insertRow": "Insira a nova linha",
"deleteRow": "Excluir linha", "deleteRow": "Excluir linha",
"deleteSelectedRow": "Excluir linhas selecionadas", "deleteSelectedRow": "Excluir linhas selecionadas",

2
packages/nc-gui/lang/pt_BR.json

@ -368,6 +368,8 @@
"setPrimary": "Definido como valor primário", "setPrimary": "Definido como valor primário",
"addRow": "Adicionar nova linha", "addRow": "Adicionar nova linha",
"saveRow": "Salvar linha", "saveRow": "Salvar linha",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Insira a nova linha", "insertRow": "Insira a nova linha",
"deleteRow": "Excluir linha", "deleteRow": "Excluir linha",
"deleteSelectedRow": "Excluir linhas selecionadas", "deleteSelectedRow": "Excluir linhas selecionadas",

2
packages/nc-gui/lang/ru.json

@ -368,6 +368,8 @@
"setPrimary": "Установить в качестве основного значения", "setPrimary": "Установить в качестве основного значения",
"addRow": "Добавить новую строку", "addRow": "Добавить новую строку",
"saveRow": "Сохранить строку", "saveRow": "Сохранить строку",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Вставить новый строк", "insertRow": "Вставить новый строк",
"deleteRow": "Удалить строку", "deleteRow": "Удалить строку",
"deleteSelectedRow": "Удалить выбранные строки", "deleteSelectedRow": "Удалить выбранные строки",

2
packages/nc-gui/lang/sl.json

@ -368,6 +368,8 @@
"setPrimary": "Kot primarna vrednost", "setPrimary": "Kot primarna vrednost",
"addRow": "Dodaj novo vrstico", "addRow": "Dodaj novo vrstico",
"saveRow": "Shrani vrstico", "saveRow": "Shrani vrstico",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Vstavite novo vrstico", "insertRow": "Vstavite novo vrstico",
"deleteRow": "Izbriši vrstico", "deleteRow": "Izbriši vrstico",
"deleteSelectedRow": "Izbrišite izbrane vrstice", "deleteSelectedRow": "Izbrišite izbrane vrstice",

2
packages/nc-gui/lang/sv.json

@ -368,6 +368,8 @@
"setPrimary": "Ange som primärt värde", "setPrimary": "Ange som primärt värde",
"addRow": "Lägg till ny rad", "addRow": "Lägg till ny rad",
"saveRow": "Spara rad", "saveRow": "Spara rad",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Infoga ny rad", "insertRow": "Infoga ny rad",
"deleteRow": "Radera raden", "deleteRow": "Radera raden",
"deleteSelectedRow": "Ta bort valda rader", "deleteSelectedRow": "Ta bort valda rader",

2
packages/nc-gui/lang/th.json

@ -368,6 +368,8 @@
"setPrimary": "ตงคาเปนคาปฐมภ", "setPrimary": "ตงคาเปนคาปฐมภ",
"addRow": "เพมแถวใหม", "addRow": "เพมแถวใหม",
"saveRow": "บนทกแถว", "saveRow": "บนทกแถว",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "แทรกแถวใหม", "insertRow": "แทรกแถวใหม",
"deleteRow": "ลบแถว", "deleteRow": "ลบแถว",
"deleteSelectedRow": "ลบแถวทเลอก", "deleteSelectedRow": "ลบแถวทเลอก",

2
packages/nc-gui/lang/tr.json

@ -368,6 +368,8 @@
"setPrimary": "Birincil değer yap", "setPrimary": "Birincil değer yap",
"addRow": "Yeni satır ekle", "addRow": "Yeni satır ekle",
"saveRow": "Satırı kaydet", "saveRow": "Satırı kaydet",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Yeni Satır Ekle", "insertRow": "Yeni Satır Ekle",
"deleteRow": "Satırı Sil", "deleteRow": "Satırı Sil",
"deleteSelectedRow": "Seçilen Satırları Sil", "deleteSelectedRow": "Seçilen Satırları Sil",

74
packages/nc-gui/lang/uk.json

@ -16,7 +16,7 @@
"cancel": "Скасувати", "cancel": "Скасувати",
"submit": "Подавати", "submit": "Подавати",
"create": "Створювати", "create": "Створювати",
"duplicate": "Duplicate", "duplicate": "Дублювати",
"insert": "Вставляти", "insert": "Вставляти",
"delete": "Видаляти", "delete": "Видаляти",
"update": "Оновлення", "update": "Оновлення",
@ -56,19 +56,19 @@
"notification": "Сповіщення", "notification": "Сповіщення",
"reference": "Довідник", "reference": "Довідник",
"function": "Функція", "function": "Функція",
"confirm": "Confirm", "confirm": "Підтвердити",
"generate": "Generate", "generate": "Генерувати",
"copy": "Copy", "copy": "Копіювати",
"misc": "Miscellaneous", "misc": "Інше",
"lock": "Lock", "lock": "Блокувати",
"unlock": "Unlock", "unlock": "Розблокувати",
"credentials": "Credentials", "credentials": "Дані доступу",
"help": "Help", "help": "Довідка",
"questions": "Questions", "questions": "Питання",
"reachOut": "Reach out here", "reachOut": "Reach out here",
"betaNote": "This feature is currently in beta.", "betaNote": "Ця функція знаходиться в стадії бета-версії.",
"moreInfo": "More information can be found here", "moreInfo": "Тут можна знайти більше інформації",
"logs": "Logs", "logs": "Логи",
"groupingField": "Grouping Field" "groupingField": "Grouping Field"
}, },
"objects": { "objects": {
@ -84,8 +84,8 @@
"pages": "Сторінка", "pages": "Сторінка",
"record": "Рекорд", "record": "Рекорд",
"records": "Записи", "records": "Записи",
"webhook": "Webhook", "webhook": "Вебхук",
"webhooks": "Webhooks", "webhooks": "Вебхуки",
"view": "Погляд", "view": "Погляд",
"views": "Вигляд", "views": "Вигляд",
"viewType": { "viewType": {
@ -190,15 +190,15 @@
"headLogin": "Вхід | Нокодб", "headLogin": "Вхід | Нокодб",
"resetPassword": "Скинути пароль", "resetPassword": "Скинути пароль",
"teamAndSettings": "Team & Settings", "teamAndSettings": "Team & Settings",
"apiDocs": "API Docs", "apiDocs": "Документи API",
"importFromAirtable": "Import From Airtable", "importFromAirtable": "Імпортувати з Airtable",
"generateToken": "Generate Token", "generateToken": "Генерувати токен",
"APIsAndSupport": "APIs & Support", "APIsAndSupport": "API та Підтримка",
"helpCenter": "Help center", "helpCenter": "Довідковий центр",
"swaggerDocumentation": "Swagger Documentation", "swaggerDocumentation": "Документація Swagger",
"quickImportFrom": "Quick Import From", "quickImportFrom": "Швидкий імпорт з",
"quickImport": "Quick Import", "quickImport": "Швидкий імпорт",
"advancedSettings": "Advanced Settings", "advancedSettings": "Додаткові налаштування",
"codeSnippet": "Code Snippet" "codeSnippet": "Code Snippet"
}, },
"labels": { "labels": {
@ -220,7 +220,7 @@
"port": "Номер порту", "port": "Номер порту",
"username": "Ім'я користувача", "username": "Ім'я користувача",
"password": "Пароль", "password": "Пароль",
"schemaName": "Schema name", "schemaName": "Назва схеми",
"database": "База даних", "database": "База даних",
"action": "Дія", "action": "Дія",
"actions": "Акції", "actions": "Акції",
@ -268,25 +268,25 @@
"childColumn": "Дитяча колонка", "childColumn": "Дитяча колонка",
"onUpdate": "На оновлення", "onUpdate": "На оновлення",
"onDelete": "На видалі", "onDelete": "На видалі",
"account": "Account", "account": "Обліковий запис",
"language": "Language", "language": "Мова",
"primaryColor": "Primary Color", "primaryColor": "Основний колір",
"accentColor": "Accent Color", "accentColor": "Додатковий колір",
"customTheme": "Custom Theme", "customTheme": "Користувацька тема",
"requestDataSource": "Request a data source you need?", "requestDataSource": "Request a data source you need?",
"apiKey": "API Key", "apiKey": "API ключ",
"sharedBase": "Shared Base", "sharedBase": "Shared Base",
"importData": "Import Data", "importData": "Імпорт даних",
"importSecondaryViews": "Import Secondary Views", "importSecondaryViews": "Import Secondary Views",
"importRollupColumns": "Import Rollup Columns", "importRollupColumns": "Import Rollup Columns",
"importLookupColumns": "Import Lookup Columns", "importLookupColumns": "Import Lookup Columns",
"importAttachmentColumns": "Import Attachment Columns", "importAttachmentColumns": "Import Attachment Columns",
"importFormulaColumns": "Import Formula Columns", "importFormulaColumns": "Import Formula Columns",
"noData": "No Data", "noData": "Немає даних",
"goToDashboard": "Go to Dashboard", "goToDashboard": "Go to Dashboard",
"importing": "Importing", "importing": "Імпортування",
"flattenNested": "Flatten Nested", "flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed", "downloadAllowed": "Завантаження дозволене",
"weAreHiring": "We are Hiring!", "weAreHiring": "We are Hiring!",
"primaryKey": "Primary key", "primaryKey": "Primary key",
"hasMany": "has many", "hasMany": "has many",
@ -368,6 +368,8 @@
"setPrimary": "Встановлено як первинне значення", "setPrimary": "Встановлено як первинне значення",
"addRow": "Додати новий рядок", "addRow": "Додати новий рядок",
"saveRow": "Рятувати рядок", "saveRow": "Рятувати рядок",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Вставте новий рядок", "insertRow": "Вставте новий рядок",
"deleteRow": "Видалити рядок", "deleteRow": "Видалити рядок",
"deleteSelectedRow": "Видалити вибрані рядки", "deleteSelectedRow": "Видалити вибрані рядки",
@ -497,7 +499,7 @@
"excelURL": "Введіть URL-адресу Excel", "excelURL": "Введіть URL-адресу Excel",
"csvURL": "Введіть URL-адресу CSV", "csvURL": "Введіть URL-адресу CSV",
"footMsg": "# рядків, щоб розібрати для виведення даних", "footMsg": "# рядків, щоб розібрати для виведення даних",
"excelImport": "Лист (и) доступні для імпорту", "excelImport": "лист(и) доступні для імпорту",
"exportMetadata": "Ви хочете експортувати метадані з мета-таблиць?", "exportMetadata": "Ви хочете експортувати метадані з мета-таблиць?",
"importMetadata": "Ви хочете імпортувати метадані з мета-таблиць?", "importMetadata": "Ви хочете імпортувати метадані з мета-таблиць?",
"clearMetadata": "Ви хочете очистити метадані з мета-таблиць?", "clearMetadata": "Ви хочете очистити метадані з мета-таблиць?",

76
packages/nc-gui/lang/vi.json

@ -16,7 +16,7 @@
"cancel": "Hủy bỏ", "cancel": "Hủy bỏ",
"submit": "Nộp", "submit": "Nộp",
"create": "Tạo ra", "create": "Tạo ra",
"duplicate": "Duplicate", "duplicate": "Tạo bản sao",
"insert": "Chèn", "insert": "Chèn",
"delete": "Xóa bỏ", "delete": "Xóa bỏ",
"update": "Cập nhật", "update": "Cập nhật",
@ -56,20 +56,20 @@
"notification": "Thông báo", "notification": "Thông báo",
"reference": "Thẩm quyền giải quyết", "reference": "Thẩm quyền giải quyết",
"function": "Chức năng", "function": "Chức năng",
"confirm": "Confirm", "confirm": "Xác nhận",
"generate": "Generate", "generate": "Generate",
"copy": "Copy", "copy": "Sao Chép",
"misc": "Miscellaneous", "misc": "Các tùy chọn khác",
"lock": "Lock", "lock": "Khoá",
"unlock": "Unlock", "unlock": "Mở khoá",
"credentials": "Credentials", "credentials": "Thông tin đăng nhập",
"help": "Help", "help": "Trợ giúp",
"questions": "Questions", "questions": "Câu hỏi",
"reachOut": "Reach out here", "reachOut": "Tiếp cận sau",
"betaNote": "This feature is currently in beta.", "betaNote": "Tính năng này hiện không được hỗ trợ.",
"moreInfo": "More information can be found here", "moreInfo": "More information can be found here",
"logs": "Logs", "logs": "Nhật ký",
"groupingField": "Grouping Field" "groupingField": "Nhóm theo trường"
}, },
"objects": { "objects": {
"project": "Dự định", "project": "Dự định",
@ -189,20 +189,20 @@
"headCreateProject": "Tạo dự án |. NOCODB.", "headCreateProject": "Tạo dự án |. NOCODB.",
"headLogin": "Đăng nhập |. NOCODB.", "headLogin": "Đăng nhập |. NOCODB.",
"resetPassword": "Đặt lại mật khẩu của bạn", "resetPassword": "Đặt lại mật khẩu của bạn",
"teamAndSettings": "Team & Settings", "teamAndSettings": "Cài đặt nhóm",
"apiDocs": "API Docs", "apiDocs": "Văn bản API",
"importFromAirtable": "Import From Airtable", "importFromAirtable": "Nhập khẩu từ CSDL Airtable",
"generateToken": "Generate Token", "generateToken": "Tạo mã Token",
"APIsAndSupport": "APIs & Support", "APIsAndSupport": "APIS và Hỗ trợ",
"helpCenter": "Help center", "helpCenter": "Help center",
"swaggerDocumentation": "Swagger Documentation", "swaggerDocumentation": "Tài liệu Swagger",
"quickImportFrom": "Quick Import From", "quickImportFrom": "Kết nhập nhanh dữ liệu từ",
"quickImport": "Quick Import", "quickImport": "Quick Import",
"advancedSettings": "Advanced Settings", "advancedSettings": "Cài đặt Nâng cao",
"codeSnippet": "Code Snippet" "codeSnippet": "Thư viện mã"
}, },
"labels": { "labels": {
"createdBy": "Created By", "createdBy": "Đã tạo bởi",
"notifyVia": "Thông báo qua", "notifyVia": "Thông báo qua",
"projName": "Tên dự án", "projName": "Tên dự án",
"tableName": "Tên bảng.", "tableName": "Tên bảng.",
@ -220,7 +220,7 @@
"port": "Cổng số", "port": "Cổng số",
"username": "tên tài khoản", "username": "tên tài khoản",
"password": "Mật khẩu", "password": "Mật khẩu",
"schemaName": "Schema name", "schemaName": "Tên lược đồ",
"database": "Cơ sở dữ liệu", "database": "Cơ sở dữ liệu",
"action": "Hoạt động", "action": "Hoạt động",
"actions": "Hành động", "actions": "Hành động",
@ -233,7 +233,7 @@
"where": "Ở đâu", "where": "Ở đâu",
"cache": "Cache.", "cache": "Cache.",
"chat": "Trò chuyện", "chat": "Trò chuyện",
"email": "E-mail", "email": "Thư điện tử",
"storage": "Kho", "storage": "Kho",
"uiAcl": "UI-ACL", "uiAcl": "UI-ACL",
"models": "Mô hình", "models": "Mô hình",
@ -258,7 +258,7 @@
"bookDemo": "Đặt một bản demo miễn phí", "bookDemo": "Đặt một bản demo miễn phí",
"getAnswered": "Nhận câu hỏi của bạn được trả lời", "getAnswered": "Nhận câu hỏi của bạn được trả lời",
"joinDiscord": "Tham gia thông minh", "joinDiscord": "Tham gia thông minh",
"joinCommunity": "Join NocoDB Community", "joinCommunity": "Tham gia cộng đồng",
"joinReddit": "Tham gia /r/NocoDB", "joinReddit": "Tham gia /r/NocoDB",
"followNocodb": "Theo dõi NocoDB" "followNocodb": "Theo dõi NocoDB"
}, },
@ -268,21 +268,21 @@
"childColumn": "Cột trẻ con.", "childColumn": "Cột trẻ con.",
"onUpdate": "Trên bản cập nhật", "onUpdate": "Trên bản cập nhật",
"onDelete": "Trên xóa", "onDelete": "Trên xóa",
"account": "Account", "account": "Tài khoản",
"language": "Language", "language": "Language",
"primaryColor": "Primary Color", "primaryColor": "Màu chính",
"accentColor": "Accent Color", "accentColor": "Màu phụ",
"customTheme": "Custom Theme", "customTheme": "Giao diện tùy chỉnh",
"requestDataSource": "Request a data source you need?", "requestDataSource": "Request a data source you need?",
"apiKey": "API Key", "apiKey": "API Key",
"sharedBase": "Shared Base", "sharedBase": "Shared Base",
"importData": "Import Data", "importData": "Kết nhập dữ liệu",
"importSecondaryViews": "Import Secondary Views", "importSecondaryViews": "Kết nhập Hiển thị thứ hai",
"importRollupColumns": "Import Rollup Columns", "importRollupColumns": "Import Rollup Columns",
"importLookupColumns": "Import Lookup Columns", "importLookupColumns": "Import Lookup Columns",
"importAttachmentColumns": "Import Attachment Columns", "importAttachmentColumns": "Kết nhập cột tệp đính kèm",
"importFormulaColumns": "Import Formula Columns", "importFormulaColumns": "Kết nhập cột công thức",
"noData": "No Data", "noData": "Không có dữ liệu",
"goToDashboard": "Go to Dashboard", "goToDashboard": "Go to Dashboard",
"importing": "Importing", "importing": "Importing",
"flattenNested": "Flatten Nested", "flattenNested": "Flatten Nested",
@ -368,6 +368,8 @@
"setPrimary": "Đặt dưới dạng giá trị chính", "setPrimary": "Đặt dưới dạng giá trị chính",
"addRow": "Thêm hàng mới", "addRow": "Thêm hàng mới",
"saveRow": "Lưu hàng.", "saveRow": "Lưu hàng.",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Chèn hàng mới", "insertRow": "Chèn hàng mới",
"deleteRow": "Xóa hàng", "deleteRow": "Xóa hàng",
"deleteSelectedRow": "Xóa các hàng đã chọn", "deleteSelectedRow": "Xóa các hàng đã chọn",
@ -424,13 +426,13 @@
"erd": { "erd": {
"showColumns": "Show Columns", "showColumns": "Show Columns",
"showPkAndFk": "Show Primary and Foreign Keys", "showPkAndFk": "Show Primary and Foreign Keys",
"showSqlViews": "Show SQL Views", "showSqlViews": "Hiển thị truy vấn SQL",
"showMMTables": "Show Many to Many tables", "showMMTables": "Show Many to Many tables",
"showJunctionTableNames": "Show Junction Table Names" "showJunctionTableNames": "Show Junction Table Names"
}, },
"kanban": { "kanban": {
"collapseStack": "Collapse Stack", "collapseStack": "Collapse Stack",
"deleteStack": "Delete Stack", "deleteStack": "Xóa ngăn xếp",
"stackedBy": "Stacked By", "stackedBy": "Stacked By",
"chooseGroupingField": "Choose a Grouping Field", "chooseGroupingField": "Choose a Grouping Field",
"addOrEditStack": "Add / Edit Stack" "addOrEditStack": "Add / Edit Stack"

2
packages/nc-gui/lang/zh-Hans.json

@ -368,6 +368,8 @@
"setPrimary": "设置为主要值", "setPrimary": "设置为主要值",
"addRow": "添加新行", "addRow": "添加新行",
"saveRow": "保存行", "saveRow": "保存行",
"saveAndExit": "保存并退出",
"saveAndStay": "Save & Stay",
"insertRow": "插入新行", "insertRow": "插入新行",
"deleteRow": "删除行", "deleteRow": "删除行",
"deleteSelectedRow": "删除所选行", "deleteSelectedRow": "删除所选行",

298
packages/nc-gui/lang/zh-Hant.json

@ -6,7 +6,7 @@
"close": "關閉", "close": "關閉",
"yes": "是", "yes": "是",
"no": "否", "no": "否",
"ok": "OK", "ok": "確認",
"and": "和", "and": "和",
"or": "或", "or": "或",
"add": "新增", "add": "新增",
@ -59,23 +59,23 @@
"confirm": "確認", "confirm": "確認",
"generate": "Generate", "generate": "Generate",
"copy": "複製", "copy": "複製",
"misc": "其他", "misc": "Miscellaneous",
"lock": "鎖定", "lock": "鎖定",
"unlock": "解鎖", "unlock": "解鎖",
"credentials": "憑證", "credentials": "憑證",
"help": "幫助", "help": "幫助",
"questions": "問題", "questions": "問題",
"reachOut": "Reach out here", "reachOut": "Reach out here",
"betaNote": "此功能還在測試中", "betaNote": "此功能目前是 Beta 測試版",
"moreInfo": "More information can be found here", "moreInfo": "更多資訊能在這裡找到",
"logs": "Logs", "logs": "日誌",
"groupingField": "Grouping Field" "groupingField": "Grouping Field"
}, },
"objects": { "objects": {
"project": "專案", "project": "項目",
"projects": "全部專案", "projects": "項目",
"table": "資料表", "table": "表",
"tables": "全部資料表", "tables": "表",
"field": "欄位", "field": "欄位",
"fields": "欄位", "fields": "欄位",
"column": "列", "column": "列",
@ -90,7 +90,7 @@
"views": "所有檢視", "views": "所有檢視",
"viewType": { "viewType": {
"grid": "網格", "grid": "網格",
"gallery": "圖庫", "gallery": "相簿",
"form": "表單", "form": "表單",
"kanban": "看板", "kanban": "看板",
"calendar": "日曆" "calendar": "日曆"
@ -100,19 +100,19 @@
"role": "角色", "role": "角色",
"roles": "角色", "roles": "角色",
"roleType": { "roleType": {
"owner": "有者", "owner": "有者",
"creator": "創造者", "creator": "創造者",
"editor": "編輯", "editor": "編輯",
"commenter": "評論者", "commenter": "評論者",
"viewer": "檢視者", "viewer": "檢視者",
"orgLevelCreator": "組織級建立者", "orgLevelCreator": "組織級建立者",
"orgLevelViewer": "組織級檢視者" "orgLevelViewer": "組織級檢視者"
}, },
"sqlVIew": "SQL View" "sqlVIew": "SQL View"
}, },
"datatype": { "datatype": {
"ID": "ID", "ID": "ID",
"ForeignKey": "外", "ForeignKey": "外鑰匙",
"SingleLineText": "單行文本", "SingleLineText": "單行文本",
"LongText": "長篇文章", "LongText": "長篇文章",
"Attachment": "附件", "Attachment": "附件",
@ -134,8 +134,8 @@
"Rating": "評分", "Rating": "評分",
"Formula": "公式", "Formula": "公式",
"Rollup": "捲起", "Rollup": "捲起",
"Count": "數", "Count": "數",
"Lookup": "查找", "Lookup": "抬頭",
"DateTime": "日期時間", "DateTime": "日期時間",
"CreateTime": "創建時間", "CreateTime": "創建時間",
"LastModifiedTime": "最後修改時間", "LastModifiedTime": "最後修改時間",
@ -192,11 +192,11 @@
"teamAndSettings": "團隊 & 設定", "teamAndSettings": "團隊 & 設定",
"apiDocs": "API 說明文件", "apiDocs": "API 說明文件",
"importFromAirtable": "從 Airtable 匯入", "importFromAirtable": "從 Airtable 匯入",
"generateToken": "Generate Token", "generateToken": "產生 Token",
"APIsAndSupport": "APIs 與支援", "APIsAndSupport": "APIs & Support",
"helpCenter": "幫助中心", "helpCenter": "幫助中心",
"swaggerDocumentation": "Swagger 文件", "swaggerDocumentation": "Swagger 文件",
"quickImportFrom": "Quick Import From", "quickImportFrom": "快速匯入從",
"quickImport": "快速匯入", "quickImport": "快速匯入",
"advancedSettings": "進階設定", "advancedSettings": "進階設定",
"codeSnippet": "程式碼片段" "codeSnippet": "程式碼片段"
@ -204,16 +204,16 @@
"labels": { "labels": {
"createdBy": "Created By", "createdBy": "Created By",
"notifyVia": "透過...通知", "notifyVia": "透過...通知",
"projName": "專案名", "projName": "項目名",
"tableName": "資料表名稱", "tableName": "表名稱",
"viewName": "查看名稱", "viewName": "檢視名稱",
"viewLink": "查看鏈接", "viewLink": "查看鏈接",
"columnName": "欄位名稱", "columnName": "名稱",
"columnType": "欄位類型", "columnType": "類型",
"roleName": "角色名稱", "roleName": "角色名稱",
"roleDescription": "角色描述", "roleDescription": "角色描述",
"databaseType": "數據庫類別", "databaseType": "鍵入數據庫",
"lengthValue": "長度 / 值", "lengthValue": "長度/值",
"dbType": "資料庫類型", "dbType": "資料庫類型",
"sqliteFile": "SQLite 檔案", "sqliteFile": "SQLite 檔案",
"hostAddress": "主機位址", "hostAddress": "主機位址",
@ -221,7 +221,7 @@
"username": "使用者名稱", "username": "使用者名稱",
"password": "密碼", "password": "密碼",
"schemaName": "Schema 名稱", "schemaName": "Schema 名稱",
"database": "資料庫", "database": "數據庫",
"action": "行動", "action": "行動",
"actions": "行動", "actions": "行動",
"operation": "操作", "operation": "操作",
@ -231,7 +231,7 @@
"authentication": "驗證", "authentication": "驗證",
"token": "權杖", "token": "權杖",
"where": "在哪裡", "where": "在哪裡",
"cache": "快取", "cache": "緩存",
"chat": "聊天", "chat": "聊天",
"email": "電子郵件", "email": "電子郵件",
"storage": "貯存", "storage": "貯存",
@ -249,13 +249,13 @@
"requriedCa": "必填 - CA", "requriedCa": "必填 - CA",
"requriedIdentity": "必填 - IDENTITY", "requriedIdentity": "必填 - IDENTITY",
"inflection": { "inflection": {
"tableName": "屈折 - 資料表名稱", "tableName": "屈折 - 表名稱",
"columnName": "屈折 - 欄位名稱" "columnName": "屈折 - 欄位名稱"
}, },
"community": { "community": {
"starUs1": "在 Github 上", "starUs1": "在 Github 上",
"starUs2": "幫我們按讚", "starUs2": "幫我們按讚",
"bookDemo": "預免費 Demo", "bookDemo": "預免費 Demo",
"getAnswered": "解惑您的問題", "getAnswered": "解惑您的問題",
"joinDiscord": "加入 Discord", "joinDiscord": "加入 Discord",
"joinCommunity": "加入 NocoDB 社群", "joinCommunity": "加入 NocoDB 社群",
@ -265,10 +265,10 @@
"docReference": "文件參考文獻", "docReference": "文件參考文獻",
"selectUserRole": "選擇使用者角色", "selectUserRole": "選擇使用者角色",
"childTable": "子表格", "childTable": "子表格",
"childColumn": "子欄", "childColumn": "子欄",
"onUpdate": "更新", "onUpdate": "更新",
"onDelete": "在刪除", "onDelete": "在刪除",
"account": "帳戶", "account": "Account",
"language": "語言", "language": "語言",
"primaryColor": "Primary Color", "primaryColor": "Primary Color",
"accentColor": "Accent Color", "accentColor": "Accent Color",
@ -277,13 +277,13 @@
"apiKey": "API Key", "apiKey": "API Key",
"sharedBase": "Shared Base", "sharedBase": "Shared Base",
"importData": "匯入資料", "importData": "匯入資料",
"importSecondaryViews": "匯入 Secondary Views", "importSecondaryViews": "Import Secondary Views",
"importRollupColumns": "匯入 Rollup 欄位", "importRollupColumns": "Import Rollup Columns",
"importLookupColumns": "匯入 Lookup 欄位", "importLookupColumns": "Import Lookup Columns",
"importAttachmentColumns": "匯入 Attachment 欄位", "importAttachmentColumns": "Import Attachment Columns",
"importFormulaColumns": "Import Formula Columns", "importFormulaColumns": "Import Formula Columns",
"noData": "目前沒有資料", "noData": "沒有資料",
"goToDashboard": "Go to Dashboard", "goToDashboard": "前往儀表板",
"importing": "匯入中", "importing": "匯入中",
"flattenNested": "Flatten Nested", "flattenNested": "Flatten Nested",
"downloadAllowed": "允許下載", "downloadAllowed": "允許下載",
@ -291,16 +291,16 @@
"primaryKey": "主鍵", "primaryKey": "主鍵",
"hasMany": "has many", "hasMany": "has many",
"belongsTo": "belongs to", "belongsTo": "belongs to",
"manyToMany": "have many to many relation", "manyToMany": "有多對多關聯",
"extraConnectionParameters": "Extra connection parameters", "extraConnectionParameters": "Extra connection parameters",
"commentsOnly": "Comments only", "commentsOnly": "Comments only",
"documentation": "文件", "documentation": "Documentation",
"subscribeNewsletter": "Subscribe to our weekly newsletter", "subscribeNewsletter": "Subscribe to our weekly newsletter",
"signUpWithGoogle": "使用 Google 帳號註冊", "signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "使用 Google 帳號登入", "signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service", "agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!", "welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url" "inviteOnlySignup": "只接受使用邀請連結進行註冊"
}, },
"activity": { "activity": {
"createProject": "建立專案", "createProject": "建立專案",
@ -313,7 +313,7 @@
"deleteProject": "刪除專案", "deleteProject": "刪除專案",
"refreshProject": "重新整理專案", "refreshProject": "重新整理專案",
"saveProject": "儲存專案", "saveProject": "儲存專案",
"deleteKanbanStack": "刪除 stack?", "deleteKanbanStack": "Delete stack?",
"createProjectExtended": { "createProjectExtended": {
"extDB": "連線至外部資料庫來建立", "extDB": "連線至外部資料庫來建立",
"excel": "從 Excel 建立專案", "excel": "從 Excel 建立專案",
@ -332,10 +332,10 @@
"projInfo": "複製專案資訊", "projInfo": "複製專案資訊",
"themes": "主題" "themes": "主題"
}, },
"sort": "排序", "sort": "種類",
"addSort": "加排序選項", "addSort": "加排序選項",
"filter": "篩選", "filter": "篩選",
"addFilter": "加過濾器", "addFilter": "加過濾器",
"share": "分享", "share": "分享",
"shareBase": { "shareBase": {
"disable": "禁用共享基礎", "disable": "禁用共享基礎",
@ -352,7 +352,7 @@
"deleteUser": "從專案中刪除使用者", "deleteUser": "從專案中刪除使用者",
"resendInvite": "重新發送邀請電子郵件", "resendInvite": "重新發送邀請電子郵件",
"copyInviteURL": "複製邀請連結", "copyInviteURL": "複製邀請連結",
"copyPasswordResetURL": "Copy password reset URL", "copyPasswordResetURL": "複製重設密碼連結",
"newRole": "新角色", "newRole": "新角色",
"reloadRoles": "重新載入角色", "reloadRoles": "重新載入角色",
"nextPage": "下一頁", "nextPage": "下一頁",
@ -360,14 +360,16 @@
"nextRecord": "下一步記錄", "nextRecord": "下一步記錄",
"previousRecord": "之前的紀錄", "previousRecord": "之前的紀錄",
"copyApiURL": "複製 API 網址", "copyApiURL": "複製 API 網址",
"createTable": "建立資料表", "createTable": "表創造",
"refreshTable": "表刷新", "refreshTable": "表刷新",
"renameTable": "重命名資料表", "renameTable": "重命名",
"deleteTable": "刪除資料表", "deleteTable": "刪除",
"addField": "將新欄位增加到此資料表", "addField": "將新字段添加到此表",
"setPrimary": "設置為主要值", "setPrimary": "設置為主要值",
"addRow": "新增行", "addRow": "新增行",
"saveRow": "儲存行", "saveRow": "儲存行",
"saveAndExit": "儲存並結束",
"saveAndStay": "Save & Stay",
"insertRow": "插入新行", "insertRow": "插入新行",
"deleteRow": "刪除行", "deleteRow": "刪除行",
"deleteSelectedRow": "刪除所選行", "deleteSelectedRow": "刪除所選行",
@ -389,12 +391,12 @@
"copyView": "複製檢視", "copyView": "複製檢視",
"renameView": "重新命名檢視", "renameView": "重新命名檢視",
"deleteView": "刪除檢視", "deleteView": "刪除檢視",
"createGrid": "創建網格視", "createGrid": "創建網格視",
"createGallery": "創建畫廊視圖", "createGallery": "創建相簿檢視",
"createCalendar": "創建日曆視", "createCalendar": "創建日曆視",
"createKanban": "創建尋呼視圖", "createKanban": "創建看板檢視",
"createForm": "創建表單視", "createForm": "創建表單視",
"showSystemFields": "顯示系統欄位", "showSystemFields": "顯示系統字段",
"copyUrl": "複製網址", "copyUrl": "複製網址",
"openTab": "開啟新分頁", "openTab": "開啟新分頁",
"iFrame": "複製嵌入式 HTML 程式碼", "iFrame": "複製嵌入式 HTML 程式碼",
@ -412,9 +414,9 @@
"sponsorUs": "贊助我們", "sponsorUs": "贊助我們",
"sendEmail": "傳送電子郵件", "sendEmail": "傳送電子郵件",
"addUserToProject": "Add user to project", "addUserToProject": "Add user to project",
"getApiSnippet": "Get API Snippet", "getApiSnippet": "取得 API 程式碼片段",
"clearCell": "Clear cell", "clearCell": "清除儲存格",
"addFilterGroup": "Add Filter Group", "addFilterGroup": "增加過濾組",
"linkRecord": "Link record", "linkRecord": "Link record",
"addNewRecord": "Add new record", "addNewRecord": "Add new record",
"useConnectionUrl": "Use Connection URL", "useConnectionUrl": "Use Connection URL",
@ -425,7 +427,7 @@
"showColumns": "顯示欄位", "showColumns": "顯示欄位",
"showPkAndFk": "顯示主鍵與外鍵", "showPkAndFk": "顯示主鍵與外鍵",
"showSqlViews": "Show SQL Views", "showSqlViews": "Show SQL Views",
"showMMTables": "Show Many to Many tables", "showMMTables": "顯示多對多資料表",
"showJunctionTableNames": "Show Junction Table Names" "showJunctionTableNames": "Show Junction Table Names"
}, },
"kanban": { "kanban": {
@ -446,13 +448,13 @@
"dark": "它確實有黑色(^⇧b)", "dark": "它確實有黑色(^⇧b)",
"light": "它是黑色嗎?(^⇧b)" "light": "它是黑色嗎?(^⇧b)"
}, },
"addTable": "建立資料表", "addTable": "添加新表",
"inviteMore": "邀請更多使用者", "inviteMore": "邀請更多用戶",
"toggleNavDraw": "切換導航抽屜", "toggleNavDraw": "切換導航抽屜",
"reloadApiToken": "重新載入 API 權杖", "reloadApiToken": "重新載入 API 權杖",
"generateNewApiToken": "產生新 API 權杖", "generateNewApiToken": "產生新 API 權杖",
"addRole": "添加新角色", "addRole": "添加新角色",
"reloadList": "重新載列表", "reloadList": "重新載列表",
"metaSync": "同步中繼資料", "metaSync": "同步中繼資料",
"sqlMigration": "重新加載遷移", "sqlMigration": "重新加載遷移",
"updateRestart": "更新並重新啟動", "updateRestart": "更新並重新啟動",
@ -468,15 +470,15 @@
"projName": "輸入專案名稱", "projName": "輸入專案名稱",
"password": { "password": {
"enter": "輸入密碼", "enter": "輸入密碼",
"current": "前密碼", "current": "前密碼",
"new": "新密碼", "new": "新密碼",
"save": "儲存密碼", "save": "儲存密碼",
"confirm": "確認新密碼" "confirm": "確認新密碼"
}, },
"searchProjectTree": "搜索專案樹", "searchProjectTree": "搜索",
"searchFields": "搜索欄位", "searchFields": "搜索字段",
"searchColumn": "搜索 {search} 列", "searchColumn": "搜索{search}列",
"searchApps": "搜索應用程", "searchApps": "搜索應用程",
"searchModels": "搜索模型", "searchModels": "搜索模型",
"noItemsFound": "未找到任何項目", "noItemsFound": "未找到任何項目",
"defaultValue": "預設值", "defaultValue": "預設值",
@ -487,13 +489,13 @@
"msg": { "msg": {
"info": { "info": {
"roles": { "roles": {
"orgCreator": "建立者可以建立專案與存取任何受邀請的專案.", "orgCreator": "建立者可以建立專案與存取任何受邀請的專案",
"orgViewer": "檢視者不可建立新專案但可以存取任何受邀請的專案." "orgViewer": "檢視者不能建立專案但可以存取任何受邀請的專案"
}, },
"footerInfo": "每頁行駛", "footerInfo": "每頁行駛",
"upload": "選擇檔案以上傳", "upload": "選擇檔案以上傳",
"upload_sub": "或拖放檔案", "upload_sub": "或拖放檔案",
"excelSupport": "支:.xls,.xlsx,.xlsm,.ods,.ots", "excelSupport": "支:.xls,.xlsx,.xlsm,.ods,.ots",
"excelURL": "輸入 Excel 檔案 URL", "excelURL": "輸入 Excel 檔案 URL",
"csvURL": "輸入 CSV 檔案 URL", "csvURL": "輸入 CSV 檔案 URL",
"footMsg": "要解析為推斷數據類型的行數", "footMsg": "要解析為推斷數據類型的行數",
@ -506,34 +508,34 @@
"startProject": "你想啟動這個專案嗎?", "startProject": "你想啟動這個專案嗎?",
"restartProject": "你想重新啟動專案嗎?", "restartProject": "你想重新啟動專案嗎?",
"deleteProject": "你想刪除這個專案嗎?", "deleteProject": "你想刪除這個專案嗎?",
"shareBasePrivate": "產生公開享的 Readonly Base", "shareBasePrivate": "產生公開享的 Readonly Base",
"shareBasePublic": "網路上的任何人都可以查看", "shareBasePublic": "網路上的任何人都可以查看",
"userInviteNoSMTP": "看起來你還沒有配置郵件!請複上面的邀請鏈接並將其發送給", "userInviteNoSMTP": "看起來你還沒有配置郵件!請複上面的邀請鏈接並將其發送給",
"dragDropHide": "在此處拖放欄位以隱藏", "dragDropHide": "在此處拖放字段以隱藏",
"formInput": "輸入表單輸入標籤", "formInput": "輸入表單輸入標籤",
"formHelpText": "添加一些幫助文本", "formHelpText": "添加一些幫助文本",
"onlyCreator": "僅建立者可見", "onlyCreator": "僅建立者可見",
"formDesc": "添加表單描述", "formDesc": "添加表單描述",
"beforeEnablePwd": "使用密碼限制存取權限", "beforeEnablePwd": "使用密碼限制存取權限",
"afterEnablePwd": "存取受密碼限制", "afterEnablePwd": "存取受密碼限制",
"privateLink": "此檢視通過私人連結共享", "privateLink": "此檢視通過私人連結共享",
"privateLinkAdditionalInfo": "具有私有連結的人只能看到此檢視中可見的儲存格", "privateLinkAdditionalInfo": "具有私有連結的人只能看到此檢視中可見的儲存格",
"afterFormSubmitted": "表格提交後", "afterFormSubmitted": "表格提交後",
"apiOptions": "存取專案方式", "apiOptions": "存取專案方式",
"submitAnotherForm": "顯示 '提交另一個表格' 按鈕", "submitAnotherForm": "顯示“提交另一個表格”按鈕",
"showBlankForm": "5 秒後顯示空白表格", "showBlankForm": "5 秒後顯示空白表格",
"emailForm": "發電子郵件給我", "emailForm": "發電子郵件給我",
"showSysFields": "顯示系統欄位", "showSysFields": "顯示系統字段",
"filterAutoApply": "自動申請", "filterAutoApply": "自動申請",
"showMessage": "顯示此消息", "showMessage": "顯示此消息",
"viewNotShared": "當前視不共享!", "viewNotShared": "當前視不共享!",
"showAllViews": "顯示此表的所有共享視圖", "showAllViews": "顯示此表的所有共享視圖",
"collabView": "具有編輯權限或更高的合作者可以更改視圖配置。", "collabView": "具有編輯權限或更高的合作者可以更改視圖配置。",
"lockedView": "沒有人可以編輯視圖配置,直到它被解鎖。", "lockedView": "沒有人可以編輯視圖配置,直到它被解鎖。",
"personalView": "只有您可以編輯視圖配置。默認情況下,其他合作者的個人視圖隱藏。", "personalView": "只有您可以編輯視圖配置。默認情況下,其他合作者的個人視圖隱藏。",
"ownerDesc": "可以添加/刪除創建者。和完整編輯資料庫結構和欄位。", "ownerDesc": "可以添加/刪除創建者。和完整編輯數據庫結構和字段。",
"creatorDesc": "可以完全編輯資料庫結構和值。", "creatorDesc": "可以完全編輯數據庫結構和值。",
"editorDesc": "可以編輯記錄但無法更改資料庫/欄位的結構。", "editorDesc": "可以編輯記錄但無法更改數據庫/字段的結構。",
"commenterDesc": "可以查看和評論記錄,但無法編輯任何內容", "commenterDesc": "可以查看和評論記錄,但無法編輯任何內容",
"viewerDesc": "可以查看記錄但無法編輯任何內容", "viewerDesc": "可以查看記錄但無法編輯任何內容",
"addUser": "新增使用者", "addUser": "新增使用者",
@ -551,7 +553,7 @@
}, },
"sponsor": { "sponsor": {
"header": "你可以幫助我們!", "header": "你可以幫助我們!",
"message": "我們是一個小型團隊,全職打造 NocoDB 並且開源程式碼。我們相信像 NocoDB 這樣的工具應該在網際網路上自由提供給每位問題解決者。" "message": "我們是一支小型團隊,全職工作,使Nocodb開放來源。我們相信一個像Nocodb這樣的工具應該在互聯網上的每個問題求解器上自由提供。"
}, },
"loginMsg": "登入 NocoDB", "loginMsg": "登入 NocoDB",
"passwordRecovery": { "passwordRecovery": {
@ -568,16 +570,16 @@
"dontHaveAccount": "沒有帳號?" "dontHaveAccount": "沒有帳號?"
}, },
"addView": { "addView": {
"grid": "加入網格檢視", "grid": "加入網格檢視",
"gallery": "加入圖庫檢視表", "gallery": "加入相簿檢視",
"form": "加入表單檢視", "form": "加入表單檢視",
"kanban": "加入看板檢視", "kanban": "加入看板檢視",
"calendar": "加入日曆檢視" "calendar": "加入日曆檢視"
}, },
"tablesMetadataInSync": "表元數據同步", "tablesMetadataInSync": "表元數據同步",
"addMultipleUsers": "您可以添加多個逗號(,)分隔的電子郵件", "addMultipleUsers": "您可以添加多個逗號(,)分隔的電子郵件",
"enterTableName": "輸入表名", "enterTableName": "輸入表名",
"addDefaultColumns": "建立預設欄位", "addDefaultColumns": "添加默認列",
"tableNameInDb": "數據庫中保存的表名", "tableNameInDb": "數據庫中保存的表名",
"airtable": { "airtable": {
"credentials": "Where to find this?" "credentials": "Where to find this?"
@ -591,18 +593,18 @@
"copiedToClipboard": "複製到剪貼簿", "copiedToClipboard": "複製到剪貼簿",
"requriedFieldsCantBeMoved": "Required field can't be moved", "requriedFieldsCantBeMoved": "Required field can't be moved",
"updateNotAllowedWithoutPK": "Update not allowed for table which doesn't have primary key", "updateNotAllowedWithoutPK": "Update not allowed for table which doesn't have primary key",
"autoIncFieldNotEditable": "Auto increment field is not editable", "autoIncFieldNotEditable": "自增欄位不可編輯",
"editingPKnotSupported": "Editing primary key not supported", "editingPKnotSupported": "不支援編輯主鍵",
"deletedCache": "Deleted cache successfully", "deletedCache": "刪除快取成功",
"cacheEmpty": "快取是空的", "cacheEmpty": "快取是空的",
"exportedCache": "Exported Cache Successfully", "exportedCache": "Exported Cache Successfully",
"valueAlreadyInList": "This value is already in the list", "valueAlreadyInList": "此值已在列表中",
"noColumnsToUpdate": "No columns to update", "noColumnsToUpdate": "No columns to update",
"tableDeleted": "Deleted table successfully", "tableDeleted": "刪除資料表成功",
"generatePublicShareableReadonlyBase": "Generate publicly shareable readonly base", "generatePublicShareableReadonlyBase": "Generate publicly shareable readonly base",
"deleteViewConfirmation": "Are you sure you want to delete this view?", "deleteViewConfirmation": "是否確定要刪除此檢視?",
"deleteTableConfirmation": "Do you want to delete the table", "deleteTableConfirmation": "你想刪除此資料表",
"showM2mTables": "顯示 M2M 資料表", "showM2mTables": "顯示多對多資料表",
"deleteKanbanStackConfirmation": "Deleting this stack will also remove the select option `{stackToBeDeleted}` from the `{groupingField}`. The records will move to the uncategorized stack." "deleteKanbanStackConfirmation": "Deleting this stack will also remove the select option `{stackToBeDeleted}` from the `{groupingField}`. The records will move to the uncategorized stack."
}, },
"error": { "error": {
@ -619,51 +621,51 @@
"passwdRequired": "密碼為必填", "passwdRequired": "密碼為必填",
"passwdLength": "您的密碼應至少有 8 個字元", "passwdLength": "您的密碼應至少有 8 個字元",
"passwdMismatch": "密碼不匹配", "passwdMismatch": "密碼不匹配",
"completeRuleSet": "At least 8 characters with one Uppercase, one number and one special character", "completeRuleSet": "密碼必須含有至少 8 個字元,其中有一個大寫字母、一個數字和一個特殊字元",
"atLeast8Char": "At least 8 characters", "atLeast8Char": "至少 8 個字元",
"atLeastOneUppercase": "One Uppercase letter", "atLeastOneUppercase": "一個大寫字母",
"atLeastOneNumber": "One Number", "atLeastOneNumber": "一個數字",
"atLeastOneSpecialChar": "One special character", "atLeastOneSpecialChar": "一個特殊字元",
"allowedSpecialCharList": "Allowed special character list" "allowedSpecialCharList": "允許特殊字元列表"
}, },
"invalidURL": "無效的 URL", "invalidURL": "無效的連結",
"internalError": "Some internal error occurred", "internalError": "發生內部錯誤",
"templateGeneratorNotFound": "Template Generator cannot be found!", "templateGeneratorNotFound": "Template Generator cannot be found!",
"fileUploadFailed": "檔案上傳失敗", "fileUploadFailed": "上傳文件失敗",
"primaryColumnUpdateFailed": "Failed to update primary column", "primaryColumnUpdateFailed": "Failed to update primary column",
"formDescriptionTooLong": "Data too long for Form Description", "formDescriptionTooLong": "表單描述資料過長",
"columnsRequired": "Following columns are required", "columnsRequired": "Following columns are required",
"selectAtleastOneColumn": "At least one column has to be selected", "selectAtleastOneColumn": "至少必須選擇一個欄位",
"columnDescriptionNotFound": "Cannot find the destination column for", "columnDescriptionNotFound": "Cannot find the destination column for",
"duplicateMappingFound": "Duplicate mapping found, please remove one of the mapping", "duplicateMappingFound": "Duplicate mapping found, please remove one of the mapping",
"nullValueViolatesNotNull": "Null value violates not-null constraint", "nullValueViolatesNotNull": "Null 值違反不可為 Null 限制條件",
"sourceHasInvalidNumbers": "Source data contains some invalid numbers", "sourceHasInvalidNumbers": "來源資料包含無效的數字",
"sourceHasInvalidBoolean": "Source data contains some invalid boolean values", "sourceHasInvalidBoolean": "來源資料包含無效的布林值",
"invalidForm": "無效的表", "invalidForm": "無效的表",
"formValidationFailed": "Form validation failed", "formValidationFailed": "Form validation failed",
"youHaveBeenSignedOut": "You have been signed out", "youHaveBeenSignedOut": "您已登出",
"failedToLoadList": "Failed to load list", "failedToLoadList": "Failed to load list",
"failedToLoadChildrenList": "Failed to load children list", "failedToLoadChildrenList": "Failed to load children list",
"deleteFailed": "Delete failed", "deleteFailed": "刪除失敗",
"unlinkFailed": "Unlink failed", "unlinkFailed": "Unlink failed",
"rowUpdateFailed": "Row update failed", "rowUpdateFailed": "資料更新失敗",
"deleteRowFailed": "Failed to delete row", "deleteRowFailed": "刪除資料失敗",
"setFormDataFailed": "Failed to set form data", "setFormDataFailed": "Failed to set form data",
"formViewUpdateFailed": "Failed to update form view", "formViewUpdateFailed": "Failed to update form view",
"tableNameRequired": "Table name is required", "tableNameRequired": "資料表名稱必填",
"nameShouldStartWithAnAlphabetOr_": "Name should start with an alphabet or _", "nameShouldStartWithAnAlphabetOr_": "名稱必須用 英文字母 或 _ 當開頭",
"followingCharactersAreNotAllowed": "Following characters are not allowed", "followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Column name is required", "columnNameRequired": "欄位名稱必填",
"projectNameExceeds50Characters": "Project name exceeds 50 characters", "projectNameExceeds50Characters": "專案名稱超過 50 個字元",
"projectNameCannotStartWithSpace": "Project name cannot start with space", "projectNameCannotStartWithSpace": "專案名稱不能有空白開頭",
"requiredField": "Required field", "requiredField": "必填欄位",
"ipNotAllowed": "IP not allowed", "ipNotAllowed": "IP not allowed",
"targetFileIsNotAnAcceptedFileType": "Target file is not an accepted file type", "targetFileIsNotAnAcceptedFileType": "Target file is not an accepted file type",
"theAcceptedFileTypeIsCsv": "The accepted file type is .csv", "theAcceptedFileTypeIsCsv": "The accepted file type is .csv",
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots", "theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key 不可為空", "parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed", "duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} 不可為空.", "fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible" "projectNotAccessible": "Project not accessible"
}, },
"toast": { "toast": {
@ -677,7 +679,7 @@
"authToken": "驗證權杖已複製到剪貼簿", "authToken": "驗證權杖已複製到剪貼簿",
"projInfo": "已將專案資訊複製到剪貼簿", "projInfo": "已將專案資訊複製到剪貼簿",
"inviteUrlCopy": "已將邀請連結複製到剪貼簿", "inviteUrlCopy": "已將邀請連結複製到剪貼簿",
"createView": "成功建立檢視", "createView": "成功建立檢視",
"formEmailSMTP": "請啟用 App Store 中的 SMTP 外掛程式以啟用電子郵件通知", "formEmailSMTP": "請啟用 App Store 中的 SMTP 外掛程式以啟用電子郵件通知",
"collabView": "成功轉換為協作視圖", "collabView": "成功轉換為協作視圖",
"lockedView": "成功轉換為鎖定視圖", "lockedView": "成功轉換為鎖定視圖",
@ -685,37 +687,37 @@
}, },
"success": { "success": {
"updatedUIACL": "Updated UI ACL for tables successfully", "updatedUIACL": "Updated UI ACL for tables successfully",
"pluginUninstalled": "Plugin uninstalled successfully", "pluginUninstalled": "外掛移除安裝成功",
"pluginSettingsSaved": "Plugin settings saved successfully", "pluginSettingsSaved": "外掛設定儲存成功",
"pluginTested": "Successfully tested plugin settings", "pluginTested": "外掛設定測試成功",
"tableRenamed": "資料表重新命名成功", "tableRenamed": "資料表重新命名成功",
"viewDeleted": "View deleted successfully", "viewDeleted": "檢視刪除成功",
"primaryColumnUpdated": "Successfully updated as primary column", "primaryColumnUpdated": "Successfully updated as primary column",
"tableDataExported": "Successfully exported all table data", "tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated", "updated": "成功更新",
"sharedViewDeleted": "Deleted shared view successfully", "sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully", "userDeleted": "使用者已成功删除",
"viewRenamed": "View renamed successfully", "viewRenamed": "檢視重新命名成功",
"tokenGenerated": "Token generated successfully", "tokenGenerated": "Token 產生成功",
"tokenDeleted": "Token deleted successfully", "tokenDeleted": "Token 刪除成功",
"userAddedToProject": "成功增加使用者到專案", "userAddedToProject": "專案增加使用者成功",
"userAdded": "成功增加使用者", "userAdded": "增加使用者成功",
"userDeletedFromProject": "Successfully deleted user from project", "userDeletedFromProject": "專案移除使用者成功",
"inviteEmailSent": "Invite Email sent successfully", "inviteEmailSent": "邀請郵件發送成功",
"inviteURLCopied": "Invite URL 複製到剪貼簿", "inviteURLCopied": "邀請連結已複製到剪貼簿",
"passwordResetURLCopied": "Password reset URL copied to 剪貼簿", "passwordResetURLCopied": "密碼重置連結已複製到剪貼簿",
"shareableURLCopied": "Copied shareable base URL to 剪貼簿!", "shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!", "embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details", "userDetailsUpdated": "成功更新使用者資料",
"tableDataImported": "Successfully imported table data", "tableDataImported": "成功匯入資料表資料",
"webhookUpdated": "Webhook details updated successfully", "webhookUpdated": "Webhook details updated successfully",
"webhookDeleted": "Hook deleted successfully", "webhookDeleted": "Hook deleted successfully",
"webhookTested": "Webhook tested successfully", "webhookTested": "Webhook tested successfully",
"columnUpdated": "欄位已更新", "columnUpdated": "欄位已更新",
"columnCreated": "欄位已建立", "columnCreated": "欄位已建立",
"passwordChanged": "密碼變更成功. 請重新登入.", "passwordChanged": "密碼已更新,請重新登入。",
"settingsSaved": "設定儲存成功", "settingsSaved": "設定已成功儲存",
"roleUpdated": "角色更新成功" "roleUpdated": "角色已成功更新"
} }
} }
} }

2
packages/nc-gui/layouts/default.vue

@ -20,7 +20,7 @@ export default {
<template> <template>
<div class="w-full h-full"> <div class="w-full h-full">
<Teleport :to="hasSidebar ? '#nc-sidebar-left' : null" :disabled="!hasSidebar"> <Teleport :to="hasSidebar ? '#nc-sidebar-left' : null" :disabled="!hasSidebar">
<slot name="sidebar" /> <slot :key="$route.name" name="sidebar" />
</Teleport> </Teleport>
<a-layout-content> <a-layout-content>

2
packages/nc-gui/package-lock.json generated

@ -90,7 +90,7 @@
} }
}, },
"../nocodb-sdk": { "../nocodb-sdk": {
"version": "0.98.4", "version": "0.99.2",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",

1
packages/nc-gui/pages/account/index.vue

@ -68,6 +68,7 @@ const openKeys = ref([/^\/account\/users/.test($route.fullPath) && 'users'])
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item <a-menu-item
v-if="isUIAllowed('appStore')"
key="apps" key="apps"
class="group active:(!ring-0) hover:(!bg-primary !bg-opacity-25)" class="group active:(!ring-0) hover:(!bg-primary !bg-opacity-25)"
@click="navigateTo('/account/apps')" @click="navigateTo('/account/apps')"

5
packages/nc-gui/pages/account/index/users.vue

@ -1,5 +0,0 @@
<template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull pt-2">
<NuxtPage />
</div>
</template>

30
packages/nc-gui/pages/account/index/users/[[nestedPage]].vue

@ -5,18 +5,20 @@ const { isUIAllowed } = useUIPermission()
</script> </script>
<template> <template>
<template <div class="h-full overflow-y-scroll scrollbar-thin-dull pt-2">
v-if=" <template
$route.params.nestedPage === 'password-reset' || v-if="
(!isUIAllowed('superAdminUserManagement') && !isUIAllowed('superAdminAppSettings')) $route.params.nestedPage === 'password-reset' ||
" (!isUIAllowed('superAdminUserManagement') && !isUIAllowed('superAdminAppSettings'))
> "
<LazyAccountResetPassword /> >
</template> <LazyAccountResetPassword />
<template v-else-if="$route.params.nestedPage === 'settings'"> </template>
<LazyAccountSignupSettings /> <template v-else-if="$route.params.nestedPage === 'settings'">
</template> <LazyAccountSignupSettings />
<template v-else-if="isUIAllowed('superAdminUserManagement')"> </template>
<LazyAccountUserList /> <template v-else-if="isUIAllowed('superAdminUserManagement')">
</template> <LazyAccountUserList />
</template>
</div>
</template> </template>

22
packages/nc-gui/pages/index/index/[projectId].vue

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Form } from 'ant-design-vue' import type { Form } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk' import type { ProjectType } from 'nocodb-sdk'
import type { VNodeRef } from '@vue/runtime-core'
import { import {
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
message, message,
@ -8,14 +9,13 @@ import {
projectTitleValidator, projectTitleValidator,
reactive, reactive,
ref, ref,
tryOnMounted,
useProject, useProject,
useRoute, useRoute,
} from '#imports' } from '#imports'
const route = useRoute() const route = useRoute()
const { project, loadProject, updateProject, isLoading, projectLoadedHook } = useProject() const { loadProject, updateProject, isLoading } = useProject()
loadProject(false) loadProject(false)
@ -43,21 +43,7 @@ const renameProject = async () => {
} }
} }
// select and focus title field on load const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
projectLoadedHook(async () => {
formState.title = project.value.title as string
tryOnMounted(() => {
// todo: replace setTimeout and follow better approach
setTimeout(() => {
const input = form.value?.$el?.querySelector('input[type=text]')
input.focus()
input.setSelectionRange(0, formState.title?.length)
}, 150)
})
})
</script> </script>
<template> <template>
@ -89,7 +75,7 @@ projectLoadedHook(async () => {
@finish="renameProject" @finish="renameProject"
> >
<a-form-item :label="$t('labels.projName')" name="title" :rules="nameValidationRules"> <a-form-item :label="$t('labels.projName')" name="title" :rules="nameValidationRules">
<a-input v-model:value="formState.title" name="title" class="nc-metadb-project-name" /> <a-input :ref="focus" v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
</a-form-item> </a-form-item>
<div class="text-center"> <div class="text-center">

19
packages/nc-gui/pages/index/index/create.vue

@ -1,11 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Form } from 'ant-design-vue' import type { Form } from 'ant-design-vue'
import type { VNodeRef } from '@vue/runtime-core'
import { import {
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
message, message,
navigateTo, navigateTo,
nextTick,
onMounted,
projectTitleValidator, projectTitleValidator,
reactive, reactive,
ref, ref,
@ -47,19 +46,7 @@ const createProject = async () => {
} }
} }
// select and focus title field on load const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
onMounted(async () => {
await nextTick(() => {
// todo: replace setTimeout and follow better approach
setTimeout(() => {
const input = form.value?.$el?.querySelector('input[type=text]')
input.setSelectionRange(0, formState.title.length)
input.focus()
}, 500)
})
})
</script> </script>
<template> <template>
@ -88,7 +75,7 @@ onMounted(async () => {
@finish="createProject" @finish="createProject"
> >
<a-form-item :label="$t('labels.projName')" name="title" :rules="nameValidationRules" class="m-10"> <a-form-item :label="$t('labels.projName')" name="title" :rules="nameValidationRules" class="m-10">
<a-input v-model:value="formState.title" name="title" class="nc-metadb-project-name" /> <a-input :ref="focus" v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
</a-form-item> </a-form-item>
<div class="text-center"> <div class="text-center">

156
packages/nc-gui/pages/index/index/index.vue

@ -212,92 +212,94 @@ const copyProjectMeta = async () => {
</a-dropdown> </a-dropdown>
</div> </div>
<Transition name="layout" mode="out-in"> <!--
<div v-if="isLoading"> TODO: bring back transition after fixing the bug with navigation
<a-skeleton /> <Transition name="layout" mode="out-in"> -->
</div> <div v-if="isLoading">
<a-skeleton />
<a-table </div>
v-else
:custom-row="customRow"
:data-source="filteredProjects"
:pagination="{ position: ['bottomCenter'] }"
:table-layout="md ? 'auto' : 'fixed'"
>
<template #emptyText>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</template>
<!-- Title --> <a-table
<a-table-column key="title" :title="$t('general.title')" data-index="title"> v-else
<template #default="{ text, record }"> :custom-row="customRow"
<div class="flex items-center"> :data-source="filteredProjects"
<div @click.stop> :pagination="{ position: ['bottomCenter'] }"
<a-menu class="!border-0 !m-0 !p-0" trigger-sub-menu-action="click"> :table-layout="md ? 'auto' : 'fixed'"
<template v-if="isUIAllowed('projectTheme')"> >
<a-sub-menu key="theme" popup-class-name="custom-color"> <template #emptyText>
<template #title> <a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
<div </template>
class="color-selector"
:style="{ <!-- Title -->
'background-color': getProjectPrimary(record), <a-table-column key="title" :title="$t('general.title')" data-index="title">
'width': '8px', <template #default="{ text, record }">
'height': '100%', <div class="flex items-center">
}" <div @click.stop>
/> <a-menu class="!border-0 !m-0 !p-0" trigger-sub-menu-action="click">
</template> <template v-if="isUIAllowed('projectTheme')">
<a-sub-menu key="theme" popup-class-name="custom-color">
<template #title>
<div
class="color-selector"
:style="{
'background-color': getProjectPrimary(record),
'width': '8px',
'height': '100%',
}"
/>
</template>
<template #expandIcon></template> <template #expandIcon></template>
<LazyGeneralColorPicker <LazyGeneralColorPicker
:model-value="getProjectPrimary(record)" :model-value="getProjectPrimary(record)"
:colors="projectThemeColors" :colors="projectThemeColors"
:row-size="9" :row-size="9"
:advanced="false" :advanced="false"
@input="handleProjectColor(record.id, $event)" @input="handleProjectColor(record.id, $event)"
/> />
<a-sub-menu key="pick-primary"> <a-sub-menu key="pick-primary">
<template #title> <template #title>
<div class="nc-project-menu-item group !py-0"> <div class="nc-project-menu-item group !py-0">
<ClarityColorPickerSolid class="group-hover:text-accent" /> <ClarityColorPickerSolid class="group-hover:text-accent" />
Custom Color Custom Color
</div> </div>
</template> </template>
<template #expandIcon></template> <template #expandIcon></template>
<LazyGeneralChromeWrapper @input="handleProjectColor(record.id, $event)" /> <LazyGeneralChromeWrapper @input="handleProjectColor(record.id, $event)" />
</a-sub-menu>
</a-sub-menu> </a-sub-menu>
</template> </a-sub-menu>
</a-menu> </template>
</div> </a-menu>
<div
class="capitalize color-transition group-hover:text-primary !w-[400px] h-full overflow-hidden overflow-ellipsis whitespace-nowrap pl-2"
>
{{ text }}
</div>
</div> </div>
</template> <div
</a-table-column> class="capitalize color-transition group-hover:text-primary !w-[400px] h-full overflow-hidden overflow-ellipsis whitespace-nowrap pl-2"
<!-- Actions --> >
{{ text }}
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }">
<div class="flex items-center gap-2">
<MdiEditOutline v-e="['c:project:edit:rename']" class="nc-action-btn" @click.stop="navigateTo(`/${text}`)" />
<MdiDeleteOutline
class="nc-action-btn"
:data-testid="`delete-project-${record.title}`"
@click.stop="deleteProject(record)"
/>
</div> </div>
</template> </div>
</a-table-column> </template>
</a-table> </a-table-column>
</Transition> <!-- Actions -->
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }">
<div class="flex items-center gap-2">
<MdiEditOutline v-e="['c:project:edit:rename']" class="nc-action-btn" @click.stop="navigateTo(`/${text}`)" />
<MdiDeleteOutline
class="nc-action-btn"
:data-testid="`delete-project-${record.title}`"
@click.stop="deleteProject(record)"
/>
</div>
</template>
</a-table-column>
</a-table>
<!-- </Transition> -->
</div> </div>
</template> </template>

5
packages/nc-gui/pages/index/index/user.vue

@ -68,7 +68,10 @@ const resetError = () => {
</script> </script>
<template> <template>
<div class="relative flex flex-col justify-center gap-2 w-full p-8 md:(bg-white rounded-lg border-1 border-gray-200 shadow)"> <div
class="relative flex flex-col justify-center gap-2 w-full p-8 md:(bg-white rounded-lg border-1 border-gray-200 shadow)"
data-testid="user-change-password"
>
<LazyGeneralNocoIcon class="color-transition hover:(ring ring-accent)" :animate="isLoading" /> <LazyGeneralNocoIcon class="color-transition hover:(ring ring-accent)" :animate="isLoading" />
<div <div

2
packages/nc-gui/windi.config.ts

@ -20,7 +20,7 @@ export default defineConfig({
}, },
darkMode: 'class', darkMode: 'class',
safelist: ['text-yellow-500', 'text-sky-500', 'text-red-500'],
plugins: [ plugins: [
scrollbar, scrollbar,
animations, animations,

2
packages/nc-lib-gui/package.json

@ -1,6 +1,6 @@
{ {
"name": "nc-lib-gui", "name": "nc-lib-gui",
"version": "0.98.4", "version": "0.99.2",
"description": "NocoDB GUI", "description": "NocoDB GUI",
"author": { "author": {
"name": "NocoDB", "name": "NocoDB",

12
packages/nc-plugin/package-lock.json generated

@ -8353,9 +8353,9 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.0.4", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
@ -18648,9 +18648,9 @@
"dev": true "dev": true
}, },
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true, "dev": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"

224
packages/noco-docs/content/en/engineering/playwright.md

@ -0,0 +1,224 @@
---
title: "Playwright E2E Testing"
description: "Overview to playwright based e2e tests"
position: 3260
category: "Engineering"
menuTitle: "Playwright E2E Testing"
---
## How to run tests
All the tests reside in `tests/playwright` folder.
Make sure to install the dependencies(in the playwright folder):
```bash
npm install
npx playwright install chromium --with-deps
```
### Run Test Server
Start the backend test server (in `packages/nocodb` folder):
```bash
npm run watch:run:playwright
```
Start the frontend test server (in `packages/nc-gui` folder):
```bash
NUXT_PAGE_TRANSITION_DISABLE=true npm run dev
```
### Running all tests
For selecting db type, rename `.env.example` to `.env` and set `E2E_DEV_DB_TYPE` to `sqlite`(default), `mysql` or `pg`.
headless mode(without opening browser):
```bash
npm run test
```
with browser:
```bash
npm run test:debug
```
</br>
</br>
For setting up mysql(sakila):
```bash
docker-compose -f ./tests/playwright/scripts/docker-compose-mysql-playwright.yml up -d
```
For setting up postgres(sakila):
```bash
docker-compose -f ./tests/playwright/scripts/docker-compose-playwright-pg.yml
```
### Running individual tests
Add `.only` to the test you want to run:
```js
test.only('should login', async ({ page }) => {
// ...
})
```
```bash
npm run test
```
## Concepts
### Independent tests
- All tests are independent of each other.
- Each test starts with a fresh project with a fresh sakila database(option to not use sakila db is also there).
- Each test creates a new user(email as `user@nocodb.com`) and logs in with that user to the dashboard.
Caveats:
- Some stuffs are shared i.e, users, plugins etc. So be catious while writing tests touching that. A fix for this is in the works.
- In test, we prefix email and project with the test id, which will be deleted after the test is done.
### What to test
- UI verification. This includes verifying the state of the UI element, i.e if the element is visible, if the element has a particular text etc.
- Test should verify all user flow. A test has a default timeout of 60 seconds. If a test is taking more than 60 seconds, it is a sign that the test should be broken down into smaller tests.
- Test should also verify all the side effects the feature(i.e. On adding a new column type, should verify column deletion as well) will have, and also error cases.
- Test name should be descriptive. It should be easy to understand what the test is doing by just reading the test name.
### Playwright
- Playwright is a nodejs library for automating chromium, firefox and webkit.
- For each test, a new browser context is created. This means that each test runs in a new incognito window.
- For assertion always use `expect` from `@playwright/test` library. This library provides a lot of useful assertions, which also has retry logic built in.
## Page Objects
- Page objects are used to abstract over the components/page. This makes the tests more readable and maintainable.
- All page objects are in `tests/playwright/pages` folder.
- All the test related code should be in page objects.
- Methods should be as thin as possible and its better to have multiple methods than one big method, which improves reusability.
The methods of a page object can be classified into 2 categories:
- Actions: Performs an UI actions like click, type, select etc. Is also responsible for waiting for the element to be ready and the action to be performed. This included waiting for API calls to complete.
- Assertions: Asserts the state of the UI element, i.e if the element is visible, if the element has a particular text etc. Use `expect` from `@playwright/test` and if not use `expect.poll` to wait for the assertion to pass.
## Writing a test
Let's write a test for testing filter functionality.
For simplicity, we will have `DashboardPage` implemented, which will have all the methods related to dashboard page and also its child components like Grid, etc.
### Create a test suite
Create a new file `filter.spec.ts` in `tests/playwright/tests` folder and use `setup` method to create a new project and user.
```js
import { test, expect } from '@playwright/test';
import setup, { NcContext } from '../setup';
test.describe('Filter', () => {
let context: NcContext;
test.beforeEach(async ({ page }) => {
context = await setup({ page });
})
test('should filter', async ({ page }) => {
// ...
});
});
```
### Create a page object
Since filter is UI wise scoped to a `Toolbar` , we will add filter page object to `ToolbarPage` page object.
```js
export class ToolbarPage extends BasePage {
readonly parent: GridPage | GalleryPage | FormPage | KanbanPage;
readonly filter: ToolbarFilterPage;
constructor(parent: GridPage | GalleryPage | FormPage | KanbanPage) {
super(parent.rootPage);
this.parent = parent;
this.filter = new ToolbarFilterPage(this);
}
}
```
We will create `ToolbarFilterPage` page object, which will have all the methods related to filter.
```js
export class ToolbarFilterPage extends BasePage {
readonly toolbar: ToolbarPage;
constructor(toolbar: ToolbarPage) {
super(toolbar.rootPage);
this.toolbar = toolbar;
}
}
```
Here `BasePage` is an abstract class, which used to enforce structure for all page objects. Thus all page object *should* inherit `BasePage`.
- Helper methods like `waitForResponse` and `getClipboardText` (this can be access on any page object, with `this.waitForResponse`)
- Provides structure for page objects, enforces all Page objects to have `rootPage` property, which is the page object created in the test setup.
- Enforces all pages to have a `get` method which will return the locator of the main container of that page, hence we can have focused dom selection, i.e.
```js
// This will only select the button inside the container of the concerned page
await this.get().querySelector('button').count();
```
### Writing an action method
This a method which will reset/clear all the filters. Since this is an action method, it will also wait for the `delete` filter API to return. Ignoring this API call will cause flakiness in the test, down the line.
```js
async resetFilter() {
await this.waitForResponse({
uiAction: this.get().locator('.nc-filter-item-remove-btn').click(),
httpMethodsToMatch: ['DELETE'],
requestUrlPathToMatch: '/api/v1/db/meta/filters/',
});
}
```
### Writing an assertion/verification method
Here we use `expect` from `@playwright/test` library, which has retry logic built in.
```js
import { expect } from '@playwright/test';
async verifyFilter({ title }: { title: string }) {
await expect(
this.get().locator(`[data-testid="nc-fields-menu-${title}"]`).locator('input[type="checkbox"]')
).toBeChecked();
}
```
## Tips to avoid flakiness
- If an UI action, causes an API call or the UI state change, then wait for that API call to complete or the UI state to change.
- What to wait out can be situation specific, but in general, is best to wait for the final state to be reached, i.e. in the case of creating filter, while it seems like waiting for the filter API to complete is enough, but after its return the table rows are reloaded and the UI state changes, so its better to wait for the table rows to be reloaded.
## Accessing playwright report in the CI
- Open `Summary` tab in the CI workflow in github actions.
- Scroll down to `Artifacts` section.
- Access reports which suffixed with the db type and shard number(corresponding to the CI workerflow name). i.e `playwright-report-mysql-2` is for `playwright-mysql-2` workflow.
- Download it and run `npm install -D @playwright/test && npx playwright show-report ./` inside the downloaded folder.

3
packages/noco-docs/content/en/engineering/translation.md

@ -53,12 +53,11 @@ Refer following articles to get additional details about Crowdin Portal usage
#### GitHub changes #### GitHub changes
- Update enumeration in `enums.ts` [packages/nc-gui/lib/enums.ts] - Update enumeration in `enums.ts` [packages/nc-gui/lib/enums.ts]
- Map JSON path in `a.i18n.ts` [packages/nc-gui/plugins/a.i18n.ts] - Map JSON path in `a.i18n.ts` [packages/nc-gui/plugins/a.i18n.ts]
- Update array in `6d_language_validation.js` [scripts/cypress/integration/common/6d_language_validation.js]
#### Crowdin changes [admin only] #### Crowdin changes [admin only]
- Open `NocoDB` project - Open `NocoDB` project
- Click on `Language` on the home tab - Click on `Language` on the home tab
- Select target language, `Update` - Select target language, `Update`
- Update array in `tests/playwright/tests/language.spec.ts`
![Screenshot 2022-09-08 at 10 52 59 PM](https://user-images.githubusercontent.com/86527202/189186570-5c1c7cad-6d3f-4937-ab4d-fa7ebe022cb1.png) ![Screenshot 2022-09-08 at 10 52 59 PM](https://user-images.githubusercontent.com/86527202/189186570-5c1c7cad-6d3f-4937-ab4d-fa7ebe022cb1.png)

161
packages/noco-docs/content/en/engineering/testing.md → packages/noco-docs/content/en/engineering/unit-testing.md

@ -1,9 +1,9 @@
--- ---
title: "Writing Tests" title: "Writing Unit Tests"
description: "Overview to testing" description: "Overview to Unit Testing"
position: 3250 position: 3250
category: "Engineering" category: "Engineering"
menuTitle: "Writing Tests" menuTitle: "Unit Testing"
--- ---
## Unit Tests ## Unit Tests
@ -189,158 +189,3 @@ function tableTest() {
}); });
} }
``` ```
## Cypress Tests
### End-to-end (E2E) Tests
Cypress tests are divided into 4 suites
- SQLite tests
- Postgres tests
- MySQL tests
- Quick import tests
First 3 suites, each have 4 test category
- Table operations (create, delete, rename, add column, delete column, rename column)
- Views (Grid, Gallery, Form)
- Roles (user profiles, access control & preview)
- Miscellaneous (Import, i18n, etc)
### SQLite Tests (XCDB Project)
```shell
# install dependencies(cypress)
npm install
# start MySQL database using docker compose
docker-compose -f ./scripts/cypress/docker-compose-cypress.yml up
# Run backend api using following command
npm run start:xcdb-api:cache
# Run frontend web UI using following command
npm run start:web
# wait until both 3000 and 8080 ports are available
# or run following command to run it with GUI
npm run cypress:open
# run one of 4 test scripts
# - Table operations : xcdb-restTableOps.js
# - Views : xcdb-restViews.js
# - Roles & access control : xcdb-restRoles.js
# - Miscellaneous : xcdb-restMisc.js
```
### MySQL Tests (External DB Project)
```shell
# install dependencies(cypress)
npm install
# start MySQL database using docker compose
docker-compose -f ./scripts/cypress/docker-compose-cypress.yml up
# Run backend api using following command
npm run start:api:cache
# Run frontend web UI using following command
npm run start:web
# wait until both 3000 and 8080 ports are available
# or run following command to run it with GUI
npm run cypress:open
# run one of 4 test scripts
# - Table operations : restTableOps.js
# - Views : restViews.js
# - Roles & access control : restRoles.js
# - Miscellaneous : restMisc.js
```
### Postgres Tests (External DB Project)
```shell
# install dependencies(cypress)
npm install
# start Postgres database using docker compose
docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d
# Run backend api using following command
npm run start:api:cache
# Run frontend web UI using following command
npm run start:web
# wait until both 3000 and 8080 ports are available
# or run following command to run it with GUI
npm run cypress:open
# run one of 4 test scripts
# - Table operations : pg-restTableOps.js
# - Views : pg-restViews.js
# - Roles & access control : pg-restRoles.js
# - Miscellaneous : pg-restMisc.js
```
### Quick Import Tests (SQLite Project)
```shell
# install dependencies(cypress)
npm install
# start MySQL database using docker compose
docker-compose -f ./scripts/cypress/docker-compose-cypress.yml up
# copy existing xcdb (v0.91.7) database to ./packages/nocodb/
cp ./scripts/cypress/fixtures/quickTest/noco_0_91_7.db ./packages/nocodb/noco.db
# Run backend api using following command
npm run start:api:cache
# Run frontend web UI using following command
npm run start:web
# wait until both 3000 and 8080 ports are available
# or run following command to run it with GUI
npm run cypress:open
# run test script
# - quickTest.js
```
### Quick import tests (Postgres)
```shell
# install dependencies(cypress)
npm install
# start PG database using docker compose
docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d
# copy existing xcdb (v0.91.7) database to ./packages/nocodb/
cp ./scripts/cypress/fixtures/quickTest/noco_0_91_7.db ./packages/nocodb/noco.db
# Run backend api using following command
npm run start:api:cache
# Run frontend web UI using following command
npm run start:web
# wait until both 3000 and 8080 ports are available
# or run following command to run it with GUI
npm run cypress:open
# run test script
# - quickTest.js
```
## Accessing CI-CD CY Screenshots
1. On Jobs link, click on `Summary`
![Screenshot 2022-10-31 at 9 25 23 PM](https://user-images.githubusercontent.com/86527202/199052696-af0bf066-d82f-487a-b487-602f55594fd7.png)
2. Click on `Artifacts`
![Screenshot 2022-10-31 at 9 26 01 PM](https://user-images.githubusercontent.com/86527202/199052712-04508921-32b1-4926-8291-396c804f7c3b.png)
3. Download logs for desired suite
![Screenshot 2022-10-31 at 9 26 34 PM](https://user-images.githubusercontent.com/86527202/199052727-9aebbdd1-749e-4bda-ab00-3cdd0e3f48fe.png)

12
packages/noco-docs/package-lock.json generated

@ -10047,9 +10047,9 @@
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.0.4", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
}, },
@ -24670,9 +24670,9 @@
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
}, },
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }

4
packages/nocodb-sdk/package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.98.4", "version": "0.99.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.98.4", "version": "0.99.2",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",

2
packages/nocodb-sdk/package.json

@ -1,6 +1,6 @@
{ {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.98.4", "version": "0.99.2",
"description": "NocoDB SDK", "description": "NocoDB SDK",
"main": "build/main/index.js", "main": "build/main/index.js",
"typings": "build/main/index.d.ts", "typings": "build/main/index.d.ts",

15
packages/nocodb/docker-compose.yml

@ -447,18 +447,3 @@ services:
cp /home/app/tests/sqlite-dump/sakila.db /home/sakila.db cp /home/app/tests/sqlite-dump/sakila.db /home/sakila.db
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"# echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"#
cd /home/app/ && npm i && npm run test:graphql cd /home/app/ && npm i && npm run test:graphql
xc-cypress-test:
image: node:12.22.1-slim
ports:
- 8080:8080
volumes:
- ./:/home/app
command:
- /bin/bash
- -c
- |
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cd /home/app/ && npm i && npm run run

357
packages/nocodb/package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "nocodb", "name": "nocodb",
"version": "0.98.4", "version": "0.99.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "nocodb", "name": "nocodb",
"version": "0.98.4", "version": "0.99.2",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@google-cloud/storage": "^5.7.2", "@google-cloud/storage": "^5.7.2",
@ -64,7 +64,7 @@
"mysql2": "^2.2.5", "mysql2": "^2.2.5",
"nanoid": "^3.1.20", "nanoid": "^3.1.20",
"nc-help": "0.2.79", "nc-help": "0.2.79",
"nc-lib-gui": "0.98.4", "nc-lib-gui": "0.99.2",
"nc-plugin": "0.1.2", "nc-plugin": "0.1.2",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"nocodb-sdk": "file:../nocodb-sdk", "nocodb-sdk": "file:../nocodb-sdk",
@ -119,7 +119,7 @@
"eslint-plugin-functional": "^3.0.2", "eslint-plugin-functional": "^3.0.2",
"eslint-plugin-import": "^2.22.0", "eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"mocha": "^8.1.1", "mocha": "^10.1.0",
"nodemon": "^2.0.7", "nodemon": "^2.0.7",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^2.7.1", "prettier": "^2.7.1",
@ -152,7 +152,7 @@
} }
}, },
"../nocodb-sdk": { "../nocodb-sdk": {
"version": "0.98.4", "version": "0.99.2",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",
@ -1629,12 +1629,6 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@ungap/promise-all-settled": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
"integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
"dev": true
},
"node_modules/@webassemblyjs/ast": { "node_modules/@webassemblyjs/ast": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@ -7369,15 +7363,6 @@
"graphql": ">=0.8.0" "graphql": ">=0.8.0"
} }
}, },
"node_modules/growl": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
"dev": true,
"engines": {
"node": ">=4.x"
}
},
"node_modules/gtoken": { "node_modules/gtoken": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz",
@ -8473,6 +8458,18 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
}, },
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-utf8": { "node_modules/is-utf8": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@ -9300,15 +9297,19 @@
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="
}, },
"node_modules/log-symbols": { "node_modules/log-symbols": {
"version": "4.0.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
"integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"chalk": "^4.0.0" "chalk": "^4.1.0",
"is-unicode-supported": "^0.1.0"
}, },
"engines": { "engines": {
"node": ">=10" "node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/log-symbols/node_modules/chalk": { "node_modules/log-symbols/node_modules/chalk": {
@ -9957,43 +9958,39 @@
} }
}, },
"node_modules/mocha": { "node_modules/mocha": {
"version": "8.4.0", "version": "10.1.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz",
"integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@ungap/promise-all-settled": "1.1.2",
"ansi-colors": "4.1.1", "ansi-colors": "4.1.1",
"browser-stdout": "1.3.1", "browser-stdout": "1.3.1",
"chokidar": "3.5.1", "chokidar": "3.5.3",
"debug": "4.3.1", "debug": "4.3.4",
"diff": "5.0.0", "diff": "5.0.0",
"escape-string-regexp": "4.0.0", "escape-string-regexp": "4.0.0",
"find-up": "5.0.0", "find-up": "5.0.0",
"glob": "7.1.6", "glob": "7.2.0",
"growl": "1.10.5",
"he": "1.2.0", "he": "1.2.0",
"js-yaml": "4.0.0", "js-yaml": "4.1.0",
"log-symbols": "4.0.0", "log-symbols": "4.1.0",
"minimatch": "3.0.4", "minimatch": "5.0.1",
"ms": "2.1.3", "ms": "2.1.3",
"nanoid": "3.1.20", "nanoid": "3.3.3",
"serialize-javascript": "5.0.1", "serialize-javascript": "6.0.0",
"strip-json-comments": "3.1.1", "strip-json-comments": "3.1.1",
"supports-color": "8.1.1", "supports-color": "8.1.1",
"which": "2.0.2", "workerpool": "6.2.1",
"wide-align": "1.1.3",
"workerpool": "6.1.0",
"yargs": "16.2.0", "yargs": "16.2.0",
"yargs-parser": "20.2.4", "yargs-parser": "20.2.4",
"yargs-unparser": "2.0.0" "yargs-unparser": "2.0.0"
}, },
"bin": { "bin": {
"_mocha": "bin/_mocha", "_mocha": "bin/_mocha",
"mocha": "bin/mocha" "mocha": "bin/mocha.js"
}, },
"engines": { "engines": {
"node": ">= 10.12.0" "node": ">= 14.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -10015,50 +10012,6 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true "dev": true
}, },
"node_modules/mocha/node_modules/chokidar": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
"dev": true,
"dependencies": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.5.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
"fsevents": "~2.3.1"
}
},
"node_modules/mocha/node_modules/debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/mocha/node_modules/debug/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/mocha/node_modules/escape-string-regexp": { "node_modules/mocha/node_modules/escape-string-regexp": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@ -10088,9 +10041,9 @@
} }
}, },
"node_modules/mocha/node_modules/glob": { "node_modules/mocha/node_modules/glob": {
"version": "7.1.6", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"fs.realpath": "^1.0.0", "fs.realpath": "^1.0.0",
@ -10107,10 +10060,22 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/mocha/node_modules/glob/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/mocha/node_modules/js-yaml": { "node_modules/mocha/node_modules/js-yaml": {
"version": "4.0.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
@ -10135,21 +10100,30 @@
} }
}, },
"node_modules/mocha/node_modules/minimatch": { "node_modules/mocha/node_modules/minimatch": {
"version": "3.0.4", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^2.0.1"
}, },
"engines": { "engines": {
"node": "*" "node": ">=10"
}
},
"node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
} }
}, },
"node_modules/mocha/node_modules/nanoid": { "node_modules/mocha/node_modules/nanoid": {
"version": "3.1.20", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
"integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
"dev": true, "dev": true,
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.cjs"
@ -10173,16 +10147,13 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/mocha/node_modules/readdirp": { "node_modules/mocha/node_modules/serialize-javascript": {
"version": "3.5.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"picomatch": "^2.2.1" "randombytes": "^2.1.0"
},
"engines": {
"node": ">=8.10.0"
} }
}, },
"node_modules/mocha/node_modules/supports-color": { "node_modules/mocha/node_modules/supports-color": {
@ -10632,9 +10603,9 @@
} }
}, },
"node_modules/nc-lib-gui": { "node_modules/nc-lib-gui": {
"version": "0.98.4", "version": "0.99.2",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.98.4.tgz", "resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.99.2.tgz",
"integrity": "sha512-6E8NCOm8nRbJ+9WIE+5YOFas6OIINj95X0wVKVScMGSpPZ9xIHXLKXZYjIoqEFT2Q94wAV8vPPX2iZhvqXDeZw==", "integrity": "sha512-ghjsyPGHGMMx4zvHqX5mhJhW/DZuZd1pYh8+zm9eru6v/xx6QUQAfcpZrMCfy9MadIY8haiva1khGAZMnY6grQ==",
"dependencies": { "dependencies": {
"express": "^4.17.1" "express": "^4.17.1"
} }
@ -17458,9 +17429,9 @@
} }
}, },
"node_modules/workerpool": { "node_modules/workerpool": {
"version": "6.1.0", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
"integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
"dev": true "dev": true
}, },
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
@ -18954,12 +18925,6 @@
"eslint-visitor-keys": "^2.0.0" "eslint-visitor-keys": "^2.0.0"
} }
}, },
"@ungap/promise-all-settled": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
"integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
"dev": true
},
"@webassemblyjs/ast": { "@webassemblyjs/ast": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@ -23542,12 +23507,6 @@
"integrity": "sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==", "integrity": "sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==",
"requires": {} "requires": {}
}, },
"growl": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
"dev": true
},
"gtoken": { "gtoken": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz",
@ -24325,6 +24284,12 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
}, },
"is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"dev": true
},
"is-utf8": { "is-utf8": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@ -24989,12 +24954,13 @@
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="
}, },
"log-symbols": { "log-symbols": {
"version": "4.0.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
"integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
"dev": true, "dev": true,
"requires": { "requires": {
"chalk": "^4.0.0" "chalk": "^4.1.0",
"is-unicode-supported": "^0.1.0"
}, },
"dependencies": { "dependencies": {
"chalk": { "chalk": {
@ -25534,33 +25500,29 @@
} }
}, },
"mocha": { "mocha": {
"version": "8.4.0", "version": "10.1.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz",
"integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@ungap/promise-all-settled": "1.1.2",
"ansi-colors": "4.1.1", "ansi-colors": "4.1.1",
"browser-stdout": "1.3.1", "browser-stdout": "1.3.1",
"chokidar": "3.5.1", "chokidar": "3.5.3",
"debug": "4.3.1", "debug": "4.3.4",
"diff": "5.0.0", "diff": "5.0.0",
"escape-string-regexp": "4.0.0", "escape-string-regexp": "4.0.0",
"find-up": "5.0.0", "find-up": "5.0.0",
"glob": "7.1.6", "glob": "7.2.0",
"growl": "1.10.5",
"he": "1.2.0", "he": "1.2.0",
"js-yaml": "4.0.0", "js-yaml": "4.1.0",
"log-symbols": "4.0.0", "log-symbols": "4.1.0",
"minimatch": "3.0.4", "minimatch": "5.0.1",
"ms": "2.1.3", "ms": "2.1.3",
"nanoid": "3.1.20", "nanoid": "3.3.3",
"serialize-javascript": "5.0.1", "serialize-javascript": "6.0.0",
"strip-json-comments": "3.1.1", "strip-json-comments": "3.1.1",
"supports-color": "8.1.1", "supports-color": "8.1.1",
"which": "2.0.2", "workerpool": "6.2.1",
"wide-align": "1.1.3",
"workerpool": "6.1.0",
"yargs": "16.2.0", "yargs": "16.2.0",
"yargs-parser": "20.2.4", "yargs-parser": "20.2.4",
"yargs-unparser": "2.0.0" "yargs-unparser": "2.0.0"
@ -25578,39 +25540,6 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true "dev": true
}, },
"chokidar": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
"dev": true,
"requires": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
"fsevents": "~2.3.1",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.5.0"
}
},
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
},
"dependencies": {
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
}
}
},
"escape-string-regexp": { "escape-string-regexp": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@ -25628,9 +25557,9 @@
} }
}, },
"glob": { "glob": {
"version": "7.1.6", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"fs.realpath": "^1.0.0", "fs.realpath": "^1.0.0",
@ -25639,12 +25568,23 @@
"minimatch": "^3.0.4", "minimatch": "^3.0.4",
"once": "^1.3.0", "once": "^1.3.0",
"path-is-absolute": "^1.0.0" "path-is-absolute": "^1.0.0"
},
"dependencies": {
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
}
} }
}, },
"js-yaml": { "js-yaml": {
"version": "4.0.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true, "dev": true,
"requires": { "requires": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
@ -25660,18 +25600,29 @@
} }
}, },
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
"dev": true, "dev": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^2.0.1"
},
"dependencies": {
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
}
} }
}, },
"nanoid": { "nanoid": {
"version": "3.1.20", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
"integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
"dev": true "dev": true
}, },
"p-locate": { "p-locate": {
@ -25683,13 +25634,13 @@
"p-limit": "^3.0.2" "p-limit": "^3.0.2"
} }
}, },
"readdirp": { "serialize-javascript": {
"version": "3.5.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
"dev": true, "dev": true,
"requires": { "requires": {
"picomatch": "^2.2.1" "randombytes": "^2.1.0"
} }
}, },
"supports-color": { "supports-color": {
@ -26064,9 +26015,9 @@
} }
}, },
"nc-lib-gui": { "nc-lib-gui": {
"version": "0.98.4", "version": "0.99.2",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.98.4.tgz", "resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.99.2.tgz",
"integrity": "sha512-6E8NCOm8nRbJ+9WIE+5YOFas6OIINj95X0wVKVScMGSpPZ9xIHXLKXZYjIoqEFT2Q94wAV8vPPX2iZhvqXDeZw==", "integrity": "sha512-ghjsyPGHGMMx4zvHqX5mhJhW/DZuZd1pYh8+zm9eru6v/xx6QUQAfcpZrMCfy9MadIY8haiva1khGAZMnY6grQ==",
"requires": { "requires": {
"express": "^4.17.1" "express": "^4.17.1"
} }
@ -31434,9 +31385,9 @@
} }
}, },
"workerpool": { "workerpool": {
"version": "6.1.0", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
"integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
"dev": true "dev": true
}, },
"wrap-ansi": { "wrap-ansi": {

13
packages/nocodb/package.json

@ -1,6 +1,6 @@
{ {
"name": "nocodb", "name": "nocodb",
"version": "0.98.4", "version": "0.99.2",
"description": "NocoDB Backend", "description": "NocoDB Backend",
"main": "dist/bundle.js", "main": "dist/bundle.js",
"author": { "author": {
@ -37,11 +37,8 @@
"watch:build": "nodemon -e ts,js -w ./src -x npm run build", "watch:build": "nodemon -e ts,js -w ./src -x npm run build",
"watch:run": "cross-env NC_DISABLE_TELE1=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"", "watch:run": "cross-env NC_DISABLE_TELE1=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"",
"watch:run:playwright": "rm -f ./test_noco.db; cross-env DATABASE_URL=sqlite:./test_noco.db PLAYWRIGHT_TEST=true NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/testDocker --log-error --project tsconfig.json\"", "watch:run:playwright": "rm -f ./test_noco.db; cross-env DATABASE_URL=sqlite:./test_noco.db PLAYWRIGHT_TEST=true NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/testDocker --log-error --project tsconfig.json\"",
"watch:run:playwright:quick": "rm -f ./test_noco.db; cp ../../scripts/cypress/fixtures/quickTest/noco_0_91_7.db ./test_noco.db; cross-env DATABASE_URL=sqlite:./test_noco.db NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"", "watch:run:playwright:quick": "rm -f ./test_noco.db; cp ../../tests/playwright/fixtures/noco_0_91_7.db ./test_noco.db; cross-env DATABASE_URL=sqlite:./test_noco.db NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"",
"watch:run:playwright:pg:cyquick": "rm -f ./test_noco.db; cp ../../scripts/cypress/fixtures/quickTest/noco_0_91_7.db ./test_noco.db; cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG_CyQuick.ts --log-error --project tsconfig.json\"", "watch:run:playwright:pg:cyquick": "rm -f ./test_noco.db; cp ../../tests/playwright/fixtures/noco_0_91_7.db ./test_noco.db; cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG_CyQuick.ts --log-error --project tsconfig.json\"",
"watch:run:cypress": "cross-env EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"",
"watch:run:cypress:pg": "cross-env EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG --log-error --project tsconfig.json\"",
"watch:run:cypress:pg:cyquick": "cross-env EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG_CyQuick.ts --log-error --project tsconfig.json\"",
"watch:run:mysql": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunMysql --log-error --project tsconfig.json\"", "watch:run:mysql": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunMysql --log-error --project tsconfig.json\"",
"watch:run:pg": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG --log-error --project tsconfig.json\"", "watch:run:pg": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG --log-error --project tsconfig.json\"",
"run": "ts-node src/run/docker", "run": "ts-node src/run/docker",
@ -107,7 +104,7 @@
"mysql2": "^2.2.5", "mysql2": "^2.2.5",
"nanoid": "^3.1.20", "nanoid": "^3.1.20",
"nc-help": "0.2.79", "nc-help": "0.2.79",
"nc-lib-gui": "0.98.4", "nc-lib-gui": "0.99.2",
"nc-plugin": "0.1.2", "nc-plugin": "0.1.2",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"nocodb-sdk": "file:../nocodb-sdk", "nocodb-sdk": "file:../nocodb-sdk",
@ -162,7 +159,7 @@
"eslint-plugin-functional": "^3.0.2", "eslint-plugin-functional": "^3.0.2",
"eslint-plugin-import": "^2.22.0", "eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"mocha": "^8.1.1", "mocha": "^10.1.0",
"nodemon": "^2.0.7", "nodemon": "^2.0.7",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^2.7.1", "prettier": "^2.7.1",

2
packages/nocodb/src/lib/db/sql-client/lib/KnexClient.ts

@ -892,7 +892,7 @@ class KnexClient extends SqlClient {
getMinMax(_columnObject) {} getMinMax(_columnObject) {}
async mockDb(_args) { async mockDb(_args) {
// todo: remove method // todo: remove method
} }
async dbCacheInitAsyncKnex(_cbk = null) { async dbCacheInitAsyncKnex(_cbk = null) {

473
packages/nocodb/src/lib/db/sql-client/lib/mssql/MssqlClient.ts

@ -183,7 +183,7 @@ class MssqlClient extends KnexClient {
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
try { try {
await this.sqlClient.raw('SELECT 1+1 as data'); await this.sqlClient.raw('SELECT 1+1 AS data');
} catch (e) { } catch (e) {
log.ppe(e); log.ppe(e);
result.code = -1; result.code = -1;
@ -246,7 +246,7 @@ class MssqlClient extends KnexClient {
try { try {
const rows = await this.sqlClient.raw( const rows = await this.sqlClient.raw(
`SELECT SERVERPROPERTY('productversion') as version, SERVERPROPERTY ('productlevel') as level, SERVERPROPERTY ('edition') as edition, @@version as versionD` `SELECT SERVERPROPERTY('productversion') AS version, SERVERPROPERTY ('productlevel') AS level, SERVERPROPERTY ('edition') AS edition, @@version AS versionD`
); );
result.data.object = {}; result.data.object = {};
@ -284,7 +284,7 @@ class MssqlClient extends KnexClient {
const tempSqlClient = knex(connectionParamsWithoutDb); const tempSqlClient = knex(connectionParamsWithoutDb);
const rows = await tempSqlClient.raw( const rows = await tempSqlClient.raw(
`select name from sys.databases where name = '${args.database}'` `SELECT name from sys.databases WHERE name = '${args.database}'`
); );
if (rows.length === 0) { if (rows.length === 0) {
@ -356,12 +356,14 @@ class MssqlClient extends KnexClient {
try { try {
/** ************** START : create _evolution table if not exists *************** */ /** ************** START : create _evolution table if not exists *************** */
const exists = await this.sqlClient.schema.withSchema(this.schema).hasTable(args.tn); const exists = await this.sqlClient.schema
.withSchema(this.schema)
.hasTable(args.tn);
if (!exists) { if (!exists) {
await this.sqlClient.schema.withSchema(this.schema).createTable( await this.sqlClient.schema
args.tn, .withSchema(this.schema)
function (table) { .createTable(args.tn, function (table) {
table.increments(); table.increments();
table.string('title').notNullable(); table.string('title').notNullable();
table.string('titleDown').nullable(); table.string('titleDown').nullable();
@ -371,8 +373,7 @@ class MssqlClient extends KnexClient {
table.integer('status').nullable(); table.integer('status').nullable();
table.dateTime('created'); table.dateTime('created');
table.timestamps(); table.timestamps();
} });
);
log.debug('Table created:', `${this.getTnPath(args.tn)}`); log.debug('Table created:', `${this.getTnPath(args.tn)}`);
} else { } else {
log.debug(`${this.getTnPath(args.tn)} tables exists`); log.debug(`${this.getTnPath(args.tn)} tables exists`);
@ -394,7 +395,9 @@ class MssqlClient extends KnexClient {
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
try { try {
result.data.value = await this.sqlClient.schema.withSchema(this.schema).hasTable(args.tn); result.data.value = await this.sqlClient.schema
.withSchema(this.schema)
.hasTable(args.tn);
} catch (e) { } catch (e) {
log.ppe(e, _func); log.ppe(e, _func);
throw e; throw e;
@ -412,7 +415,7 @@ class MssqlClient extends KnexClient {
try { try {
const rows = await this.sqlClient.raw( const rows = await this.sqlClient.raw(
`select name from sys.databases where name = '${args.databaseName}'` `SELECT name FROM sys.databases WHERE name = '${args.databaseName}'`
); );
result.data.value = rows.length > 0; result.data.value = rows.length > 0;
} catch (e) { } catch (e) {
@ -438,7 +441,7 @@ class MssqlClient extends KnexClient {
try { try {
result.data.list = await this.sqlClient.raw( result.data.list = await this.sqlClient.raw(
`SELECT name as database_name, database_id, create_date from sys.databases order by name` `SELECT name AS database_name, database_id, create_date FROM sys.databases ORDER BY name`
); );
} catch (e) { } catch (e) {
log.ppe(e, _func); log.ppe(e, _func);
@ -463,8 +466,8 @@ class MssqlClient extends KnexClient {
try { try {
result.data.list = await this.sqlClient.raw( result.data.list = await this.sqlClient.raw(
`select schema_name(t.schema_id) as schema_name, `SELECT schema_name(t.schema_id) AS schema_name,
t.name as tn, t.create_date, t.modify_date from sys.tables t WHERE schema_name(t.schema_id) = ? order by schema_name,tn `, t.name AS tn, t.create_date, t.modify_date FROM sys.tables t WHERE schema_name(t.schema_id) = ? ORDER BY schema_name,tn `,
[this.schema || 'dbo'] [this.schema || 'dbo']
); );
} catch (e) { } catch (e) {
@ -484,7 +487,7 @@ class MssqlClient extends KnexClient {
try { try {
result.data.list = await this.sqlClient.raw( result.data.list = await this.sqlClient.raw(
`SELECT name as schema_name FROM master.${this.schema}.sysdatabases where name not in ('master', 'tempdb', 'model', 'msdb');` `SELECT name AS schema_name FROM master.${this.schema}.sysdatabases WHERE name not in ('master', 'tempdb', 'model', 'msdb');`
); );
} catch (e) { } catch (e) {
log.ppe(e, _func); log.ppe(e, _func);
@ -532,53 +535,53 @@ class MssqlClient extends KnexClient {
try { try {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw(`select const response = await this.sqlClient.raw(`SELECT
c.table_name as tn, c.table_name AS tn,
case WHEN trg1.trigger_name IS NULL THEN CAST(0 as BIT) ELSE CAST(1 as BIT) END as au, CASE WHEN trg1.trigger_name IS NULL THEN CAST(0 AS BIT) ELSE CAST(1 AS BIT) END AS au,
c.column_name as cn, c.column_name AS cn,
c.ordinal_position as cop, c.ordinal_position AS cop,
pk.constraint_type as ck, pk.constraint_type AS ck,
case WHEN COLUMNPROPERTY(object_id(CONCAT('${this.schema}.', c.TABLE_NAME)), c.COLUMN_NAME, 'IsIdentity') = 1 CASE WHEN COLUMNPROPERTY(object_id(CONCAT('${this.schema}.', c.TABLE_NAME)), c.COLUMN_NAME, 'IsIdentity') = 1
THEN THEN
1 1
ELSE ELSE
0 0
END as ai, END AS ai,
c.is_nullable as nrqd, c.is_nullable AS nrqd,
c.data_type as dt, c.data_type AS dt,
c.column_default as cdf,c.character_maximum_length as clen, c.column_default AS cdf,c.character_maximum_length AS clen,
c.character_octet_length,c.numeric_precision as np,c.numeric_scale as ns,c.datetime_precision as dp,c.character_set_name as csn, c.character_octet_length,c.numeric_precision AS np,c.numeric_scale AS ns,c.datetime_precision AS dp,c.character_set_name AS csn,
c.collation_name as clnn, c.collation_name AS clnn,
pk.constraint_type as cst, pk.ordinal_position as op, pk.constraint_name as pk_constraint_name, pk.constraint_type AS cst, pk.ordinal_position AS op, pk.constraint_name AS pk_constraint_name,
fk.parent_table as rtn, fk.parent_column as rcn, fk.parent_table AS rtn, fk.parent_column AS rcn,
v.table_name as is_view, v.table_name AS is_view,
df.default_constraint_name df.default_constraint_name
from information_schema.columns c FROM INFORMATION_SCHEMA.COLUMNS c
left join left join
( select kc.constraint_name, kc.table_name,kc.column_name, kc.ordinal_position,tc.constraint_type ( SELECT kc.constraint_name, kc.table_name,kc.column_name, kc.ordinal_position,tc.constraint_type
from information_schema.key_column_usage kc FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kc
inner join information_schema.table_constraints as tc INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
on kc.constraint_name = tc.constraint_name and tc.constraint_type in ('primary key') ON kc.constraint_name = tc.constraint_name AND tc.constraint_type in ('primary key')
where kc.table_catalog='${args.databaseName}' and kc.table_schema='${this.schema}' WHERE kc.table_catalog='${args.databaseName}' AND kc.table_schema='${this.schema}'
) pk ) pk
on ON
pk.table_name = c.table_name and pk.column_name=c.column_name pk.table_name = c.table_name AND pk.column_name=c.column_name
left join left join
( select ( SELECT
ccu.table_name as child_table ccu.table_name AS child_table
,ccu.column_name as child_column ,ccu.column_name AS child_column
,kcu.table_name as parent_table ,kcu.table_name AS parent_table
,kcu.column_name as parent_column ,kcu.column_name AS parent_column
,ccu.constraint_name ,ccu.constraint_name
from information_schema.constraint_column_usage ccu FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu
inner join information_schema.referential_constraints rc INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
on ccu.constraint_name = rc.constraint_name ON ccu.constraint_name = rc.constraint_name
inner join information_schema.key_column_usage kcu INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
on kcu.constraint_name = rc.unique_constraint_name ) fk ON kcu.constraint_name = rc.unique_constraint_name ) fk
on ON
fk.child_table = c.table_name and fk.child_column=c.column_name fk.child_table = c.table_name AND fk.child_column=c.column_name
left join information_schema.views v left join INFORMATION_SCHEMA.VIEWS v
on v.table_name=c.table_name ON v.table_name=c.table_name
left join ( left join (
SELECT SELECT
default_constraints.name default_constraint_name, all_columns.name name default_constraints.name default_constraint_name, all_columns.name name
@ -595,17 +598,17 @@ class MssqlClient extends KnexClient {
ON all_columns.default_object_id = default_constraints.object_id ON all_columns.default_object_id = default_constraints.object_id
WHERE WHERE
schemas.name = '${this.schema}' schemas.name = '${this.schema}'
AND tables.name = '${args.tn}') df on df.name = c.column_name AND tables.name = '${args.tn}') df ON df.name = c.column_name
left join ( select trg.name as trigger_name, left join ( SELECT trg.name AS trigger_name,
tab.name as [table1] tab.name AS [table1]
from sys.triggers trg FROM sys.triggers trg
left join sys.objects tab left join sys.objects tab
on trg.parent_id = tab.object_id ON trg.parent_id = tab.object_id
where tab.name = '${args.tn}') trg1 on trg1.trigger_name = CONCAT('xc_trigger_${args.tn}_' , c.column_name) WHERE tab.name = '${args.tn}') trg1 ON trg1.trigger_name = CONCAT('xc_trigger_${args.tn}_' , c.column_name)
where c.table_catalog='${args.databaseName}' and c.table_schema='${this.schema}' and c.table_name = '${args.tn}' WHERE c.table_catalog='${args.databaseName}' AND c.table_schema='${this.schema}' AND c.table_name = '${args.tn}'
order by c.table_name, c.ordinal_position`); ORDER BY c.table_name, c.ordinal_position`);
for (let i = 0; i < response.length; i++) { for (let i = 0; i < response.length; i++) {
const el = response[i]; const el = response[i];
@ -657,39 +660,39 @@ class MssqlClient extends KnexClient {
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
try { try {
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`select t.[name] as table_view, `SELECT t.[name] AS table_view,
case when t.[type] = 'U' then 'Table' CASE WHEN t.[type] = 'U' THEN 'Table'
when t.[type] = 'V' then 'View' WHEN t.[type] = 'V' THEN 'View'
end as [object_type], END AS [object_type],
i.index_id, i.index_id,
case when i.is_primary_key = 1 then 'Primary key' CASE WHEN i.is_primary_key = 1 THEN 'Primary key'
when i.is_unique = 1 then 'Unique' WHEN i.is_unique = 1 THEN 'Unique'
else 'Not Unique' end as [type], else 'Not Unique' END AS [type],
i.[name] as index_name, i.[name] AS index_name,
substring(column_names, 1, len(column_names)-1) as [columns], substring(column_names, 1, len(column_names)-1) AS [columns],
case when i.[type] = 1 then 'Clustered index' CASE WHEN i.[type] = 1 THEN 'Clustered index'
when i.[type] = 2 then 'Nonclustered unique index' WHEN i.[type] = 2 THEN 'Nonclustered unique index'
when i.[type] = 3 then 'XML index' WHEN i.[type] = 3 THEN 'XML index'
when i.[type] = 4 then 'Spatial index' WHEN i.[type] = 4 THEN 'Spatial index'
when i.[type] = 5 then 'Clustered columnstore index' WHEN i.[type] = 5 THEN 'Clustered columnstore index'
when i.[type] = 6 then 'Nonclustered columnstore index' WHEN i.[type] = 6 THEN 'Nonclustered columnstore index'
when i.[type] = 7 then 'Nonclustered hash index' WHEN i.[type] = 7 THEN 'Nonclustered hash index'
end as index_type END AS index_type
from sys.objects t FROM sys.objects t
inner join sys.indexes i INNER JOIN sys.indexes i
on t.object_id = i.object_id ON t.object_id = i.object_id
cross apply (select col.[name] + ',' + CAST(ic.key_ordinal as varchar) + ',' cross apply (SELECT col.[name] + ',' + CAST(ic.key_ordinal AS varchar) + ','
from sys.index_columns ic FROM sys.index_columns ic
inner join sys.columns col INNER JOIN sys.columns col
on ic.object_id = col.object_id ON ic.object_id = col.object_id
and ic.column_id = col.column_id AND ic.column_id = col.column_id
where ic.object_id = t.object_id WHERE ic.object_id = t.object_id
and ic.index_id = i.index_id AND ic.index_id = i.index_id
order by col.column_id ORDER BY col.column_id
for xml path ('') ) D (column_names) for xml path ('') ) D (column_names)
where t.is_ms_shipped <> 1 WHERE t.is_ms_shipped <> 1
and index_id > 0 and t.name = '${this.getTnPath(args.tn)}' AND index_id > 0 AND t.name = '${this.getTnPath(args.tn)}'
order by schema_name(t.schema_id) + '.' + t.[name], i.index_id` ORDER BY schema_name(t.schema_id) + '.' + t.[name], i.index_id`
); );
const rows = []; const rows = [];
for (let i = 0, rowCount = 0; i < response.length; ++i, ++rowCount) { for (let i = 0, rowCount = 0; i < response.length; ++i, ++rowCount) {
@ -748,39 +751,39 @@ class MssqlClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`select t.[name] as table_view, `SELECT t.[name] AS table_view,
case when t.[type] = 'U' then 'Table' CASE WHEN t.[type] = 'U' THEN 'Table'
when t.[type] = 'V' then 'View' WHEN t.[type] = 'V' THEN 'View'
end as [object_type], END AS [object_type],
i.index_id, i.index_id,
case when i.is_primary_key = 1 then 'Primary key' CASE WHEN i.is_primary_key = 1 THEN 'Primary key'
when i.is_unique = 1 then 'Unique' WHEN i.is_unique = 1 THEN 'Unique'
else 'Not Unique' end as [type], else 'Not Unique' END AS [type],
i.[name] as index_name, i.[name] AS index_name,
substring(column_names, 1, len(column_names)-1) as [columns], substring(column_names, 1, len(column_names)-1) AS [columns],
case when i.[type] = 1 then 'Clustered index' CASE WHEN i.[type] = 1 THEN 'Clustered index'
when i.[type] = 2 then 'Nonclustered unique index' WHEN i.[type] = 2 THEN 'Nonclustered unique index'
when i.[type] = 3 then 'XML index' WHEN i.[type] = 3 THEN 'XML index'
when i.[type] = 4 then 'Spatial index' WHEN i.[type] = 4 THEN 'Spatial index'
when i.[type] = 5 then 'Clustered columnstore index' WHEN i.[type] = 5 THEN 'Clustered columnstore index'
when i.[type] = 6 then 'Nonclustered columnstore index' WHEN i.[type] = 6 THEN 'Nonclustered columnstore index'
when i.[type] = 7 then 'Nonclustered hash index' WHEN i.[type] = 7 THEN 'Nonclustered hash index'
end as index_type END AS index_type
from sys.objects t FROM sys.objects t
inner join sys.indexes i INNER JOIN sys.indexes i
on t.object_id = i.object_id ON t.object_id = i.object_id
cross apply (select col.[name] + ', ' + CAST(ic.key_ordinal as varchar) + ', ' cross apply (SELECT col.[name] + ', ' + CAST(ic.key_ordinal AS varchar) + ', '
from sys.index_columns ic FROM sys.index_columns ic
inner join sys.columns col INNER JOIN sys.columns col
on ic.object_id = col.object_id ON ic.object_id = col.object_id
and ic.column_id = col.column_id AND ic.column_id = col.column_id
where ic.object_id = t.object_id WHERE ic.object_id = t.object_id
and ic.index_id = i.index_id AND ic.index_id = i.index_id
order by col.column_id ORDER BY col.column_id
for xml path ('') ) D (column_names) for xml path ('') ) D (column_names)
where t.is_ms_shipped <> 1 WHERE t.is_ms_shipped <> 1
and index_id > 0 and t.name = '${this.getTnPath(args.tn)}' AND index_id > 0 AND t.name = '${this.getTnPath(args.tn)}'
order by schema_name(t.schema_id) + '.' + t.[name], i.index_id` ORDER BY schema_name(t.schema_id) + '.' + t.[name], i.index_id`
); );
const rows = []; const rows = [];
for (let i = 0, rowCount = 0; i < response.length; ++i, ++rowCount) { for (let i = 0, rowCount = 0; i < response.length; ++i, ++rowCount) {
@ -838,25 +841,25 @@ class MssqlClient extends KnexClient {
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
try { try {
const response = await this.sqlClient const response = await this.sqlClient
.raw(`select fk_tab.name as tn, '>-' as rel, pk_tab.name as rtn, .raw(`SELECT fk_tab.name AS tn, '>-' AS rel, pk_tab.name AS rtn,
fk_cols.constraint_column_id as no, fk_col.name as cn, ' = ' as [join], fk_cols.constraint_column_id AS no, fk_col.name AS cn, ' = ' AS [join],
pk_col.name as rcn, fk.name as cstn, pk_col.name AS rcn, fk.name AS cstn,
fk.update_referential_action_desc as ur, fk.delete_referential_action_desc as dr fk.update_referential_action_desc AS ur, fk.delete_referential_action_desc AS dr
from sys.foreign_keys fk FROM sys.foreign_keys fk
inner join sys.tables fk_tab INNER JOIN sys.tables fk_tab
on fk_tab.object_id = fk.parent_object_id ON fk_tab.object_id = fk.parent_object_id
inner join sys.tables pk_tab INNER JOIN sys.tables pk_tab
on pk_tab.object_id = fk.referenced_object_id ON pk_tab.object_id = fk.referenced_object_id
inner join sys.foreign_key_columns fk_cols INNER JOIN sys.foreign_key_columns fk_cols
on fk_cols.constraint_object_id = fk.object_id ON fk_cols.constraint_object_id = fk.object_id
inner join sys.columns fk_col INNER JOIN sys.columns fk_col
on fk_col.column_id = fk_cols.parent_column_id ON fk_col.column_id = fk_cols.parent_column_id
and fk_col.object_id = fk_tab.object_id AND fk_col.object_id = fk_tab.object_id
inner join sys.columns pk_col INNER JOIN sys.columns pk_col
on pk_col.column_id = fk_cols.referenced_column_id ON pk_col.column_id = fk_cols.referenced_column_id
and pk_col.object_id = pk_tab.object_id AND pk_col.object_id = pk_tab.object_id
where fk_tab.name = '${this.getTnPath(args.tn)}' WHERE fk_tab.name = '${this.getTnPath(args.tn)}'
order by fk_tab.name, pk_tab.name, fk_cols.constraint_column_id`); ORDER BY fk_tab.name, pk_tab.name, fk_cols.constraint_column_id`);
const ruleMapping = { const ruleMapping = {
NO_ACTION: 'NO ACTION', NO_ACTION: 'NO ACTION',
@ -904,24 +907,24 @@ class MssqlClient extends KnexClient {
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
try { try {
const response = await this const response = await this
.raw(`select fk_tab.name as tn, '>-' as rel, pk_tab.name as rtn, .raw(`SELECT fk_tab.name AS tn, '>-' AS rel, pk_tab.name AS rtn,
fk_cols.constraint_column_id as no, fk_col.name as cn, ' = ' as [join], fk_cols.constraint_column_id AS no, fk_col.name AS cn, ' = ' AS [join],
pk_col.name as rcn, fk.name as cstn, pk_col.name AS rcn, fk.name AS cstn,
fk.update_referential_action_desc as ur, fk.delete_referential_action_desc as dr fk.update_referential_action_desc AS ur, fk.delete_referential_action_desc AS dr
from sys.foreign_keys fk FROM sys.foreign_keys fk
inner join sys.tables fk_tab INNER JOIN sys.tables fk_tab
on fk_tab.object_id = fk.parent_object_id ON fk_tab.object_id = fk.parent_object_id
inner join sys.tables pk_tab INNER JOIN sys.tables pk_tab
on pk_tab.object_id = fk.referenced_object_id ON pk_tab.object_id = fk.referenced_object_id
inner join sys.foreign_key_columns fk_cols INNER JOIN sys.foreign_key_columns fk_cols
on fk_cols.constraint_object_id = fk.object_id ON fk_cols.constraint_object_id = fk.object_id
inner join sys.columns fk_col INNER JOIN sys.columns fk_col
on fk_col.column_id = fk_cols.parent_column_id ON fk_col.column_id = fk_cols.parent_column_id
and fk_col.object_id = fk_tab.object_id AND fk_col.object_id = fk_tab.object_id
inner join sys.columns pk_col INNER JOIN sys.columns pk_col
on pk_col.column_id = fk_cols.referenced_column_id ON pk_col.column_id = fk_cols.referenced_column_id
and pk_col.object_id = pk_tab.object_id AND pk_col.object_id = pk_tab.object_id
order by fk_tab.name, pk_tab.name, fk_cols.constraint_column_id`); ORDER BY fk_tab.name, pk_tab.name, fk_cols.constraint_column_id`);
const ruleMapping = { const ruleMapping = {
NO_ACTION: 'NO ACTION', NO_ACTION: 'NO ACTION',
@ -969,31 +972,31 @@ class MssqlClient extends KnexClient {
const result = new Result(); const result = new Result();
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
try { try {
const query = `select trg.name as trigger_name, const query = `SELECT trg.name AS trigger_name,
tab.name as [table], tab.name AS [table],
case when is_instead_of_trigger = 1 then 'Instead of' CASE WHEN is_instead_of_trigger = 1 THEN 'Instead of'
else 'After' end as [activation], else 'After' END AS [activation],
(case when objectproperty(trg.object_id, 'ExecIsUpdateTrigger') = 1 (CASE WHEN objectproperty(trg.object_id, 'ExecIsUpdateTrigger') = 1
then 'Update' else '' end THEN 'Update' else '' end
+ case when objectproperty(trg.object_id, 'ExecIsDeleteTrigger') = 1 + CASE WHEN objectproperty(trg.object_id, 'ExecIsDeleteTrigger') = 1
then 'Delete' else '' end THEN 'Delete' else '' end
+ case when objectproperty(trg.object_id, 'ExecIsInsertTrigger') = 1 + CASE WHEN objectproperty(trg.object_id, 'ExecIsInsertTrigger') = 1
then 'Insert' else '' end THEN 'Insert' else '' end
) as [event], ) AS [event],
case when trg.parent_class = 1 then 'Table trigger' CASE WHEN trg.parent_class = 1 THEN 'Table trigger'
when trg.parent_class = 0 then 'Database trigger' WHEN trg.parent_class = 0 THEN 'Database trigger'
end [class], END [class],
case when trg.[type] = 'TA' then 'Assembly (CLR) trigger' CASE WHEN trg.[type] = 'TA' THEN 'Assembly (CLR) trigger'
when trg.[type] = 'TR' then 'SQL trigger' WHEN trg.[type] = 'TR' THEN 'SQL trigger'
else '' end as [type], else '' END AS [type],
case when is_disabled = 1 then 'Disabled' CASE WHEN is_disabled = 1 THEN 'Disabled'
else 'Active' end as [status], else 'Active' END AS [status],
object_definition(trg.object_id) as [definition] object_definition(trg.object_id) AS [definition]
from sys.triggers trg FROM sys.triggers trg
left join sys.objects tab left join sys.objects tab
on trg.parent_id = tab.object_id ON trg.parent_id = tab.object_id
where tab.name = '${this.getTnPath(args.tn)}' WHERE tab.name = '${this.getTnPath(args.tn)}'
order by trg.name;`; ORDER BY trg.name;`;
const response = await this.sqlClient.raw(query); const response = await this.sqlClient.raw(query);
@ -1038,7 +1041,7 @@ class MssqlClient extends KnexClient {
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
try { try {
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`SELECT o.name as function_name,definition, o.create_date as created, o.modify_date as modified,o.* `SELECT o.name AS function_name,definition, o.create_date AS created, o.modify_date AS modified,o.*
FROM sys.sql_modules AS m FROM sys.sql_modules AS m
JOIN sys.objects AS o ON m.object_id = o.object_id JOIN sys.objects AS o ON m.object_id = o.object_id
AND type IN ('FN', 'IF', 'TF')` AND type IN ('FN', 'IF', 'TF')`
@ -1083,8 +1086,8 @@ class MssqlClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`select SPECIFIC_NAME as procedure_name, ROUTINE_TYPE as [type],LAST_ALTERED as modified, CREATED as created,ROUTINE_DEFINITION as definition ,pc.* `SELECT SPECIFIC_NAME AS procedure_name, ROUTINE_TYPE AS [type],LAST_ALTERED AS modified, CREATED AS created,ROUTINE_DEFINITION AS definition ,pc.*
from ${args.databaseName}.information_schema.routines as pc where routine_type = 'PROCEDURE'` FROM ${args.databaseName}.INFORMATION_SCHEMA.ROUTINES AS pc WHERE routine_type = 'PROCEDURE'`
); );
result.data.list = response; result.data.list = response;
@ -1116,8 +1119,8 @@ class MssqlClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`SELECT v.name as view_name,v.*,m.* FROM sys.views v inner join sys.schemas s on s.schema_id = v.schema_id `SELECT v.name AS view_name,v.*,m.* FROM sys.views v INNER JOIN sys.schemas s ON s.schema_id = v.schema_id
inner join sys.sql_modules as m on m.object_id = v.object_id` INNER JOIN sys.sql_modules AS m ON m.object_id = v.object_id`
); );
result.data.list = response; result.data.list = response;
@ -1150,10 +1153,10 @@ class MssqlClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`SELECT o.name as function_name,definition as create_function, o.create_date as created, o.modify_date as modified,o.* `SELECT o.name AS function_name,definition AS create_function, o.create_date AS created, o.modify_date AS modified,o.*
FROM sys.sql_modules AS m FROM sys.sql_modules AS m
JOIN sys.objects AS o ON m.object_id = o.object_id JOIN sys.objects AS o ON m.object_id = o.object_id
AND type IN ('FN', 'IF', 'TF') and o.name = '${args.function_name}'` AND type IN ('FN', 'IF', 'TF') AND o.name = '${args.function_name}'`
); );
for (let i = 0; i < response.length; i++) { for (let i = 0; i < response.length; i++) {
@ -1191,8 +1194,8 @@ class MssqlClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`select SPECIFIC_NAME as procedure_name, ROUTINE_TYPE as [type],LAST_ALTERED as modified, CREATED as created,ROUTINE_DEFINITION as create_procedure ,pc.* `SELECT SPECIFIC_NAME AS procedure_name, ROUTINE_TYPE AS [type],LAST_ALTERED AS modified, CREATED AS created,ROUTINE_DEFINITION AS create_procedure ,pc.*
from ${args.databaseName}.information_schema.routines as pc where routine_type = 'PROCEDURE' and SPECIFIC_NAME='${args.procedure_name}'` FROM ${args.databaseName}.INFORMATION_SCHEMA.ROUTINES AS pc WHERE routine_type = 'PROCEDURE' AND SPECIFIC_NAME='${args.procedure_name}'`
); );
result.data.list = response; result.data.list = response;
@ -1221,8 +1224,8 @@ class MssqlClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`SELECT v.name as view_name,v.*,m.*, m.definition as view_definition FROM sys.views v inner join sys.schemas s on s.schema_id = v.schema_id `SELECT v.name AS view_name,v.*,m.*, m.definition AS view_definition FROM sys.views v INNER JOIN sys.schemas s ON s.schema_id = v.schema_id
inner join sys.sql_modules as m on m.object_id = v.object_id where v.name = '${args.view_name}'` INNER JOIN sys.sql_modules AS m ON m.object_id = v.object_id WHERE v.name = '${args.view_name}'`
); );
result.data.list = response; result.data.list = response;
@ -1256,9 +1259,9 @@ class MssqlClient extends KnexClient {
FROM sysobjects AS [so] FROM sysobjects AS [so]
INNER JOIN sys.sql_modules AS df ON object_id = so.id INNER JOIN sys.sql_modules AS df ON object_id = so.id
INNER JOIN sysobjects AS so2 ON so.parent_obj = so2.Id INNER JOIN sysobjects AS so2 ON so.parent_obj = so2.Id
WHERE [so].[type] = 'TR' and so2.name = '${this.getTnPath( WHERE [so].[type] = 'TR' AND so2.name = '${this.getTnPath(
args.tn args.tn
)}' and [so].[name] = '${args.trigger_name}'` )}' AND [so].[name] = '${args.trigger_name}'`
); );
for (let i = 0; i < response.length; i++) { for (let i = 0; i < response.length; i++) {
@ -1288,7 +1291,7 @@ class MssqlClient extends KnexClient {
try { try {
const rows = await this.sqlClient.raw( const rows = await this.sqlClient.raw(
`select name from sys.databases where name = '${args.database_name}'` `SELECT name FROM sys.databases WHERE name = '${args.database_name}'`
); );
if (rows.length === 0) { if (rows.length === 0) {
@ -1596,7 +1599,7 @@ class MssqlClient extends KnexClient {
log.api(`${func}:args:`, args); log.api(`${func}:args:`, args);
try { try {
const query = this.genQuery( const query = this.genQuery(
`CREATE TRIGGER ?? on ?? \n${args.timing} ${args.event}\n as\n${args.statement}`, `CREATE TRIGGER ?? ON ?? \n${args.timing} ${args.event}\n as\n${args.statement}`,
[args.trigger_name, this.getTnPath(args.tn)] [args.trigger_name, this.getTnPath(args.tn)]
); );
await this.sqlClient.raw(query); await this.sqlClient.raw(query);
@ -1814,7 +1817,10 @@ class MssqlClient extends KnexClient {
const downStatement = const downStatement =
this.querySeparator() + this.querySeparator() +
this.sqlClient.schema.withSchema(this.schema).dropTable(args.table).toString(); this.sqlClient.schema
.withSchema(this.schema)
.dropTable(args.table)
.toString();
this.emit(`Success : ${upQuery}`); this.emit(`Success : ${upQuery}`);
@ -1855,8 +1861,16 @@ class MssqlClient extends KnexClient {
AS AS
BEGIN BEGIN
SET NOCOUNT ON; SET NOCOUNT ON;
UPDATE ?? Set ?? = GetDate() where ?? in (SELECT ?? FROM Inserted) UPDATE ?? Set ?? = GetDate() WHERE ?? in (SELECT ?? FROM Inserted)
END;`, [triggerName, this.getTnPath(args.table_name), this.getTnPath(args.table_name), column.column_name, pk.column_name, pk.column_name] END;`,
[
triggerName,
this.getTnPath(args.table_name),
this.getTnPath(args.table_name),
column.column_name,
pk.column_name,
pk.column_name,
]
); );
upQuery += triggerCreateQuery; upQuery += triggerCreateQuery;
@ -1891,7 +1905,7 @@ class MssqlClient extends KnexClient {
AS AS
BEGIN BEGIN
SET NOCOUNT ON; SET NOCOUNT ON;
UPDATE [${this.schema}].[${args.table_name}] Set [${column.column_name}] = GetDate() where [${pk.column_name}] in (SELECT [${pk.column_name}] FROM Inserted) UPDATE [${this.schema}].[${args.table_name}] Set [${column.column_name}] = GetDate() WHERE [${pk.column_name}] in (SELECT [${pk.column_name}] FROM Inserted)
END;`; END;`;
upQuery += triggerCreateQuery; upQuery += triggerCreateQuery;
@ -2058,7 +2072,10 @@ class MssqlClient extends KnexClient {
/** ************** create up & down statements *************** */ /** ************** create up & down statements *************** */
const upStatement = const upStatement =
this.querySeparator() + this.querySeparator() +
this.sqlClient.schema.withSchema(this.schema).dropTable(args.tn).toString(); this.sqlClient.schema
.withSchema(this.schema)
.dropTable(args.tn)
.toString();
let downQuery = this.querySeparator() + this.createTable(args.tn, args); let downQuery = this.querySeparator() + this.createTable(args.tn, args);
this.emit(`Success : ${upStatement}`); this.emit(`Success : ${upStatement}`);
@ -2072,8 +2089,9 @@ class MssqlClient extends KnexClient {
for (const relation of relationsList) { for (const relation of relationsList) {
downQuery += downQuery +=
this.querySeparator() + this.querySeparator() +
(await this.sqlClient.withSchema(this.schema).schema (await this.sqlClient
.table(relation.tn, (table) => { .withSchema(this.schema)
.schema.table(relation.tn, (table) => {
table = table table = table
.foreign(relation.cn, null) .foreign(relation.cn, null)
.references(relation.rcn) .references(relation.rcn)
@ -2116,7 +2134,8 @@ class MssqlClient extends KnexClient {
)) { )) {
downQuery += downQuery +=
this.querySeparator() + this.querySeparator() +
this.sqlClient.schema.withSchema(this.schema) this.sqlClient.schema
.withSchema(this.schema)
.table(tn, function (table) { .table(tn, function (table) {
if (non_unique) { if (non_unique) {
table.index(columns, key_name); table.index(columns, key_name);
@ -2152,7 +2171,7 @@ class MssqlClient extends KnexClient {
* @param {String} - args.childTable * @param {String} - args.childTable
* @returns {Promise<{upStatement, downStatement}>} * @returns {Promise<{upStatement, downStatement}>}
*/ */
async relationCreate(args) { async relationCreate(args) {
const _func = this.relationCreate.name; const _func = this.relationCreate.name;
const result = new Result(); const result = new Result();
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
@ -2161,19 +2180,22 @@ class MssqlClient extends KnexClient {
try { try {
const self = this; const self = this;
await this.sqlClient.schema.table(this.getTnPath(args.childTable), function (table) { await this.sqlClient.schema.table(
table = table this.getTnPath(args.childTable),
.foreign(args.childColumn, foreignKeyName) function (table) {
.references(args.parentColumn) table = table
.on(self.getTnPath(args.parentTable)); .foreign(args.childColumn, foreignKeyName)
.references(args.parentColumn)
if (args.onUpdate) { .on(self.getTnPath(args.parentTable));
table = table.onUpdate(args.onUpdate);
} if (args.onUpdate) {
if (args.onDelete) { table = table.onUpdate(args.onUpdate);
table = table.onDelete(args.onDelete); }
if (args.onDelete) {
table = table.onDelete(args.onDelete);
}
} }
}); );
const upStatement = const upStatement =
this.querySeparator() + this.querySeparator() +
@ -2234,9 +2256,12 @@ class MssqlClient extends KnexClient {
try { try {
const self = this; const self = this;
await this.sqlClient.schema.table(this.getTnPath(args.childTable), function (table) { await this.sqlClient.schema.table(
table.dropForeign(args.childColumn, foreignKeyName); this.getTnPath(args.childTable),
}); function (table) {
table.dropForeign(args.childColumn, foreignKeyName);
}
);
const upStatement = const upStatement =
this.querySeparator() + this.querySeparator() +
@ -2379,7 +2404,7 @@ class MssqlClient extends KnexClient {
const result = new Result(); const result = new Result();
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
try { try {
result.data = `DELETE FROM ${this.getTnPath(args.tn)} where ;`; result.data = `DELETE FROM ${this.getTnPath(args.tn)} WHERE ;`;
} catch (e) { } catch (e) {
log.ppe(e, _func); log.ppe(e, _func);
throw e; throw e;

4
packages/nocodb/src/lib/db/sql-client/lib/pg/PgClient.ts

@ -468,7 +468,9 @@ class PGClient extends KnexClient {
} }
if (rows.length === 0) { if (rows.length === 0) {
log.debug('creating database:', args); log.debug('creating database:', args);
await tempSqlClient.raw(`CREATE DATABASE ?? ENCODING 'UTF8'`, [args.database]); await tempSqlClient.raw(`CREATE DATABASE ?? ENCODING 'UTF8'`, [
args.database,
]);
} }
// if (this.connectionConfig.searchPath && this.connectionConfig.searchPath[0]) { // if (this.connectionConfig.searchPath && this.connectionConfig.searchPath[0]) {

26
packages/nocodb/src/lib/db/sql-data-mapper/lib/BaseModel.ts

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/ban-types,prefer-const */ /* eslint-disable @typescript-eslint/ban-types,prefer-const */
import { Knex } from 'knex'; import { Knex } from 'knex';
import Filter from '../../../models/Filter'; import Filter from '../../../models/Filter';
import Sort from '../../../models/Sort'; import Sort from '../../../models/Sort';
@ -220,10 +220,7 @@ abstract class BaseModel {
const query = this.$db.insert(data); const query = this.$db.insert(data);
if ( if (this.dbDriver.client === 'pg' || this.dbDriver.client === 'mssql') {
this.dbDriver.client === 'pg' ||
this.dbDriver.client === 'mssql'
) {
query.returning('*'); query.returning('*');
response = await this._run(query); response = await this._run(query);
} else { } else {
@ -265,10 +262,7 @@ abstract class BaseModel {
const query = this.$db.insert(data); const query = this.$db.insert(data);
if ( if (this.dbDriver.client === 'pg' || this.dbDriver.client === 'mssql') {
this.dbDriver.client === 'pg' ||
this.dbDriver.client === 'mssql'
) {
query.returning('*'); query.returning('*');
response = await this._run(query); response = await this._run(query);
} else { } else {
@ -302,13 +296,13 @@ abstract class BaseModel {
for (const d of data) { for (const d of data) {
await this.validate(d); await this.validate(d);
} }
const response = (this.dbDriver.client === 'pg' || this.dbDriver.client === 'mssql') ? const response =
this.dbDriver this.dbDriver.client === 'pg' || this.dbDriver.client === 'mssql'
.batchInsert(this.tn, data, 50) ? this.dbDriver
.returning(this.pks?.[0]?.cn || '*') : .batchInsert(this.tn, data, 50)
this.dbDriver .returning(this.pks?.[0]?.cn || '*')
.batchInsert(this.tn, data, 50); : this.dbDriver.batchInsert(this.tn, data, 50);
await this.afterInsertb(data); await this.afterInsertb(data);

17
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSql.ts

@ -417,10 +417,7 @@ class BaseModelSql extends BaseModel {
const query = dbDriver(this.tnPath).insert(insertObj); const query = dbDriver(this.tnPath).insert(insertObj);
if ( if (this.dbDriver.client === 'pg' || this.dbDriver.client === 'mssql') {
this.dbDriver.client === 'pg' ||
this.dbDriver.client === 'mssql'
) {
query.returning(this.selectQuery('')); query.returning(this.selectQuery(''));
response = await this._run(query); response = await this._run(query);
} }
@ -608,12 +605,12 @@ class BaseModelSql extends BaseModel {
await this.validate(d1); await this.validate(d1);
} }
const response = (this.dbDriver.client === 'pg' || this.dbDriver.client === 'mssql') ? const response =
await this.dbDriver this.dbDriver.client === 'pg' || this.dbDriver.client === 'mssql'
.batchInsert(this.tn, insertDatas, 50) ? await this.dbDriver
.returning(this.pks[0].cn) : .batchInsert(this.tn, insertDatas, 50)
await this.dbDriver .returning(this.pks[0].cn)
.batchInsert(this.tn, insertDatas, 50); : await this.dbDriver.batchInsert(this.tn, insertDatas, 50);
await this.afterInsertb(insertDatas, null); await this.afterInsertb(insertDatas, null);

65
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts

@ -99,9 +99,10 @@ class BaseModelSqlv2 {
qb.where(_wherePk(this.model.primaryKeys, id)); qb.where(_wherePk(this.model.primaryKeys, id));
const data = (await this.extractRawQueryAndExec(qb))?.[0]; let data = (await this.extractRawQueryAndExec(qb))?.[0];
if (data) { if (data) {
data = this.convertAttachmentType(data);
const proto = await this.getProto(); const proto = await this.getProto();
data.__proto__ = proto; data.__proto__ = proto;
} }
@ -157,9 +158,10 @@ class BaseModelSqlv2 {
qb.orderBy(this.model.primaryKey.column_name); qb.orderBy(this.model.primaryKey.column_name);
} }
const data = await qb.first(); let data = await qb.first();
if (data) { if (data) {
data = this.convertAttachmentType(data);
const proto = await this.getProto(); const proto = await this.getProto();
data.__proto__ = proto; data.__proto__ = proto;
} }
@ -251,7 +253,8 @@ class BaseModelSqlv2 {
if (!ignoreViewFilterAndSort) applyPaginate(qb, rest); if (!ignoreViewFilterAndSort) applyPaginate(qb, rest);
const proto = await this.getProto(); const proto = await this.getProto();
const data = await this.extractRawQueryAndExec(qb); let data = await this.extractRawQueryAndExec(qb);
data = this.convertAttachmentType(data);
return data?.map((d) => { return data?.map((d) => {
d.__proto__ = proto; d.__proto__ = proto;
@ -363,8 +366,7 @@ class BaseModelSqlv2 {
qb.groupBy(args.column_name); qb.groupBy(args.column_name);
if (sorts) await sortV2(sorts, qb, this.dbDriver); if (sorts) await sortV2(sorts, qb, this.dbDriver);
applyPaginate(qb, rest); applyPaginate(qb, rest);
const data = await qb; return this.convertAttachmentType(await qb);
return data;
} }
async multipleHmList({ colId, ids }, args: { limit?; offset? } = {}) { async multipleHmList({ colId, ids }, args: { limit?; offset? } = {}) {
@ -423,7 +425,8 @@ class BaseModelSqlv2 {
.as('list') .as('list')
); );
const children = await this.extractRawQueryAndExec(childQb); let children = await this.extractRawQueryAndExec(childQb);
children = this.convertAttachmentType(children);
const proto = await ( const proto = await (
await Model.getBaseModelSQL({ await Model.getBaseModelSQL({
id: childTable.id, id: childTable.id,
@ -550,7 +553,8 @@ class BaseModelSqlv2 {
await childModel.selectObject({ qb }); await childModel.selectObject({ qb });
const children = await this.extractRawQueryAndExec(qb); let children = await this.extractRawQueryAndExec(qb);
children = this.convertAttachmentType(children);
const proto = await ( const proto = await (
await Model.getBaseModelSQL({ await Model.getBaseModelSQL({
@ -671,6 +675,7 @@ class BaseModelSqlv2 {
if (this.isMySQL) { if (this.isMySQL) {
children = children[0]; children = children[0];
} }
children = this.convertAttachmentType(children);
const proto = await ( const proto = await (
await Model.getBaseModelSQL({ await Model.getBaseModelSQL({
id: rtnId, id: rtnId,
@ -735,7 +740,8 @@ class BaseModelSqlv2 {
qb.limit(+rest?.limit || 25); qb.limit(+rest?.limit || 25);
qb.offset(+rest?.offset || 0); qb.offset(+rest?.offset || 0);
const children = await this.extractRawQueryAndExec(qb); let children = await this.extractRawQueryAndExec(qb);
children = this.convertAttachmentType(children);
const proto = await ( const proto = await (
await Model.getBaseModelSQL({ id: rtnId, dbDriver: this.dbDriver }) await Model.getBaseModelSQL({ id: rtnId, dbDriver: this.dbDriver })
).getProto(); ).getProto();
@ -961,8 +967,8 @@ class BaseModelSqlv2 {
applyPaginate(qb, rest); applyPaginate(qb, rest);
const proto = await childModel.getProto(); const proto = await childModel.getProto();
const data = await qb; let data = await qb;
data = this.convertAttachmentType(data);
return data.map((c) => { return data.map((c) => {
c.__proto__ = proto; c.__proto__ = proto;
return c; return c;
@ -1076,7 +1082,8 @@ class BaseModelSqlv2 {
applyPaginate(qb, rest); applyPaginate(qb, rest);
const proto = await childModel.getProto(); const proto = await childModel.getProto();
const data = await this.extractRawQueryAndExec(qb); let data = await this.extractRawQueryAndExec(qb);
data = this.convertAttachmentType(data);
return data.map((c) => { return data.map((c) => {
c.__proto__ = proto; c.__proto__ = proto;
@ -1194,7 +1201,8 @@ class BaseModelSqlv2 {
applyPaginate(qb, rest); applyPaginate(qb, rest);
const proto = await parentModel.getProto(); const proto = await parentModel.getProto();
const data = await this.extractRawQueryAndExec(qb); let data = await this.extractRawQueryAndExec(qb);
data = this.convertAttachmentType(data);
return data.map((c) => { return data.map((c) => {
c.__proto__ = proto; c.__proto__ = proto;
@ -2609,7 +2617,9 @@ class BaseModelSqlv2 {
const proto = await this.getProto(); const proto = await this.getProto();
const result = (await groupedQb)?.map((d) => { let data = await groupedQb;
data = this.convertAttachmentType(data);
const result = data?.map((d) => {
d.__proto__ = proto; d.__proto__ = proto;
return d; return d;
}); });
@ -2726,6 +2736,35 @@ class BaseModelSqlv2 {
) )
: await this.dbDriver.raw(query); : await this.dbDriver.raw(query);
} }
private _convertAttachmentType(attachmentColumns, d) {
attachmentColumns.forEach((col) => {
if (d[col.title] && typeof d[col.title] === 'string') {
d[col.title] = JSON.parse(d[col.title]);
}
});
return d;
}
private convertAttachmentType(data) {
// attachment is stored in text and parse in UI
// convertAttachmentType is used to convert the response in string to array of object in API response
if (data) {
const attachmentColumns = this.model.columns.filter(
(c) => c.uidt === UITypes.Attachment
);
if (attachmentColumns.length) {
if (Array.isArray(data)) {
data = data.map((d) =>
this._convertAttachmentType(attachmentColumns, d)
);
} else {
this._convertAttachmentType(attachmentColumns, data);
}
}
}
return data;
}
} }
function extractSortsObject( function extractSortsObject(

3
packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts

@ -273,7 +273,8 @@ async function getGroupedDataList(model, view: View, req) {
data = data.map((item) => { data = data.map((item) => {
// todo: use map to avoid loop // todo: use map to avoid loop
const count = const count =
countArr.find((countItem: any) => countItem.key === item.key)?.count ?? 0; countArr.find((countItem: any) => countItem.key === item.key)?.count ??
0;
item.value = new PagedResponseImpl(item.value, { item.value = new PagedResponseImpl(item.value, {
...req.query, ...req.query,

4
packages/nocodb/src/lib/meta/api/index.ts

@ -1,5 +1,5 @@
import { Tele } from 'nc-help'; import { Tele } from 'nc-help';
import orgLicenseApis from './orgLicenseApis' import orgLicenseApis from './orgLicenseApis';
import orgTokenApis from './orgTokenApis'; import orgTokenApis from './orgTokenApis';
import orgUserApis from './orgUserApis'; import orgUserApis from './orgUserApis';
import projectApis from './projectApis'; import projectApis from './projectApis';
@ -62,7 +62,7 @@ export default function (router: Router, server) {
projectApis(router); projectApis(router);
utilApis(router); utilApis(router);
if(process.env['PLAYWRIGHT_TEST'] === 'true') { if (process.env['PLAYWRIGHT_TEST'] === 'true') {
router.use(testApis); router.use(testApis);
} }
router.use(columnApis); router.use(columnApis);

4
packages/nocodb/src/lib/meta/api/orgLicenseApis.ts

@ -1,12 +1,10 @@
import { Router } from 'express'; import { Router } from 'express';
import { OrgUserRoles } from 'nocodb-sdk'; import { OrgUserRoles } from 'nocodb-sdk';
import { NC_LICENSE_KEY } from '../../constants' import { NC_LICENSE_KEY } from '../../constants';
import Store from '../../models/Store'; import Store from '../../models/Store';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
async function licenseGet(_req, res) { async function licenseGet(_req, res) {
const license = await Store.get(NC_LICENSE_KEY); const license = await Store.get(NC_LICENSE_KEY);

3
packages/nocodb/src/lib/meta/api/publicApis/publicDataApis.ts

@ -157,7 +157,8 @@ async function getGroupedDataList(model, view: View, req) {
data = data.map((item) => { data = data.map((item) => {
// todo: use map to avoid loop // todo: use map to avoid loop
const count = const count =
countArr.find((countItem: any) => countItem.key === item.key)?.count ?? 0; countArr.find((countItem: any) => countItem.key === item.key)?.count ??
0;
item.value = new PagedResponseImpl(item.value, { item.value = new PagedResponseImpl(item.value, {
...req.query, ...req.query,

7
packages/nocodb/src/lib/meta/api/swagger/helpers/getSwaggerColumnMetas.ts

@ -40,6 +40,12 @@ export default async (
case UITypes.Rollup: case UITypes.Rollup:
field.type = 'number'; field.type = 'number';
break; break;
case UITypes.Attachment:
field.type = 'array';
field.items = {
$ref: `#/components/schemas/Attachment`,
};
break;
default: default:
field.virtual = false; field.virtual = false;
SwaggerTypes.setSwaggerType(c, field, dbType); SwaggerTypes.setSwaggerType(c, field, dbType);
@ -58,4 +64,5 @@ export interface SwaggerColumn {
virtual?: boolean; virtual?: boolean;
$ref?: any; $ref?: any;
column: Column; column: Column;
items?: any;
} }

21
packages/nocodb/src/lib/meta/api/swagger/helpers/swagger-base.json

@ -34,6 +34,27 @@
} }
} }
}, },
"Attachment": {
"title": "Attachment",
"type": "object",
"properties": {
"mimetype": {
"type": "string"
},
"size": {
"type": "integer"
},
"title": {
"type": "string"
},
"url": {
"type": "string"
},
"icon": {
"type": "string"
}
}
},
"Groupby": { "Groupby": {
"title": "Groupby", "title": "Groupby",
"type": "object", "type": "object",

2
packages/nocodb/src/lib/meta/api/swagger/swaggerApis.ts

@ -2,7 +2,7 @@
import catchError, { NcError } from '../../helpers/catchError'; import catchError, { NcError } from '../../helpers/catchError';
import { Router } from 'express'; import { Router } from 'express';
import Model from '../../../models/Model'; import Model from '../../../models/Model';
import ncMetaAclMw from '../../helpers/ncMetaAclMw' import ncMetaAclMw from '../../helpers/ncMetaAclMw';
import getSwaggerJSON from './helpers/getSwaggerJSON'; import getSwaggerJSON from './helpers/getSwaggerJSON';
import Project from '../../../models/Project'; import Project from '../../../models/Project';
import swaggerHtml from './swaggerHtml'; import swaggerHtml from './swaggerHtml';

12
packages/nocodb/src/lib/meta/api/sync/helpers/job.ts

@ -2237,9 +2237,12 @@ export default async (
for (let i = 0; i < ncTblList.list.length; i++) { for (let i = 0; i < ncTblList.list.length; i++) {
// not a migrated table, skip // not a migrated table, skip
if (undefined === aTblSchema.find((x) => x.name === ncTblList.list[i].title)) if (
undefined ===
aTblSchema.find((x) => x.name === ncTblList.list[i].title)
)
continue; continue;
const _perfStart = recordPerfStart(); const _perfStart = recordPerfStart();
const ncTbl = await api.dbTable.read(ncTblList.list[i].id); const ncTbl = await api.dbTable.read(ncTblList.list[i].id);
recordPerfStats(_perfStart, 'dbTable.read'); recordPerfStats(_perfStart, 'dbTable.read');
@ -2265,7 +2268,10 @@ export default async (
logBasic('Configuring Record Links...'); logBasic('Configuring Record Links...');
for (let i = 0; i < ncTblList.list.length; i++) { for (let i = 0; i < ncTblList.list.length; i++) {
// not a migrated table, skip // not a migrated table, skip
if (undefined === aTblSchema.find((x) => x.name === ncTblList.list[i].title)) if (
undefined ===
aTblSchema.find((x) => x.name === ncTblList.list[i].title)
)
continue; continue;
const ncTbl = await api.dbTable.read(ncTblList.list[i].id); const ncTbl = await api.dbTable.read(ncTblList.list[i].id);

2
packages/nocodb/src/lib/meta/api/testApis.ts

@ -6,7 +6,7 @@ export async function reset(req: Request<any, any>, res) {
parallelId: req.body.parallelId, parallelId: req.body.parallelId,
dbType: req.body.dbType, dbType: req.body.dbType,
isEmptyProject: req.body.isEmptyProject, isEmptyProject: req.body.isEmptyProject,
workerId: req.body.workerId workerId: req.body.workerId,
}); });
res.json(await service.process()); res.json(await service.process());

9
packages/nocodb/src/lib/meta/helpers/populateSamplePayload.ts

@ -94,7 +94,14 @@ async function getSampleColumnValue(column: Column): Promise<any> {
break; break;
case UITypes.Attachment: case UITypes.Attachment:
{ {
return '[{"url":"https://nocodb.com/dummy.png","title":"image.png","mimetype":"image/png","size":0}]'; return [
{
url: 'https://nocodb.com/dummy.png',
title: 'image.png',
mimetype: 'image/png',
size: 0,
},
];
} }
break; break;
case UITypes.Checkbox: case UITypes.Checkbox:

6
packages/nocodb/src/lib/models/SelectOption.ts

@ -77,9 +77,9 @@ export default class SelectOption {
return options?.length return options?.length
? { ? {
options: options.map( options: options
({ created_at, updated_at, ...c }) => new SelectOption(c) .map(({ created_at, updated_at, ...c }) => new SelectOption(c))
), .sort((x, y) => x.order - y.order),
} }
: null; : null;
} }

18
packages/nocodb/src/lib/services/test/TestResetService/index.ts

@ -23,7 +23,7 @@ const loginRootUser = async () => {
const projectTitleByType = { const projectTitleByType = {
sqlite: 'sampleREST', sqlite: 'sampleREST',
mysql: 'externalREST', mysql: 'externalREST',
pg: 'pgExtREST' pg: 'pgExtREST',
}; };
export class TestResetService { export class TestResetService {
@ -37,7 +37,7 @@ export class TestResetService {
parallelId, parallelId,
dbType, dbType,
isEmptyProject, isEmptyProject,
workerId workerId,
}: { }: {
parallelId: string; parallelId: string;
dbType: string; dbType: string;
@ -73,7 +73,7 @@ export class TestResetService {
token, token,
dbType: this.dbType, dbType: this.dbType,
parallelId: this.parallelId, parallelId: this.parallelId,
workerId: this.workerId workerId: this.workerId,
}); });
try { try {
@ -96,7 +96,7 @@ export class TestResetService {
token, token,
dbType, dbType,
parallelId, parallelId,
workerId workerId,
}: { }: {
token: string; token: string;
dbType: string; dbType: string;
@ -123,7 +123,7 @@ export class TestResetService {
token, token,
title, title,
parallelId, parallelId,
isEmptyProject: this.isEmptyProject isEmptyProject: this.isEmptyProject,
}); });
} else if (dbType == 'mysql') { } else if (dbType == 'mysql') {
await resetMysqlSakilaProject({ await resetMysqlSakilaProject({
@ -131,7 +131,7 @@ export class TestResetService {
title, title,
parallelId, parallelId,
oldProject: project, oldProject: project,
isEmptyProject: this.isEmptyProject isEmptyProject: this.isEmptyProject,
}); });
} else if (dbType == 'pg') { } else if (dbType == 'pg') {
await resetPgSakilaProject({ await resetPgSakilaProject({
@ -139,12 +139,12 @@ export class TestResetService {
title, title,
parallelId: workerId, parallelId: workerId,
oldProject: project, oldProject: project,
isEmptyProject: this.isEmptyProject isEmptyProject: this.isEmptyProject,
}); });
} }
return { return {
project: await Project.getByTitle(title) project: await Project.getByTitle(title),
}; };
} }
} }
@ -177,7 +177,7 @@ const removeProjectUsersFromCache = async (project: Project) => {
const projectUsers: ProjectUser[] = await ProjectUser.getUsersList({ const projectUsers: ProjectUser[] = await ProjectUser.getUsersList({
project_id: project.id, project_id: project.id,
limit: 1000, limit: 1000,
offset: 0 offset: 0,
}); });
for (const projectUser of projectUsers) { for (const projectUser of projectUsers) {

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

@ -157,7 +157,7 @@ export default {
dataCount: true, dataCount: true,
upload: true, upload: true,
uploadViaURL: true, uploadViaURL: true,
swaggerJson:true swaggerJson: true,
}, },
}, },
commenter: { commenter: {
@ -217,7 +217,7 @@ export default {
xcAuditModelCommentsCount: true, xcAuditModelCommentsCount: true,
xcExportAsCsv: true, xcExportAsCsv: true,
dataCount: true, dataCount: true,
swaggerJson:true swaggerJson: true,
}, },
}, },
viewer: { viewer: {
@ -273,7 +273,7 @@ export default {
list: true, list: true,
xcExportAsCsv: true, xcExportAsCsv: true,
dataCount: true, dataCount: true,
swaggerJson:true swaggerJson: true,
}, },
}, },
[OrgUserRoles.VIEWER]: { [OrgUserRoles.VIEWER]: {
@ -294,11 +294,7 @@ export default {
upload: true, upload: true,
uploadViaURL: true, uploadViaURL: true,
passwordChange: true, passwordChange: true,
pluginList: true,
pluginRead: true,
pluginTest: true,
isPluginActive: true, isPluginActive: true,
pluginUpdate: true,
projectCreate: true, projectCreate: true,
projectList: true, projectList: true,
projectCost: true, projectCost: true,

53
scripts/cypress/cypress.json

@ -1,53 +0,0 @@
{
"baseUrl": "http://localhost:3000/",
"testFiles": [
"test/restTableOps.js",
"test/restViews.js",
"test/restRoles.js",
"test/restMisc.js",
"test/xcdb-restTableOps.js",
"test/xcdb-restViews.js",
"test/xcdb-restRoles.js",
"test/xcdb-restMisc.js",
"test/pg-restTableOps.js",
"test/pg-restViews.js",
"test/pg-restRoles.js",
"test/pg-restMisc.js",
"test/quickTest.js",
"test/db-independent.js"
],
"defaultCommandTimeout": 13000,
"pageLoadTimeout": 600000,
"viewportWidth": 1980,
"viewportHeight": 1000,
"video": false,
"retries": 0,
"screenshotOnRunFailure": true,
"numTestsKeptInMemory": 0,
"experimentalInteractiveRunEvents": true,
"env": {
"testMode": [
{ "apiType": "rest", "dbType": "xcdb" },
{ "apiType": "rest", "dbType": "mysql" },
{ "apiType": "rest", "dbType": "postgres" }
],
"db": {
"host": "127.0.0.1",
"user": "root",
"password": "password"
},
"screenshot": false,
"airtable": {
"apiKey": "keyn1MR87qgyUsYg4",
"sharedBase": "https://airtable.com/shr4z0qmh6dg5s3eB"
}
},
"fixturesFolder": "scripts/cypress/fixtures",
"integrationFolder": "scripts/cypress/integration",
"pluginsFile": "scripts/cypress/plugins/index.js",
"screenshotsFolder": "scripts/cypress/screenshots",
"videosFolder": "scripts/cypress/videos",
"downloadsFolder": "scripts/cypress/downloads",
"supportFile": "scripts/cypress/support/index.js",
"chromeWebSecurity": false
}

67
scripts/cypress/docker-compose-cypress.yml

@ -1,67 +0,0 @@
version: "3.5"
# https://github.com/docker-library/mysql/issues/149
# disabling default sql-mode set to only_full_group_by
services:
xc-mysql-sakila-db:
network_mode: host
image: mysql:8.0
command: mysqld --sql_mode=""
restart: always
environment:
MYSQL_ROOT_PASSWORD: password
volumes:
- ../../packages/nocodb/tests/mysql-sakila-db:/docker-entrypoint-initdb.d
# xc-cypress-nocodb:
# network_mode: host
# image: node:14-alpine
# environment:
# - EE=true
# volumes:
# - ./packages/nocodb:/home/app
# command:
# - /bin/sh
# - -c
# - |
# echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
# # cp -r /home/app1/ /home/app/
# rm /home/app/package-lock.json
# rm /home/app/noco.db
# cd /home/app/ && npm i && EE=true npm run run
# # cd /home/app/ && npm i && EE=true npm run watch:run
# xc-cypress-nc-gui:
# network_mode: host
# image: node:14-alpine
# environment:
# - HOST=0.0.0.0
# - PORT=3000
# - EE=true
# volumes:
# - ./packages/nc-gui:/home/app
# - ./packages/nc-lib-gui:/home/nc-lib-gui
# command:
# - /bin/sh
# - -c
# - |
# echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
# apk --update --no-cache add git
# # cp -r /home/app1/ /home/app/
# rm /home/app/package-lock.json
# cd /home/app/ && npm i && npm run dev
# # cd /home/app/ && npm i && NODE_ENV=development npm run build && npm start

17
scripts/cypress/docker-compose-pg-cy-quick.yml

@ -1,17 +0,0 @@
version: "2.1"
services:
pg96:
image: postgres:9.6
restart: always
environment:
POSTGRES_PASSWORD: password
ports:
- 5432:5432
volumes:
- ../../packages/nocodb/tests/pg-cy-quick:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save