Browse Source

Merge pull request #1909 from nocodb/develop

0.90.8 Pre-release
pull/1911/head 0.90.8
աɨռɢӄաօռɢ 3 years ago committed by GitHub
parent
commit
1bc988ef52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 34
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 62
      .github/workflows/pr-to-master.yml
  3. 75
      .github/workflows/release-nocodb.yml
  4. 1
      package.json
  5. 103
      packages/nc-gui/components/ProjectTreeView.vue
  6. 1224
      packages/nc-gui/components/createOrEditProject.vue
  7. 54
      packages/nc-gui/components/project/settings/appearance.vue
  8. 5
      packages/nc-gui/components/project/spreadsheet/components/editColumn/lookupOptions.vue
  9. 5
      packages/nc-gui/components/project/spreadsheet/components/editColumn/rollupOptions.vue
  10. 12
      packages/nc-gui/components/project/spreadsheet/components/extras.vue
  11. 5
      packages/nc-gui/components/project/spreadsheet/components/spreadsheetNavDrawer.vue
  12. 1
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/formulaCell.vue
  13. 14
      packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
  14. 8
      packages/nc-gui/components/project/spreadsheet/helpers/uiTypes.js
  15. 110
      packages/nc-gui/components/project/table.vue
  16. 2
      packages/nc-gui/components/utils/language.vue
  17. 1
      packages/nc-gui/lang/da.json
  18. 1
      packages/nc-gui/lang/de.json
  19. 1
      packages/nc-gui/lang/en.json
  20. 1
      packages/nc-gui/lang/es.json
  21. 1
      packages/nc-gui/lang/fa.json
  22. 1
      packages/nc-gui/lang/fi.json
  23. 1
      packages/nc-gui/lang/fr.json
  24. 1
      packages/nc-gui/lang/hr.json
  25. 1
      packages/nc-gui/lang/id.json
  26. 1
      packages/nc-gui/lang/it_IT.json
  27. 1
      packages/nc-gui/lang/iw.json
  28. 1
      packages/nc-gui/lang/ja.json
  29. 1
      packages/nc-gui/lang/ko.json
  30. 1
      packages/nc-gui/lang/lv.json
  31. 1
      packages/nc-gui/lang/nl.json
  32. 1
      packages/nc-gui/lang/no.json
  33. 1
      packages/nc-gui/lang/pl.json
  34. 1
      packages/nc-gui/lang/pt.json
  35. 1
      packages/nc-gui/lang/pt_BR.json
  36. 1
      packages/nc-gui/lang/ru.json
  37. 1
      packages/nc-gui/lang/sl.json
  38. 1
      packages/nc-gui/lang/sv.json
  39. 1
      packages/nc-gui/lang/th.json
  40. 1
      packages/nc-gui/lang/tr.json
  41. 1
      packages/nc-gui/lang/uk.json
  42. 1
      packages/nc-gui/lang/vi.json
  43. 1
      packages/nc-gui/lang/zh_CN.json
  44. 1
      packages/nc-gui/lang/zh_HK.json
  45. 1
      packages/nc-gui/lang/zh_TW.json
  46. 1
      packages/nc-gui/layouts/default.vue
  47. 4
      packages/noco-docs/content/en/FAQs.md
  48. 2
      packages/noco-docs/content/en/setup-and-usages/usage-information.md
  49. 11
      packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts
  50. 15
      packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts
  51. 11
      packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts
  52. 11
      packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts
  53. 9
      packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts
  54. 25203
      packages/nocodb/package-lock.json
  55. 2
      packages/nocodb/package.json
  56. 62
      packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSqlv2.ts
  57. 2
      packages/nocodb/src/lib/dataMapper/lib/sql/conditionV2.ts
  58. 13
      packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/pg.ts
  59. 4
      packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/sqlite.ts
  60. 99
      packages/nocodb/src/lib/dataMapper/lib/sql/helpers/getAst.ts
  61. 11
      packages/nocodb/src/lib/noco-models/Model.ts
  62. 75
      packages/nocodb/src/lib/noco-models/Project.ts
  63. 14
      packages/nocodb/src/lib/noco/Noco.ts
  64. 6
      packages/nocodb/src/lib/noco/meta/api/dataApis/bulkDataAliasApis.ts
  65. 23
      packages/nocodb/src/lib/noco/meta/api/dataApis/dataAliasApis.ts
  66. 3
      packages/nocodb/src/lib/noco/meta/api/dataApis/dataAliasExportApis.ts
  67. 8
      packages/nocodb/src/lib/noco/meta/api/dataApis/dataAliasNestedApis.ts
  68. 77
      packages/nocodb/src/lib/noco/meta/api/dataApis/dataApis.ts
  69. 14
      packages/nocodb/src/lib/noco/meta/api/dataApis/helpers.ts
  70. 102
      packages/nocodb/src/lib/noco/meta/api/dataApis/oldDataApis.ts
  71. 6
      packages/nocodb/src/lib/noco/meta/api/index.ts
  72. 30
      packages/nocodb/src/lib/noco/meta/api/publicApis/publicDataApis.ts
  73. 29
      packages/nocodb/src/lib/noco/meta/api/publicApis/publicDataExportApis.ts
  74. 2
      packages/nocodb/src/lib/noco/meta/api/swagger/helpers/getSwaggerColumnMetas.ts
  75. 2
      packages/nocodb/src/lib/noco/meta/api/swagger/helpers/templates/params.ts
  76. 2
      packages/nocodb/src/lib/noco/meta/api/swagger/swaggerApis.ts
  77. 2
      packages/nocodb/src/lib/noco/meta/api/swagger/swaggerHtml.ts
  78. 35
      packages/nocodb/src/lib/noco/meta/api/tableApis.ts
  79. 2
      packages/nocodb/src/lib/noco/meta/helpers/NcPluginMgrv2.ts
  80. 19
      packages/nocodb/src/lib/noco/meta/helpers/apiMetrics.ts
  81. 2
      packages/nocodb/src/lib/noco/meta/helpers/extractProjectIdAndAuthenticate.ts
  82. 2
      packages/nocodb/src/lib/noco/rest/ui/auth/swagger.ts
  83. 32
      packages/nocodb/src/lib/noco/upgrader/jobs/ncProjectUpgraderV2_0090000.ts
  84. 2
      packages/nocodb/src/lib/utils/NcHelp.ts
  85. 4
      scripts/cypress/cypress.json
  86. 4
      scripts/cypress/integration/spec/roleValidation.spec.js

34
.github/ISSUE_TEMPLATE/bug_report.md

@ -9,11 +9,22 @@ assignees: ''
**Please enter the following details**
Copy and paste project info : [Youtube Video](https://www.youtube.com/watch?v=AUSNN-RCwhE)
or
please provide the following details :
Copy and Paste Project Info - Tutorials: How to check my Project info? ([YouTube Tutorial](https://www.youtube.com/watch?v=AUSNN-RCwhE) or [Documentation](https://docs.nocodb.com/FAQs#how-to-check-my-project-info-))
```
Node: **v16.14.0**
Arch: **arm64**
Platform: **darwin**
Docker: **false**
Database: **mysql2**
ProjectOnRootDB: **false**
RootDB: **mysql2**
PackageVersion: **0.90.5**
```
or provide the following info
```
NocoDB used as docker : true / false
NocoDB version :
Database used in NC_DB URL : mysql | pg | mssql | sqlite3 / (defaults to sqlite3 if empty)
@ -22,23 +33,20 @@ Database on which spreadsheet is created : mysql | pg | mssql | sqlite3 / (defau
OS on which NocoDB is running :
Node.js version if running as node :
Database version :
```
**Steps To Reproduce**
1. Go to '...'
2. Click on '....'
3. See error
**Expected behavior**
**Expected Behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
Join our discord : https://discord.gg/5RgZmkW for realtime help.
Node version : **v12.22.1**
Arch type: **x64**
Platform: **linux**
Docker: **true**
Database: **sqlite3**
packageVersion: **0.9.27**

62
.github/workflows/pr-to-master.yml

@ -0,0 +1,62 @@
name: 'PR to master branch from develop'
on:
# Triggered manually
workflow_dispatch:
inputs:
tag:
description: "Tag"
required: true
targetEnv:
description: "Target Environment"
required: true
type: choice
options:
- DEV
- PROD
# Triggered by release-nocodb.yml
workflow_call:
inputs:
tag:
description: "Tag"
required: true
type: string
targetEnv:
description: "Target Environment"
required: true
type: string
jobs:
pr-to-master:
runs-on: ubuntu-latest
steps:
- run: |
echo 'triggering pr-to-master'
- name: Checkout
uses: actions/checkout@v2
with:
ref: develop
- name: Create Pull Request
if: ${{ github.event.inputs.targetEnv == 'PROD' || inputs.targetEnv == 'PROD' }}
id: cpr
uses: repo-sync/pull-request@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
source_branch: "develop"
destination_branch: "master"
pr_title: '${{ github.event.inputs.tag || inputs.tag }} Pre-release'
pr_label: 'Bot: Automated PR,Bot: Automerge'
- name: Check outputs
if: ${{ github.event.inputs.targetEnv == 'PROD' || inputs.targetEnv == 'PROD' }}
run: |
echo "Pull Request Number - ${{ steps.cpr.outputs.pr_number }}"
echo "Pull Request URL - ${{ steps.cpr.outputs.pr_url }}"
- name: automerge
if: ${{ github.event.inputs.targetEnv == 'PROD' || inputs.targetEnv == 'PROD' }}
uses: "pascalgn/automerge-action@v0.14.3"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
PULL_REQUEST: "${{ steps.cpr.outputs.pr_number }}"
MERGE_LABELS: "Bot: Automerge"

75
.github/workflows/release-nocodb.yml

@ -6,48 +6,99 @@ on:
inputs:
tag:
description: "Target Tag"
required: true
required: false
prev_tag:
description: "Previous Tag"
required: true
required: false
jobs:
# Validate Branch
validate-branch:
runs-on: ubuntu-latest
steps:
- run: |
if [[ ${{ github.ref }} != 'refs/heads/master' ]]; then
echo "NocoDB Release is only allowed to run on master branch"
exit 1
fi
# Process Input
process-input:
runs-on: ubuntu-latest
needs: validate-branch
outputs:
target_tag: ${{ steps.process-input.outputs.target_tag }}
prev_tag: ${{ steps.process-input.outputs.prev_tag }}
steps:
- id: process-input
name: process-input
run: |
TARGET_TAG=${{github.event.inputs.tag}}
PREV_TAG=${{github.event.inputs.prev_tag}}
if [[ ${PREV_TAG} == '' ]]; then
# fetch the latest version
PREV_TAG=$(basename $(curl -fs -o/dev/null -w %{redirect_url} https://github.com/nocodb/nocodb/releases/latest))
fi
if [[ ${TARGET_TAG} == '' ]]; then
# bump the version from PREV_TAG
TARGET_TAG=$(echo ${PREV_TAG} | awk -F. -v OFS=. '{$NF += 1 ; print}')
fi
echo target version: ${TARGET_TAG}
echo previous version: ${PREV_TAG}
echo "::set-output name=target_tag::${TARGET_TAG}"
echo "::set-output name=prev_tag::${PREV_TAG}"
- name: Verify
run : |
echo ${{ steps.process-input.outputs.target_tag }}
# Merge develop to master
pr-to-master:
needs: process-input
uses: ./.github/workflows/pr-to-master.yml
with:
tag: ${{ needs.process-input.outputs.target_tag }}
targetEnv: ${{ github.event.inputs.targetEnv || 'PROD' }}
# Close all issues with target tags 'Fixed' & 'Resolved'
close-fixed-issues:
needs: [pr-to-master, process-input]
uses: ./.github/workflows/release-close-issue.yml
with:
issue_label: 'Status: Fixed'
version: ${{ github.event.inputs.tag }}
version: ${{ needs.process-input.outputs.target_tag }}
close-resolved-issues:
needs: close-fixed-issues
needs: [close-fixed-issues, process-input]
uses: ./.github/workflows/release-close-issue.yml
with:
issue_label: 'Status: Resolved'
version: ${{ github.event.inputs.tag }}
version: ${{ needs.process-input.outputs.target_tag }}
# Build, install, publish frontend and backend to npm
release-npm:
needs: close-resolved-issues
needs: [close-resolved-issues, process-input]
uses: ./.github/workflows/release-npm.yml
with:
tag: ${{ github.event.inputs.tag }}
tag: ${{ needs.process-input.outputs.target_tag }}
targetEnv: ${{ github.event.inputs.targetEnv || 'PROD' }}
secrets:
NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"
# Draft Release Note
release-draft-note:
needs: release-npm
needs: [release-npm, process-input]
uses: ./.github/workflows/release-draft.yml
with:
tag: ${{ github.event.inputs.tag }}
prev_tag: ${{ github.event.inputs.prev_tag }}
tag: ${{ needs.process-input.outputs.target_tag }}
prev_tag: ${{ needs.process-input.outputs.prev_tag }}
# Build docker image and push to docker hub
release-docker:
needs: release-draft-note
needs: [release-draft-note, process-input]
uses: ./.github/workflows/release-docker.yml
with:
tag: ${{ github.event.inputs.tag }}
tag: ${{ needs.process-input.outputs.target_tag }}
targetEnv: ${{ github.event.inputs.targetEnv || 'PROD' }}
secrets:
DOCKERHUB_USERNAME: "${{ secrets.DOCKERHUB_USERNAME }}"

1
package.json

@ -14,6 +14,7 @@
},
"scripts": {
"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",
"start:api": "cd ./packages/nocodb; npm install; NC_DISABLE_CACHE=true NC_DISABLE_TELE=true npm run watch:run:cypress",
"start:xcdb-api": "cd ./packages/nocodb; npm install; 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": "cd ./packages/nocodb; npm install; NC_DISABLE_TELE=true npm run watch:run:cypress",

103
packages/nc-gui/components/ProjectTreeView.vue

@ -17,7 +17,7 @@
>
{{ $store.getters["project/GtrProjectName"] }}
</h3>
<github-star-btn v-else />
<github-star-btn v-else/>
</div>
<v-navigation-drawer
ref="drawer"
@ -183,7 +183,8 @@
class="body-2 font-weight-medium"
v-on="on"
>
{{ $t("objects.tables")
{{
$t("objects.tables")
}}<template
v-if="item.children && item.children.length"
>
@ -210,7 +211,8 @@
v-if="item.type === 'tableDir'"
class="body-2 font-weight-medium"
>
{{ $t("objects.tables")
{{
$t("objects.tables")
}}<template
v-if="item.children && item.children.length"
>
@ -228,7 +230,7 @@
</template>
</v-list-item-title>
<v-spacer />
<v-spacer/>
<v-tooltip bottom>
<template #activator="{ on }">
@ -346,7 +348,7 @@
<span v-else class="caption">{{ child.name }}</span>
</v-list-item-title>
<template v-if="child.type === 'table'">
<v-spacer />
<v-spacer/>
<div class="action d-flex" @click.stop>
<v-menu>
<template #activator="{ on }">
@ -481,7 +483,7 @@
/>
</div>
<div class="pr-3 advance-menu d-none" :class="{ 'pl-3': !mini }">
<v-divider v-if="_isUIAllowed('treeViewProjectSettings')" />
<v-divider v-if="_isUIAllowed('treeViewProjectSettings')"/>
<v-list
v-if="_isUIAllowed('treeViewProjectSettings')"
@ -619,7 +621,7 @@
</v-tooltip>
</template>
</v-list>
<v-divider />
<v-divider/>
<v-list v-if="_isUIAllowed('previewAs') || previewAs" dense>
<v-list-item>
@ -676,10 +678,23 @@
</v-list>
</div>
<template v-if="_isUIAllowed('settings')">
<v-divider />
<div class="pt-3 pl-5 pr-3 d-flex align-center pb-2">
<v-divider/>
<div
v-t="['e:api-docs']"
class="caption pointer nc-docs pb-2 pl-5 pr-3 pt-2 d-flex align-center"
@click="openLink(apiLink)"
>
<v-icon color="brown" small class="mr-2">
mdi-open-in-new
</v-icon>
{{ $t('title.apiDocs') }}
</div>
<template v-if="_isUIAllowed('settings')">
<div class="pl-5 pr-3 d-flex align-center pb-2">
<settings-modal>
<template #default="{ click }">
<div
@ -697,18 +712,9 @@
</div>
</template>
<div
v-t="['e:api-docs']"
class="caption pointer nc-docs pb-3 pl-5 pr-3 pt-2 d-flex align-center"
@click="openLink(apiLink)"
>
<v-icon small class="mr-2">
mdi-api
</v-icon>
{{ $t('title.apiDocs') }}
</div>
<v-divider />
<extras class="pl-1" />
<!-- <v-divider/>-->
<!-- <extras class="pl-1"/>-->
</div>
</v-navigation-drawer>
@ -776,7 +782,7 @@
<script>
/* eslint-disable */
import { mapMutations, mapGetters, mapActions } from "vuex";
import {mapMutations, mapGetters, mapActions} from "vuex";
import rightClickOptions from "../helpers/rightClickOptions";
import rightClickOptionsSub from "../helpers/rightClickOptionsSub";
@ -784,11 +790,11 @@ import icons from "../helpers/treeViewIcons";
import textDlgSubmitCancel from "./utils/dlgTextSubmitCancel";
import dlgLabelSubmitCancel from "./utils/dlgLabelSubmitCancel";
import { copyTextToClipboard } from "../helpers/xutils";
import {copyTextToClipboard} from "../helpers/xutils";
import DlgTableCreate from "@/components/utils/dlgTableCreate";
import DlgViewCreate from "@/components/utils/dlgViewCreate";
import SponsorMini from "@/components/sponsorMini";
import { validateTableName } from "~/helpers";
import {validateTableName} from "~/helpers";
import ExcelImport from "~/components/import/excelImport";
import draggable from "vuedraggable";
@ -831,9 +837,9 @@ export default {
commenter: "mdi-comment-account-outline",
},
rolesList: [
{ title: "editor" },
{ title: "commenter" },
{ title: "viewer" },
{title: "editor"},
{title: "commenter"},
{title: "viewer"},
],
showSqlClient: false,
nestedMenu: {},
@ -862,7 +868,7 @@ export default {
x: 0,
y: 0,
menuItem: null,
menu: [{ title: "Execute" }],
menu: [{title: "Execute"}],
icons,
tree: [],
active: [],
@ -906,7 +912,7 @@ export default {
},
}),
computed: {
apiLink(){
apiLink() {
return new URL(`/api/v1/db/meta/projects/${this.projectId}/swagger`, this.$store.state.project.projectInfo && this.$store.state.project.projectInfo.ncSiteUrl)
},
previewAs: {
@ -1051,7 +1057,7 @@ export default {
name: "App Store",
key: `appStore`,
};
item._nodes = { env: "_noco" };
item._nodes = {env: "_noco"};
item._nodes.type = "appStore";
this.$store.dispatch("tabs/ActAddTab", item);
}
@ -1092,7 +1098,7 @@ export default {
name: `${this.$t("title.teamAndAuth")} `,
key: `roles`,
};
item._nodes = { env: "_noco" };
item._nodes = {env: "_noco"};
item._nodes.type = "roles";
this.$store.dispatch("tabs/ActAddTab", item);
}
@ -1108,7 +1114,7 @@ export default {
name: `${this.$t("title.metaMgmt")}`,
key: `disableOrEnableModel`,
};
item._nodes = { env: "_noco" };
item._nodes = {env: "_noco"};
item._nodes.type = "disableOrEnableModel";
this.$store.dispatch("tabs/ActAddTab", item);
}
@ -1332,12 +1338,12 @@ export default {
(n) => n.type === `${this.$route.query.type}Dir`
);
await this.addTab(
{ ...(node || this.listViewArr[0]) },
{...(node || this.listViewArr[0])},
false,
true
);
} else {
await this.addTab({ ...this.listViewArr[0] }, false, true);
await this.addTab({...this.listViewArr[0]}, false, true);
}
}
} catch (error) {
@ -1515,7 +1521,7 @@ export default {
dbAlias: item._nodes.dbAlias,
},
func,
{ tn: item.name },
{tn: item.name},
]);
if (result && result.data) {
copyTextToClipboard(result.data, "selection");
@ -1523,7 +1529,7 @@ export default {
copyTextToClipboard("Example String", "selection");
}
let sqlClientNode = { ...item._nodes };
let sqlClientNode = {...item._nodes};
let newItem = {
_nodes: sqlClientNode,
};
@ -1833,7 +1839,7 @@ export default {
dbAlias: item._nodes.dbAlias,
},
"viewRead",
{ view_name: item._nodes.view_name },
{view_name: item._nodes.view_name},
]);
await this.$store.dispatch("sqlMgr/ActSqlOpPlus", [
@ -1942,7 +1948,8 @@ export default {
this.loadDefaultTabs(true);
// this.loadRoles();
},
beforeCreate() {},
beforeCreate() {
},
mounted() {
// this.setBorderWidth();
// this.setEvents();
@ -2021,9 +2028,9 @@ export default {
}
/deep/
.v-list-group
.v-list-group__header
.v-list-item__icon.v-list-group__header__append-icon {
.v-list-group
.v-list-group__header
.v-list-item__icon.v-list-group__header__append-icon {
min-width: auto;
}
@ -2045,16 +2052,16 @@ export default {
}
/deep/
.nc-table-list-filter.theme--light.v-text-field
> .v-input__control
> .v-input__slot:before {
.nc-table-list-filter.theme--light.v-text-field
> .v-input__control
> .v-input__slot:before {
border-top-color: rgba(0, 0, 0, 0.12) !important;
}
/deep/
.nc-table-list-filter.theme--dark.v-text-field
> .v-input__control
> .v-input__slot:before {
.nc-table-list-filter.theme--dark.v-text-field
> .v-input__control
> .v-input__slot:before {
border-top-color: rgba(255, 255, 255, 0.12) !important;
}

1224
packages/nc-gui/components/createOrEditProject.vue

File diff suppressed because it is too large Load Diff

54
packages/nc-gui/components/project/settings/appearance.vue

@ -60,38 +60,6 @@
</v-tooltip>
</td>
</tr>-->
<tr>
<td>Show M2M Tables</td>
<td>
<v-tooltip bottom>
<template #activator="{ on }">
<v-checkbox
v-model="includeM2M"
v-t="[`c:themes:show-m2m-tables`]"
x-large
color="primary"
v-on="on"
>
mdi-table-network
</v-checkbox>
</template>
Include/Exclude M2M tables
</v-tooltip>
</td>
</tr>
<tr>
<td>Language</td>
<td>
<v-radio-group v-model="language" row>
<v-radio
v-for="{label,value} in languages"
:key="value"
:label="label"
:value="value"
/>
</v-radio-group>
</td>
</tr>
<tr>
<td>Themes</td>
<td class="pa-1">
@ -158,6 +126,28 @@
</v-list>
</td>
</tr>
<tr>
<td>Show M2M Tables</td>
<td>
<v-tooltip bottom>
<template #activator="{ on }">
<v-checkbox
v-model="includeM2M"
v-t="[`c:themes:show-m2m-tables`]"
x-large
color="primary"
v-on="on"
>
mdi-table-network
</v-checkbox>
</template>
Include/Exclude M2M tables
</v-tooltip>
</td>
</tr>
</tbody>
</template>
</v-simple-table>

5
packages/nc-gui/components/project/spreadsheet/components/editColumn/lookupOptions.vue

@ -18,9 +18,9 @@
dense
>
<template #item="{item}">
<span class="caption"><span class="font-weight-bold"> {{
<span class="caption"><span class="font-weight-bold">{{ item.column.title }}</span> <small>({{ relationNames[item.col.type] }} {{
item.title || item.table_name
}}</span> <small>({{ relationNames[item.col.type] }})
}})
</small></span>
</template>
</v-autocomplete>
@ -74,6 +74,7 @@ export default {
c.uidt === UITypes.LinkToAnotherRecord && !c.system
).map(c => ({
col: c.colOptions,
column: c,
...this.tables.find(t => t.id === c.colOptions.fk_related_model_id)
}))

5
packages/nc-gui/components/project/spreadsheet/components/editColumn/rollupOptions.vue

@ -18,9 +18,9 @@
dense
>
<template #item="{item}">
<span class="caption"><span class="font-weight-bold"> {{
<span class="caption"><span class="font-weight-bold">{{ item.column.title }}</span> <small>({{ relationNames[item.col.type] }} {{
item.title || item.table_name
}}</span> <small>({{ relationNames[item.col.type] }})
}})
</small></span>
</template>
</v-autocomplete>
@ -98,6 +98,7 @@ export default {
c.uidt === UITypes.LinkToAnotherRecord && c.colOptions.type !== 'bt' && !c.system
).map(c => ({
col: c.colOptions,
column: c,
...this.tables.find(t => t.id === c.colOptions.fk_related_model_id)
}))

12
packages/nc-gui/components/project/spreadsheet/components/extras.vue

@ -31,20 +31,20 @@
dense
>
<v-list-item>
<div class="d-flex justify-space-between d-100 pr-2">
<v-icon v-t="['e:community:discord']" class="mr-1" size="22" :color="textColors[0]" @click="open('https://discord.gg/5RgZmkW')">
<div class="justify-space-between d-100 pr-2">
<v-icon v-t="['e:community:discord']" size="22" :color="textColors[0]" @click="open('https://discord.gg/5RgZmkW')">
mdi-discord
</v-icon>
<v-icon v-t="['e:community:discourse']" class="mr-1 discourse" size="22" :color="textColors[0]" @click="open('https://community.nocodb.com/')">
<v-icon v-t="['e:community:discourse']" class=" discourse" size="22" :color="textColors[0]" @click="open('https://community.nocodb.com/')">
mdi-discourse
</v-icon>
<v-icon v-t="['e:community:reddit']" class="mr-1" size="22" color="#ff4600" @click="open('https://www.reddit.com/r/NocoDB/')">
<v-icon v-t="['e:community:reddit']" size="22" color="#ff4600" @click="open('https://www.reddit.com/r/NocoDB/')">
mdi-reddit
</v-icon>
<v-icon v-t="['e:community:twitter']" class="mr-1" size="22" :color="textColors[1]" @click="open('https://twitter.com/NocoDB')">
<v-icon v-t="['e:community:twitter']" size="22" :color="textColors[1]" @click="open('https://twitter.com/NocoDB')">
mdi-twitter
</v-icon>
<v-icon v-t="['e:community:book-demo']" class="mr-1" size="22" :color="textColors[3]" @click="open('https://calendly.com/nocodb-meeting')">
<v-icon v-t="['e:community:book-demo']" size="22" :color="textColors[3]" @click="open('https://calendly.com/nocodb-meeting')">
mdi-calendar-month
</v-icon>
</div>

5
packages/nc-gui/components/project/spreadsheet/components/spreadsheetNavDrawer.vue

@ -270,7 +270,10 @@
</v-icon>
<!-- <extras />-->
<sponsor-mini nav />
<v-divider/>
<extras class="pl-1"/>
<!-- <sponsor-mini nav />-->
</div>
<!--<div class="text-center">
<v-hover >

1
packages/nc-gui/components/project/spreadsheet/components/virtualCell/formulaCell.vue

@ -49,6 +49,7 @@ export default {
// handle date returned from PostgreSQL
handleTZ(val) {
if (!val) { return }
if (typeof val !== 'string') { return val }
return val.replace(/((?:-?(?:[1-9][0-9]*)?[0-9]{4})-(?:1[0-2]|0[1-9])-(?:3[01]|0[1-9]|[12][0-9])T(?:2[0-3]|[01][0-9]):(?:[0-5][0-9]):(?:[0-5][0-9])(?:\.[0-9]+)?(?:Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9]))/g, (i, v) => {
return dayjs(v).format('YYYY-MM-DD HH:mm')
})

14
packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue

@ -202,20 +202,20 @@ export default {
return ''
},
childTable() {
if (this.relationMeta?.table_name) {
return this.relationMeta.table_name
if (this.relationMeta?.title) {
return this.relationMeta.title
}
return ''
},
parentTable() {
if (this.rels.includes(this.type)) {
return this.meta.table_name
return this.meta.title
}
return ''
},
parentColumn() {
if (this.rels.includes(this.type)) {
return this.column.column_name
return this.column.title
}
return ''
},
@ -228,13 +228,13 @@ export default {
} else if (this.type === 'mm') {
return `'${this.childTable}' & '${this.parentTable}' have <br>many to many relation`
} else if (this.type === 'bt') {
return `'${this.childColumn.column_name}' belongs to '${this.childTable}'`
return `'${this.column.title}' belongs to '${this.childTable}'`
} else if (this.type === 'lk') {
return `'${this.childColumn.column_name}' from '${this.childTable}' (${this.childColumn.uidt})`
return `'${this.childColumn.title}' from '${this.childTable}' (${this.childColumn.uidt})`
} else if (this.type === 'formula') {
return `Formula - ${this.column.colOptions.formula}`
} else if (this.type === 'rl') {
return `'${this.childColumn.column_name}' of '${this.childTable}' (${this.childColumn.uidt})`
return `'${this.childColumn.title}' of '${this.childTable}' (${this.childColumn.uidt})`
}
return ''
}

8
packages/nc-gui/components/project/spreadsheet/helpers/uiTypes.js

@ -1,8 +1,8 @@
const uiTypes = [
{
name: 'ID',
icon: 'mdi-identifier'
},
// {
// name: 'ID',
// icon: 'mdi-identifier'
// },
{
name: 'LinkToAnotherRecord',
icon: 'mdi-link-variant',

110
packages/nc-gui/components/project/table.vue

@ -14,11 +14,12 @@
>
<template v-if="_isUIAllowed('smartSheet')">
<v-tab v-show="relationTabs && relationTabs.length" class="">
<v-icon small> mdi-table-edit </v-icon>&nbsp;<span
<v-icon small>
mdi-table-edit
</v-icon>&nbsp;<span
class="caption text-capitalize font-weight-bold"
>
{{ nodes.title }}</span
>
{{ nodes.title }}</span>
</v-tab>
<v-tab-item style="height: 100%">
<rows-xc-data-table
@ -49,15 +50,16 @@
</template>
<script>
import { mapActions } from "vuex";
import dlgLabelSubmitCancel from "../utils/dlgLabelSubmitCancel";
import { isMetaTable } from "@/helpers/xutils";
import RowsXcDataTable from "@/components/project/spreadsheet/rowsXcDataTable";
import { mapActions } from 'vuex'
import { UITypes } from 'nocodb-sdk'
import dlgLabelSubmitCancel from '../utils/dlgLabelSubmitCancel'
import { isMetaTable } from '@/helpers/xutils'
import RowsXcDataTable from '@/components/project/spreadsheet/rowsXcDataTable'
export default {
components: {
RowsXcDataTable,
dlgLabelSubmitCancel,
dlgLabelSubmitCancel
},
data() {
return {
@ -72,87 +74,103 @@ export default {
loadRows: false,
loadColumnsMock: false,
relationTabs: [],
deleteId: null,
};
deleteId: null
}
},
methods: {
async handleKeyDown(event) {
const activeTabEleKey = `tabs${this.active}`;
const activeTabEleKey = `tabs${this.active}`
if (
this.$refs[activeTabEleKey] &&
this.$refs[activeTabEleKey].handleKeyDown
) {
await this.$refs[activeTabEleKey].handleKeyDown(event);
await this.$refs[activeTabEleKey].handleKeyDown(event)
}
},
...mapActions({
removeTableTab: "tabs/removeTableTab",
loadTablesFromParentTreeNode: "project/loadTablesFromParentTreeNode",
removeTableTab: 'tabs/removeTableTab',
loadTablesFromParentTreeNode: 'project/loadTablesFromParentTreeNode'
}),
mtdNewTableUpdate(value) {
this.newTableCopy = value;
this.newTableCopy = value
},
async deleteTable(action = "", id) {
async deleteTable(action = '', id) {
if (id) {
this.deleteId = id;
this.deleteId = id
}
if (action === "showDialog") {
this.dialogShow = true;
} else if (action === "hideDialog") {
this.dialogShow = false;
if (action === 'showDialog') {
this.dialogShow = true
} else if (action === 'hideDialog') {
this.dialogShow = false
} else {
// todo : check relations and triggers
try {
await this.$api.dbTable.delete(this.deleteId);
const meta = await this.$store.dispatch('meta/ActLoadMeta', { id: this.deleteId })
const relationColumns = meta.columns.filter(c => c.uidt === UITypes.LinkToAnotherRecord)
if (relationColumns.length) {
const refColMsgs = await Promise.all(relationColumns.map(async(c, i) => {
const refMeta = await this.$store.dispatch('meta/ActLoadMeta', { id: c.colOptions.fk_related_model_id })
return `${i + 1}. ${c.title} is a LinkToAnotherRecord of ${(refMeta && refMeta.title) || c.title}`
}))
this.$toast.info(`<div style="padding:10px 4px">Unable to delete tables because of the following.
<br><br>${refColMsgs.join('<br>')}<br><br>
Delete them & try again</div>
`).goAway(10000)
this.dialogShow = false
return
}
await this.$api.dbTable.delete(this.deleteId)
this.removeTableTab({
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
table_name: this.nodes.table_name,
});
table_name: this.nodes.table_name
})
await this.loadTablesFromParentTreeNode({
_nodes: {
...this.nodes,
},
});
...this.nodes
}
})
this.$store.commit("meta/MutMeta", {
this.$store.commit('meta/MutMeta', {
key: this.nodes.table_name,
value: null,
});
this.$store.commit("meta/MutMeta", {
value: null
})
this.$store.commit('meta/MutMeta', {
key: this.deleteId,
value: null,
});
value: null
})
} catch (e) {
const msg = await this._extractSdkResponseErrorMsg(e);
this.$toast.error(msg).goAway(3000);
const msg = await this._extractSdkResponseErrorMsg(e)
this.$toast.error(msg).goAway(3000)
}
this.dialogShow = false;
this.$e("a:table:delete");
this.dialogShow = false
this.$e('a:table:delete')
}
},
onTabChange() {
this.$emit("update:hideLogWindows", this.active === 2);
},
this.$emit('update:hideLogWindows', this.active === 2)
}
},
computed: {
isMetaTable() {
return isMetaTable(this.nodes.table_name);
},
return isMetaTable(this.nodes.table_name)
}
},
mounted() {
this.onTabChange();
this.onTabChange()
},
props: {
nodes: Object,
hideLogWindows: Boolean,
tabId: String,
isActive: Boolean,
isView: Boolean,
},
};
isView: Boolean
}
}
</script>
<style scoped>

2
packages/nc-gui/components/utils/language.vue

@ -24,7 +24,7 @@
<v-divider />
<v-list-item>
<a
href="https://github.com/nocodb/nocodb/tree/master/packages/nc-gui/lang"
href="https://docs.nocodb.com/engineering/translation/#how-to-contribute--for-community-members"
target="_blank"
class="caption"
>

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

@ -191,6 +191,7 @@
"port": "Port nummer.",
"username": "Brugernavn.",
"password": "Adgangskode",
"schemaName": "Schema name",
"action": "Handling",
"actions": "Handlinger",
"operation": "Operation",

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

@ -191,6 +191,7 @@
"port": "Port-Nummer",
"username": "Benutzername",
"password": "Passwort",
"schemaName": "Schema name",
"action": "Aktion",
"actions": "Aktionen",
"operation": "Vorgang",

1
packages/nc-gui/lang/en.json

@ -191,6 +191,7 @@
"port": "Port Number",
"username": "Username",
"password": "Password",
"schemaName": "Schema name",
"action": "Action",
"actions": "Actions",
"operation": "Operation",

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

@ -191,6 +191,7 @@
"port": "Puerto",
"username": "Usuario",
"password": "Contraseña",
"schemaName": "Schema name",
"action": "Acción",
"actions": "Acciones",
"operation": "Operación",

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

@ -191,6 +191,7 @@
"port": "شماره Port",
"username": "نام کاربری",
"password": "کلمه عبور",
"schemaName": "Schema name",
"action": "اقدام",
"actions": "اقدامات",
"operation": "عملیات",

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

@ -191,6 +191,7 @@
"port": "Porttinumero",
"username": "Käyttäjätunnus",
"password": "Salasana",
"schemaName": "Schema name",
"action": "Toiminta",
"actions": "Toiminnot",
"operation": "Operaatio",

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

@ -191,6 +191,7 @@
"port": "Numéro de port",
"username": "Utilisateur",
"password": "Mot de passe",
"schemaName": "Schema name",
"action": "Action",
"actions": "Actions",
"operation": "Opération",

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

@ -191,6 +191,7 @@
"port": "Broj porta",
"username": "Korisničko ime",
"password": "Lozinka",
"schemaName": "Schema name",
"action": "Akcijski",
"actions": "Akcije",
"operation": "Operacija",

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

@ -191,6 +191,7 @@
"port": "Nomor port.",
"username": "Nama pengguna",
"password": "Kata sandi",
"schemaName": "Schema name",
"action": "Tindakan",
"actions": "Tindakan",
"operation": "Operasi",

1
packages/nc-gui/lang/it_IT.json

@ -191,6 +191,7 @@
"port": "Numero di porta",
"username": "Nome utente",
"password": "Password",
"schemaName": "Schema name",
"action": "Azione",
"actions": "Azioni",
"operation": "Operazione",

1
packages/nc-gui/lang/iw.json

@ -191,6 +191,7 @@
"port": "פורט",
"username": "שם משתמש",
"password": "סיסמה",
"schemaName": "Schema name",
"action": "פעולה",
"actions": "פעולות",
"operation": "מבצע",

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

@ -191,6 +191,7 @@
"port": "ポート番号",
"username": "ユーザー名",
"password": "パスワード",
"schemaName": "Schema name",
"action": "アクション",
"actions": "アクション",
"operation": "操作",

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

@ -191,6 +191,7 @@
"port": "포트 번호",
"username": "사용자 이름",
"password": "비밀번호",
"schemaName": "Schema name",
"action": "동작",
"actions": "행위",
"operation": "작업",

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

@ -191,6 +191,7 @@
"port": "Porta numurs",
"username": "Lietotājvārds",
"password": "Parole",
"schemaName": "Schema name",
"action": "Darbība",
"actions": "Darbības",
"operation": "Operācija",

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

@ -191,6 +191,7 @@
"port": "Poortnummer",
"username": "Gebruikersnaam",
"password": "Wachtwoord",
"schemaName": "Schema name",
"action": "Actie",
"actions": "Acties",
"operation": "Operatie",

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

@ -191,6 +191,7 @@
"port": "Portnummer",
"username": "Brukernavn",
"password": "Passord",
"schemaName": "Schema name",
"action": "Handling",
"actions": "Handlinger",
"operation": "Operasjon",

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

@ -191,6 +191,7 @@
"port": "Numer portu",
"username": "Nazwa użytkownika",
"password": "Hasło",
"schemaName": "Schema name",
"action": "Akcja",
"actions": "działania",
"operation": "Operacja",

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

@ -191,6 +191,7 @@
"port": "Número da Porta",
"username": "Utilizador",
"password": "Palavra-passe",
"schemaName": "Schema name",
"action": "Açao",
"actions": "Ações",
"operation": "Operação",

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

@ -191,6 +191,7 @@
"port": "Número da Porta",
"username": "Usuário",
"password": "Senha",
"schemaName": "Schema name",
"action": "Açao",
"actions": "Ações",
"operation": "Operação",

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

@ -191,6 +191,7 @@
"port": "Номер порта",
"username": "Имя пользователя",
"password": "Пароль",
"schemaName": "Schema name",
"action": "Действие",
"actions": "Действия",
"operation": "Операция",

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

@ -191,6 +191,7 @@
"port": "Številka vrat",
"username": "Uporabniško ime",
"password": "Geslo",
"schemaName": "Schema name",
"action": "Akcija",
"actions": "Akcijah",
"operation": "Operacija",

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

@ -191,6 +191,7 @@
"port": "Portnummer",
"username": "Användarnamn",
"password": "Lösenord",
"schemaName": "Schema name",
"action": "Handling",
"actions": "Handlingar",
"operation": "Drift",

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

@ -191,6 +191,7 @@
"port": "หมายเลขพอรต",
"username": "ชอผใช",
"password": "รหสผาน",
"schemaName": "Schema name",
"action": "หนงบ",
"actions": "การกระทำ",
"operation": "การดำเนนการ",

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

@ -191,6 +191,7 @@
"port": "Port Numarası",
"username": "Kullanıcı Adı",
"password": "Şifre",
"schemaName": "Schema name",
"action": "Aksiyon",
"actions": "Aksiyonlar",
"operation": "Operasyon",

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

@ -191,6 +191,7 @@
"port": "Номер порту",
"username": "Ім'я користувача",
"password": "Пароль",
"schemaName": "Schema name",
"action": "Дія",
"actions": "Акції",
"operation": "Операція",

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

@ -191,6 +191,7 @@
"port": "Cổng số",
"username": "tên tài khoản",
"password": "Mật khẩu",
"schemaName": "Schema name",
"action": "Hoạt động",
"actions": "Hành động",
"operation": "Hoạt động",

1
packages/nc-gui/lang/zh_CN.json

@ -191,6 +191,7 @@
"port": "端口号",
"username": "用户名",
"password": "密码",
"schemaName": "Schema name",
"action": "行动",
"actions": "行动",
"operation": "操作",

1
packages/nc-gui/lang/zh_HK.json

@ -191,6 +191,7 @@
"port": "端口號碼",
"username": "用戶名稱",
"password": "密碼",
"schemaName": "Schema name",
"action": "行動",
"actions": "行動",
"operation": "操作",

1
packages/nc-gui/lang/zh_TW.json

@ -191,6 +191,7 @@
"port": "連線埠號碼",
"username": "使用者名稱",
"password": "密碼",
"schemaName": "Schema name",
"action": "行動",
"actions": "行動",
"operation": "操作",

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

@ -160,6 +160,7 @@
</v-list-item-title>
</v-list-item>
<v-list-item
v-if="isDashboard"
v-t="['a:navbar:user:swagger']"
dense
@click.stop="

4
packages/noco-docs/content/en/FAQs.md

@ -23,13 +23,13 @@ menuTitle: 'FAQs'
- You can hover the NocoDB icon on the top left corner or check ``PackageVersion`` in Project info.
![image](https://user-images.githubusercontent.com/35857179/161957765-922058dc-dba1-44c5-95f4-2924c875e1c6.png)
![image](https://user-images.githubusercontent.com/35857179/164968969-da53adda-97fc-4a79-9331-039ddef75a13.png)
## How to check my Project info ?
- You can click on top right icon and click ``Copy Project Info``.
![image](https://user-images.githubusercontent.com/35857179/161957820-e0ac8b04-aafd-40c3-8fa8-2fdaa7dc157f.png)
![image](https://user-images.githubusercontent.com/35857179/164968940-6c987863-e7d8-4b44-a46d-2f755825af0a.png)
You should see the similar result as below.

2
packages/noco-docs/content/en/setup-and-usages/usage-information.md

@ -42,7 +42,7 @@ Here is an example :
"os_release" : "5.10.25-linuxkit",
"node_version" : "14.18.2",
"docker" : "true",
"xc_version" : "0.84.15",
"xc_version" : "a0885e8e6a38d9fbb5d39e7d04a44da7773d4f",
"env" : "dev",
}

11
packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts

@ -139,9 +139,9 @@ export class MssqlUi {
static getNewColumn(suffix) {
return {
column_name: 'title' + suffix,
dt: 'int',
dt: 'varchar',
dtx: 'specificType',
ct: 'integer(11)',
ct: 'varchar(45)',
nrqd: true,
rqd: false,
ck: false,
@ -152,11 +152,10 @@ export class MssqlUi {
clen: 45,
np: null,
ns: null,
// data_type_x_specific: ' ',
dtxp: '',
dtxs: ' ',
dtxp: '45',
dtxs: '',
altered: 1,
uidt: 'Number',
uidt: 'SingleLineText',
uip: '',
uicn: ''
};

15
packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts

@ -143,9 +143,9 @@ export class MysqlUi {
static getNewColumn(suffix) {
return {
column_name: 'title' + suffix,
dt: 'int',
dt: 'varchar',
dtx: 'specificType',
ct: 'integer(11)',
ct: 'varchar(45)',
nrqd: true,
rqd: false,
ck: false,
@ -156,11 +156,10 @@ export class MysqlUi {
clen: 45,
np: null,
ns: null,
// data_type_x_specific: ' ',
dtxp: '11',
dtxs: ' ',
dtxp: '45',
dtxs: '',
altered: 1,
uidt: 'Number',
uidt: 'SingleLineText',
uip: '',
uicn: ''
};
@ -678,14 +677,14 @@ export class MysqlUi {
// set headers before settings result
for (let i = 0; i < keys.length; i++) {
const text = keys[i];
headers.push({ text, value: text, sortable: false });
headers.push({text, value: text, sortable: false});
}
} else {
const keys = Object.keys(result);
for (let i = 0; i < keys.length; i++) {
const text = keys[i];
if (typeof text !== 'function') {
headers.push({ text, value: text, sortable: false });
headers.push({text, value: text, sortable: false});
}
}
result = [result];

11
packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts

@ -89,9 +89,9 @@ export class OracleUi {
static getNewColumn(suffix) {
return {
column_name: 'title' + suffix,
dt: 'integer',
dt: 'varchar',
dtx: 'specificType',
ct: 'integer(11)',
ct: 'varchar(45)',
nrqd: true,
rqd: false,
ck: false,
@ -102,11 +102,10 @@ export class OracleUi {
clen: 45,
np: null,
ns: null,
// data_type_x_specific: ' ',
dtxp: '11',
dtxs: ' ',
dtxp: '45',
dtxs: '',
altered: 1,
uidt: 'Number',
uidt: 'SingleLineText',
uip: '',
uicn: ''
};

11
packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts

@ -203,9 +203,9 @@ export class PgUi {
static getNewColumn(suffix) {
return {
column_name: 'title' + suffix,
dt: 'int4',
dt: 'character varying',
dtx: 'specificType',
ct: 'integer(11)',
ct: 'varchar(45)',
nrqd: true,
rqd: false,
ck: false,
@ -216,11 +216,10 @@ export class PgUi {
clen: 45,
np: null,
ns: null,
// data_type_x_specific: ' ',
dtxp: '11',
dtxs: ' ',
dtxp: '45',
dtxs: '',
altered: 1,
uidt: 'Number',
uidt: 'SingleLineText',
uip: '',
uicn: ''
};

9
packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts

@ -126,9 +126,9 @@ export class SqliteUi {
static getNewColumn(suffix) {
return {
column_name: 'title' + suffix,
dt: 'integer',
dt: 'varchar',
dtx: 'specificType',
ct: 'integer(11)',
ct: 'varchar',
nrqd: true,
rqd: false,
ck: false,
@ -139,11 +139,10 @@ export class SqliteUi {
clen: 45,
np: null,
ns: null,
// data_type_x_specific: ' ',
dtxp: '',
dtxs: ' ',
dtxs: '',
altered: 1,
uidt: 'Number',
uidt: 'SingleLineText',
uip: '',
uicn: ''
};

25203
packages/nocodb/package-lock.json generated

File diff suppressed because it is too large Load Diff

2
packages/nocodb/package.json

@ -150,7 +150,7 @@
"mysql2": "^2.2.5",
"nanoid": "^3.1.20",
"nc-common": "0.0.6",
"nc-help": "^0.2.44",
"nc-help": "0.2.46",
"nc-lib-gui": "0.90.7",
"nc-plugin": "^0.1.1",
"ncp": "^2.0.0",

62
packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSqlv2.ts

@ -21,7 +21,6 @@ import View from '../../../noco-models/View';
import {
AuditOperationSubTypes,
AuditOperationTypes,
isSystemColumn,
RelationTypes,
SortType,
UITypes,
@ -288,69 +287,8 @@ class BaseModelSqlv2 {
return ((await qb) as any).count;
}
public async defaultResolverReq(
query?: any,
extractOnlyPrimaries = false,
includePkByDefault = true
) {
await this.model.getColumns();
if (extractOnlyPrimaries) {
return {
[this.model.primaryKey.title]: 1,
[this.model.primaryValue.title]: 1
};
}
let fields = query?.fields || query?.f;
if (fields && fields !== '*') {
fields = Array.isArray(fields) ? fields : fields.split(',');
} else {
fields = null;
}
let allowedCols = null;
if (this.viewId)
allowedCols = (await View.getColumns(this.viewId)).reduce(
(o, c) => ({
...o,
[c.fk_column_id]: c.show
}),
{}
);
const view = await View.get(this.viewId);
return this.model.getColumns().then(columns =>
Promise.resolve(
columns.reduce(
(obj, col) => ({
...obj,
[col.title]:
allowedCols && (!includePkByDefault || !col.pk)
? allowedCols[col.id] &&
(!isSystemColumn(col) || view.show_system_fields) &&
(!fields?.length || fields.includes(col.title))
: fields?.length
? fields.includes(col.title)
: 1
}),
{}
)
)
);
}
async multipleHmList({ colId, ids }, args?: { limit?; offset? }) {
try {
// const {
// where,
// limit,
// offset,
// conditionGraph,
// sort
// // ...restArgs
// } = this.dbModels[child]._getChildListArgs(args);
// let { fields } = restArgs;
// todo: get only required fields
let fields = '*';

2
packages/nocodb/src/lib/dataMapper/lib/sql/conditionV2.ts

@ -349,7 +349,7 @@ async function generateLookupCondition(
};
} else if (relationColumnOptions.type === RelationTypes.BELONGS_TO) {
qb = knex(`${parentModel.table_name} as ${alias}`);
qb.select(`${alias}.${childColumn.column_name}`);
qb.select(`${alias}.${parentColumn.column_name}`);
await nestedConditionJoin(
{

13
packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/pg.ts

@ -35,17 +35,8 @@ const pg = {
},
DATEADD: ({ fn, knex, pt, colAlias }: MapFnArgs) => {
return knex.raw(
`CASE
WHEN CAST(${fn(pt.arguments[0])} AS text) LIKE '%:%' THEN
${fn(pt.arguments[0])} + INTERVAL '${fn(pt.arguments[1])}
${String(fn(pt.arguments[2])).replace(
/["']/g,
''
)}'
ELSE
${fn(pt.arguments[0])} + INTERVAL '${fn(pt.arguments[1])}
${String(fn(pt.arguments[2])).replace(/["']/g, '')}'
END${colAlias}`
`${fn(pt.arguments[0])} + (${fn(pt.arguments[1])} ||
'${String(fn(pt.arguments[2])).replace(/["']/g, '')}')::interval${colAlias}`
);
}
};

4
packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/sqlite.ts

@ -64,13 +64,13 @@ const sqlite3 = {
STRFTIME('%Y-%m-%d %H:%M', DATETIME(DATETIME(${fn(
pt.arguments[0]
)}, 'localtime'),
'${dateIN > 0 ? '+' : ''}${fn(pt.arguments[1])} ${String(fn(pt.arguments[2])).replace(
${dateIN > 0 ? '+' : ''}${fn(pt.arguments[1])} || ' ${String(fn(pt.arguments[2])).replace(
/["']/g,
''
)}'))
ELSE
DATE(DATETIME(${fn(pt.arguments[0])}, 'localtime'),
'${dateIN > 0 ? '+' : ''}${fn(pt.arguments[1])} ${String(fn(pt.arguments[2])).replace(
${dateIN > 0 ? '+' : ''}${fn(pt.arguments[1])} || ' ${String(fn(pt.arguments[2])).replace(
/["']/g,
''
)}')

99
packages/nocodb/src/lib/dataMapper/lib/sql/helpers/getAst.ts

@ -0,0 +1,99 @@
import View from '../../../../noco-models/View';
import { isSystemColumn, UITypes } from 'nocodb-sdk';
import Model from '../../../../noco-models/Model';
import LinkToAnotherRecordColumn from '../../../../noco-models/LinkToAnotherRecordColumn';
const getAst = async ({
query,
extractOnlyPrimaries = false,
includePkByDefault = true,
model,
view
}: {
query?: RequestQuery;
extractOnlyPrimaries?: boolean;
includePkByDefault?: boolean;
model: Model;
view?: View;
}) => {
if (!model.columns?.length) await model.getColumns();
if (extractOnlyPrimaries) {
return {
[model.primaryKey.title]: 1,
[model.primaryValue.title]: 1
};
}
let fields = query?.fields || query?.f;
if (fields && fields !== '*') {
fields = Array.isArray(fields) ? fields : fields.split(',');
} else {
fields = null;
}
let allowedCols = null;
if (view)
allowedCols = (await View.getColumns(view.id)).reduce(
(o, c) => ({
...o,
[c.fk_column_id]: c.show
}),
{}
);
return model.columns.reduce(async (obj, col) => {
let value: number | boolean | { [key: string]: any } = 1;
const nestedFields =
query?.nested?.[col.title]?.fields || query?.nested?.[col.title]?.f;
if (nestedFields && nestedFields !== '*') {
if (col.uidt === UITypes.LinkToAnotherRecord) {
const model = await col
.getColOptions<LinkToAnotherRecordColumn>()
.then(colOpt => colOpt.getRelatedTable());
value = await getAst({
model,
query: query?.nested?.[col.title]
});
} else {
value = (Array.isArray(fields) ? fields : fields.split(',')).reduce(
(o, f) => ({ ...o, [f]: 1 }),
{}
);
}
} else if (col.uidt === UITypes.LinkToAnotherRecord) {
const model = await col
.getColOptions<LinkToAnotherRecordColumn>()
.then(colOpt => colOpt.getRelatedTable());
value = await getAst({
model,
query: query?.nested,
extractOnlyPrimaries: true
});
}
return {
...(await obj),
[col.title]:
allowedCols && (!includePkByDefault || !col.pk)
? allowedCols[col.id] &&
(!isSystemColumn(col) || view.show_system_fields) &&
(!fields?.length || fields.includes(col.title)) &&
value
: fields?.length
? fields.includes(col.title)
: value
};
}, Promise.resolve({}));
};
type RequestQuery = {
[fields in 'f' | 'fields']?: string | string[];
} & {
nested?: {
[field: string]: RequestQuery;
};
};
export default getAst;

11
packages/nocodb/src/lib/noco-models/Model.ts

@ -51,14 +51,6 @@ export default class Model implements TableType {
columnsById?: { [id: string]: Column };
views?: View[];
// private static baseModels: {
// [baseId: string]: {
// [dbAlias: string]: {
// [tableIdOrName: string]: BaseModelSqlv2;
// };
// };
// } = {};
constructor(data: Partial<TableType | Model>) {
Object.assign(this, data);
}
@ -124,7 +116,8 @@ export default class Model implements TableType {
})),
type: model.type || ModelTypes.TABLE,
created_at: model.created_at,
updated_at: model.updated_at
updated_at: model.updated_at,
id: model.id
}
);

75
packages/nocodb/src/lib/noco-models/Project.ts

@ -179,13 +179,16 @@ export default class Project implements ProjectType {
// get existing cache
const key = `${CacheScope.PROJECT}:${projectId}`;
const o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);
if (o?.uuid) {
await NocoCache.del(`${CacheScope.PROJECT}:${o.uuid}`);
}
if (o) await NocoCache.del(`${CacheScope.PROJECT}:${projectId}`);
if (o?.title) {
if (o) {
// delete <scope>:<id>
await NocoCache.del(`${CacheScope.PROJECT}:${projectId}`);
// delete <scope>:<title>
await NocoCache.del(`${CacheScope.PROJECT}:${o.title}`);
// delete <scope>:<uuid>
await NocoCache.del(`${CacheScope.PROJECT}:${o.uuid}`);
// delete <scope>:ref:<titleOfId>
await NocoCache.del(`${CacheScope.PROJECT}:ref:${o.title}`);
await NocoCache.del(`${CacheScope.PROJECT}:ref:${o.id}`);
}
// remove item in cache list
@ -264,12 +267,17 @@ export default class Project implements ProjectType {
await base.delete(ncMeta);
}
const project = await this.get(projectId);
if (project.uuid) {
if (project) {
// delete <scope>:<uuid>
await NocoCache.del(`${CacheScope.PROJECT}:${project.uuid}`);
}
if (project.title) {
// delete <scope>:<title>
await NocoCache.del(`${CacheScope.PROJECT}:${project.title}`);
// delete <scope>:ref:<titleOfId>
await NocoCache.del(`${CacheScope.PROJECT}:ref:${project.title}`);
await NocoCache.del(`${CacheScope.PROJECT}:ref:${project.id}`);
}
await NocoCache.deepDel(
CacheScope.PROJECT,
`${CacheScope.PROJECT}:${projectId}`,
@ -323,4 +331,53 @@ export default class Project implements ProjectType {
}
return projectData?.id && this.get(projectData?.id, ncMeta);
}
static async getByTitleOrId(titleOrId: string, ncMeta = Noco.ncMeta) {
const projectId =
titleOrId &&
(await NocoCache.get(
`${CacheScope.PROJECT}:ref:${titleOrId}`,
CacheGetType.TYPE_OBJECT
));
let projectData = null;
if (!projectId) {
projectData = await Noco.ncMeta.metaGet2(
null,
null,
MetaTable.PROJECT,
{
deleted: false
},
null,
{
_or: [
{
id: {
eq: titleOrId
}
},
{
title: {
eq: titleOrId
}
}
]
}
);
await NocoCache.set(
`${CacheScope.PROJECT}:ref:${titleOrId}`,
projectData?.id
);
} else {
return this.get(projectId);
}
return projectData?.id && this.get(projectData?.id, ncMeta);
}
static async getWithInfoByTitleOrId(titleOrId: string, ncMeta = Noco.ncMeta) {
const project = await this.getByTitleOrId(titleOrId, ncMeta);
if (project) await project.getBases(ncMeta);
return project;
}
}

14
packages/nocodb/src/lib/noco/Noco.ts

@ -568,14 +568,28 @@ export default class Noco {
}
this.config.auth.jwt.secret = secret;
}
let serverId = (
await Noco._ncMeta.metaGet('', '', 'nc_store', {
key: 'nc_server_id'
})
)?.value;
if (!serverId) {
await Noco._ncMeta.metaInsert('', '', 'nc_store', {
key: 'nc_server_id',
value: serverId = Tele.id
});
}
process.env.NC_SERVER_UUID = serverId;
}
public static get ncMeta(): NcMetaIO {
return this._ncMeta;
}
public get ncMeta(): NcMetaIO {
return Noco._ncMeta;
}
public static getConfig(): NcConfig {
return Noco.config;
}

6
packages/nocodb/src/lib/noco/meta/api/dataApis/bulkDataAliasApis.ts

@ -4,6 +4,7 @@ import Base from '../../../../noco-models/Base';
import NcConnectionMgrv2 from '../../../common/NcConnectionMgrv2';
import ncMetaAclMw from '../../helpers/ncMetaAclMw';
import { getViewAndModelFromRequestByAliasOrId } from './helpers';
import apiMetrics from '../../helpers/apiMetrics';
async function bulkDataInsert(req: Request, res: Response) {
const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
@ -72,22 +73,27 @@ const router = Router({ mergeParams: true });
router.post(
'/api/v1/db/data/bulk/:orgs/:projectName/:tableName',
apiMetrics,
ncMetaAclMw(bulkDataInsert, 'bulkDataInsert')
);
router.patch(
'/api/v1/db/data/bulk/:orgs/:projectName/:tableName',
apiMetrics,
ncMetaAclMw(bulkDataUpdate, 'bulkDataUpdate')
);
router.patch(
'/api/v1/db/data/bulk/:orgs/:projectName/:tableName/all',
apiMetrics,
ncMetaAclMw(bulkDataUpdateAll, 'bulkDataUpdateAll')
);
router.delete(
'/api/v1/db/data/bulk/:orgs/:projectName/:tableName',
apiMetrics,
ncMetaAclMw(bulkDataDelete, 'bulkDataDelete')
);
router.delete(
'/api/v1/db/data/bulk/:orgs/:projectName/:tableName/all',
apiMetrics,
ncMetaAclMw(bulkDataDeleteAll, 'bulkDataDeleteAll')
);

23
packages/nocodb/src/lib/noco/meta/api/dataApis/dataAliasApis.ts

@ -7,6 +7,8 @@ import { PagedResponseImpl } from '../../helpers/PagedResponse';
import View from '../../../../noco-models/View';
import ncMetaAclMw from '../../helpers/ncMetaAclMw';
import { getViewAndModelFromRequestByAliasOrId } from './helpers';
import apiMetrics from '../../helpers/apiMetrics';
import getAst from '../../../../dataMapper/lib/sql/helpers/getAst';
async function dataList(req: Request, res: Response) {
const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
@ -86,7 +88,7 @@ async function getDataList(model, view: View, req) {
dbDriver: NcConnectionMgrv2.get(base)
});
const requestObj = await baseModel.defaultResolverReq(req.query);
const requestObj = await getAst({ model, query: req.query, view });
const listArgs: any = { ...req.query };
try {
@ -129,7 +131,7 @@ async function getFindOne(model, view: View, req) {
} catch (e) {}
return await nocoExecute(
await baseModel.defaultResolverReq(),
await getAst({ model, query: args, view }),
await baseModel.findOne(args),
{},
{}
@ -149,7 +151,7 @@ async function dataRead(req: Request, res: Response) {
res.json(
await nocoExecute(
await baseModel.defaultResolverReq(),
await getAst({ model, query: req.query, view }),
await baseModel.readByPk(req.params.rowId),
{},
{}
@ -162,70 +164,85 @@ const router = Router({ mergeParams: true });
// table data crud apis
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName',
apiMetrics,
ncMetaAclMw(dataList, 'dataList')
);
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/find-one',
apiMetrics,
ncMetaAclMw(dataFindOne, 'dataFindOne')
);
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/count',
apiMetrics,
ncMetaAclMw(dataCount, 'dataCount')
);
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/count',
apiMetrics,
ncMetaAclMw(dataCount, 'dataCount')
);
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId',
apiMetrics,
ncMetaAclMw(dataRead, 'dataRead')
);
router.patch(
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId',
apiMetrics,
ncMetaAclMw(dataUpdate, 'dataUpdate')
);
router.delete(
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId',
apiMetrics,
ncMetaAclMw(dataDelete, 'dataDelete')
);
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName',
apiMetrics,
ncMetaAclMw(dataList, 'dataList')
);
// table view data crud apis
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName',
apiMetrics,
ncMetaAclMw(dataList, 'dataList')
);
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/find-one',
apiMetrics,
ncMetaAclMw(dataFindOne, 'dataFindOne')
);
router.post(
'/api/v1/db/data/:orgs/:projectName/:tableName',
apiMetrics,
ncMetaAclMw(dataInsert, 'dataInsert')
);
router.post(
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName',
apiMetrics,
ncMetaAclMw(dataInsert, 'dataInsert')
);
router.patch(
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId',
apiMetrics,
ncMetaAclMw(dataUpdate, 'dataUpdate')
);
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId',
apiMetrics,
ncMetaAclMw(dataRead, 'dataRead')
);
router.delete(
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId',
apiMetrics,
ncMetaAclMw(dataDelete, 'dataDelete')
);

3
packages/nocodb/src/lib/noco/meta/api/dataApis/dataAliasExportApis.ts

@ -4,6 +4,7 @@ import {
extractCsvData,
getViewAndModelFromRequestByAliasOrId
} from './helpers';
import apiMetrics from '../../helpers/apiMetrics';
async function csvDataExport(req: Request, res: Response) {
const { view } = await getViewAndModelFromRequestByAliasOrId(req);
@ -23,10 +24,12 @@ const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/export/csv',
apiMetrics,
ncMetaAclMw(csvDataExport, 'exportCsv')
);
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/export/csv',
apiMetrics,
ncMetaAclMw(csvDataExport, 'exportCsv')
);

8
packages/nocodb/src/lib/noco/meta/api/dataApis/dataAliasNestedApis.ts

@ -6,6 +6,7 @@ import { PagedResponseImpl } from '../../helpers/PagedResponse';
import ncMetaAclMw from '../../helpers/ncMetaAclMw';
import { getViewAndModelFromRequestByAliasOrId } from './helpers';
import { NcError } from '../../helpers/catchError';
import apiMetrics from '../../helpers/apiMetrics';
export async function mmList(req: Request, res: Response, next) {
const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
@ -256,32 +257,39 @@ const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/mm/:columnName/exclude',
apiMetrics,
ncMetaAclMw(mmExcludedList, 'mmExcludedList')
);
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/hm/:columnName/exclude',
apiMetrics,
ncMetaAclMw(hmExcludedList, 'hmExcludedList')
);
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/bt/:columnName/exclude',
apiMetrics,
ncMetaAclMw(btExcludedList, 'btExcludedList')
);
router.post(
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/:relationType/:columnName/:refRowId',
apiMetrics,
ncMetaAclMw(relationDataAdd, 'relationDataAdd')
);
router.delete(
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/:relationType/:columnName/:refRowId',
apiMetrics,
ncMetaAclMw(relationDataRemove, 'relationDataRemove')
);
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/mm/:columnName',
apiMetrics,
ncMetaAclMw(mmList, 'mmList')
);
router.get(
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/hm/:columnName',
apiMetrics,
ncMetaAclMw(hmList, 'hmList')
);

77
packages/nocodb/src/lib/noco/meta/api/dataApis/dataApis.ts

@ -7,6 +7,8 @@ import { PagedResponseImpl } from '../../helpers/PagedResponse';
import View from '../../../../noco-models/View';
import ncMetaAclMw from '../../helpers/ncMetaAclMw';
import { NcError } from '../../helpers/catchError';
import apiMetrics from '../../helpers/apiMetrics';
import getAst from '../../../../dataMapper/lib/sql/helpers/getAst';
export async function dataList(req: Request, res: Response, next) {
const view = await View.get(req.params.viewId);
@ -315,24 +317,14 @@ async function dataRead(req: Request, res: Response, next) {
id: model.id,
dbDriver: NcConnectionMgrv2.get(base)
});
const key = `${model.title}Read`;
res.json(
(
await nocoExecute(
{
[key]: await baseModel.defaultResolverReq()
},
{
[key]: async id => {
return await baseModel.readByPk(id);
// return row ? new ctx.types[model.title](row) : null;
}
},
await getAst({ model, query: req.query }),
await baseModel.readByPk(req.params.rowId),
{},
{ nested: { [key]: req.params.rowId } }
{}
)
)?.[key]
);
} catch (e) {
console.log(e);
@ -450,10 +442,7 @@ async function getDataList(model, view: View, req) {
dbDriver: NcConnectionMgrv2.get(base)
});
const key = `${model._tn}List`;
const requestObj = {
[key]: await baseModel.defaultResolverReq(req.query)
};
const requestObj = await getAst({ query: req.query, model, view });
const listArgs: any = { ...req.query };
try {
@ -463,18 +452,12 @@ async function getDataList(model, view: View, req) {
listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
} catch (e) {}
const data = (
await nocoExecute(
const data = await nocoExecute(
requestObj,
{
[key]: async args => {
return await baseModel.list(args);
}
},
await baseModel.list(listArgs),
{},
{ nested: { [key]: listArgs } }
)
)?.[key];
listArgs
);
const count = await baseModel.count(listArgs);
@ -539,7 +522,7 @@ async function relationDataAdd(req, res) {
const router = Router({ mergeParams: true });
// router.get('/data/:orgs/:projectName/:tableName', ncMetaAclMw(dataListNew));
// router.get('/data/:orgs/:projectName/:tableName',apiMetrics,ncMetaAclMw(dataListNew));
// router.get(
// '/data/:orgs/:projectName/:tableName/views/:viewName',
// ncMetaAclMw(dataListNew)
@ -558,14 +541,38 @@ const router = Router({ mergeParams: true });
// ncMetaAclMw(dataDeleteNew)
// );
router.get('/data/:viewId/', ncMetaAclMw(dataList, 'dataList'));
router.post('/data/:viewId/', ncMetaAclMw(dataInsert, 'dataInsert'));
router.get('/data/:viewId/:rowId', ncMetaAclMw(dataRead, 'dataRead'));
router.patch('/data/:viewId/:rowId', ncMetaAclMw(dataUpdate, 'dataUpdate'));
router.delete('/data/:viewId/:rowId', ncMetaAclMw(dataDelete, 'dataDelete'));
router.get('/data/:viewId/', apiMetrics, ncMetaAclMw(dataList, 'dataList'));
router.post(
'/data/:viewId/',
apiMetrics,
ncMetaAclMw(dataInsert, 'dataInsert')
);
router.get(
'/data/:viewId/:rowId',
apiMetrics,
ncMetaAclMw(dataRead, 'dataRead')
);
router.patch(
'/data/:viewId/:rowId',
apiMetrics,
ncMetaAclMw(dataUpdate, 'dataUpdate')
);
router.delete(
'/data/:viewId/:rowId',
apiMetrics,
ncMetaAclMw(dataDelete, 'dataDelete')
);
router.get('/data/:viewId/:rowId/mm/:colId', ncMetaAclMw(mmList, 'mmList'));
router.get('/data/:viewId/:rowId/hm/:colId', ncMetaAclMw(hmList, 'hmList'));
router.get(
'/data/:viewId/:rowId/mm/:colId',
apiMetrics,
ncMetaAclMw(mmList, 'mmList')
);
router.get(
'/data/:viewId/:rowId/hm/:colId',
apiMetrics,
ncMetaAclMw(hmList, 'hmList')
);
router.get(
'/data/:viewId/:rowId/mm/:colId/exclude',

14
packages/nocodb/src/lib/noco/meta/api/dataApis/helpers.ts

@ -13,16 +13,13 @@ import LookupColumn from '../../../../noco-models/LookupColumn';
import LinkToAnotherRecordColumn from '../../../../noco-models/LinkToAnotherRecordColumn';
import papaparse from 'papaparse';
import getAst from '../../../../dataMapper/lib/sql/helpers/getAst';
export async function getViewAndModelFromRequestByAliasOrId(
req:
| Request<{ projectName: string; tableName: string; viewName?: string }>
| Request
) {
let project = await Project.getWithInfoByTitle(req.params.projectName);
if (!project) {
project = await Project.getWithInfo(req.params.projectName);
}
const project = await Project.getWithInfoByTitleOrId(req.params.projectName);
const model = await Model.getByAliasOrId({
project_id: project.id,
@ -75,7 +72,12 @@ export async function extractCsvData(view: View, req: Request) {
elapsed = temp[0] * 1000 + temp[1] / 1000000
) {
const rows = await nocoExecute(
await baseModel.defaultResolverReq(req.query, false, false),
await getAst({
query: req.query,
includePkByDefault: false,
model: view.model,
view
}),
await baseModel.list({ ...req.query, offset, limit }),
{},
req.query

102
packages/nocodb/src/lib/noco/meta/api/dataApis/oldDataApis.ts

@ -3,15 +3,66 @@ import Model from '../../../../noco-models/Model';
import { nocoExecute } from 'nc-help';
import Base from '../../../../noco-models/Base';
import NcConnectionMgrv2 from '../../../common/NcConnectionMgrv2';
import { PagedResponseImpl } from '../../helpers/PagedResponse';
import View from '../../../../noco-models/View';
import ncMetaAclMw from '../../helpers/ncMetaAclMw';
import Project from '../../../../noco-models/Project';
import { NcError } from '../../helpers/catchError';
import apiMetrics from '../../helpers/apiMetrics';
import getAst from '../../../../dataMapper/lib/sql/helpers/getAst';
export async function dataList(req: Request, res: Response) {
const { model, view } = await getViewAndModelFromRequest(req);
res.json(await getDataList(model, view, req));
const base = await Base.get(model.base_id);
const baseModel = await Model.getBaseModelSQL({
id: model.id,
viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base)
});
const requestObj = await getAst({
query: req.query,
model,
view
});
const listArgs: any = { ...req.query };
try {
listArgs.filterArr = JSON.parse(listArgs.filterArrJson);
} catch (e) {}
try {
listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
} catch (e) {}
const data = await nocoExecute(
requestObj,
await baseModel.list(listArgs),
{},
listArgs
);
res.json(data);
}
export async function dataCount(req: Request, res: Response) {
const { model, view } = await getViewAndModelFromRequest(req);
const base = await Base.get(model.base_id);
const baseModel = await Model.getBaseModelSQL({
id: model.id,
viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base)
});
const listArgs: any = { ...req.query };
try {
listArgs.filterArr = JSON.parse(listArgs.filterArrJson);
} catch (e) {}
const count = await baseModel.count(listArgs);
res.json({
count
});
}
async function dataInsert(req: Request, res: Response) {
@ -52,39 +103,7 @@ async function dataDelete(req: Request, res: Response) {
res.json(await baseModel.delByPk(req.params.rowId, null, req));
}
async function getDataList(model, view: View, req) {
const base = await Base.get(model.base_id);
const baseModel = await Model.getBaseModelSQL({
id: model.id,
viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base)
});
const requestObj = await baseModel.defaultResolverReq(req.query);
const listArgs: any = { ...req.query };
try {
listArgs.filterArr = JSON.parse(listArgs.filterArrJson);
} catch (e) {}
try {
listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
} catch (e) {}
const data = await nocoExecute(
requestObj,
await baseModel.list(listArgs),
{},
listArgs
);
const count = await baseModel.count(listArgs);
return new PagedResponseImpl(data, {
...req.query,
count
});
}
async function getViewAndModelFromRequest(req) {
const project = await Project.getWithInfo(req.params.projectId);
const model = await Model.getByAliasOrId({
@ -115,7 +134,11 @@ async function dataRead(req: Request, res: Response) {
res.json(
await nocoExecute(
await baseModel.defaultResolverReq(),
await getAst({
query: req.query,
model,
view
}),
await baseModel.readByPk(req.params.rowId),
{},
{}
@ -130,20 +153,29 @@ router.get(
ncMetaAclMw(dataList, 'dataList')
);
router.get(
'/nc/:projectId/api/v1/:tableName/count',
ncMetaAclMw(dataCount, 'dataCount')
);
router.post(
'/nc/:projectId/api/v1/:tableName',
apiMetrics,
ncMetaAclMw(dataInsert, 'dataInsert')
);
router.get(
'/nc/:projectId/api/v1/:tableName/:rowId',
apiMetrics,
ncMetaAclMw(dataRead, 'dataRead')
);
router.patch(
'/nc/:projectId/api/v1/:tableName/:rowId',
apiMetrics,
ncMetaAclMw(dataUpdate, 'dataUpdate')
);
router.delete(
'/nc/:projectId/api/v1/:tableName/:rowId',
apiMetrics,
ncMetaAclMw(dataDelete, 'dataDelete')
);

6
packages/nocodb/src/lib/noco/meta/api/index.ts

@ -95,7 +95,6 @@ export default function(router: Router, server) {
credentials: true
}
});
io.use(function(socket, next) {
passport.authenticate(
'jwt',
@ -110,7 +109,10 @@ export default function(router: Router, server) {
}
)(socket.handshake, {}, next);
}).on('connection', socket => {
const id = getHash(Tele.id + (socket?.handshake as any)?.user?.id);
const id = getHash(
(process.env.NC_SERVER_UUID || Tele.id) +
(socket?.handshake as any)?.user?.id
);
socket.on('page', args => {
Tele.page({ ...args, id });

30
packages/nocodb/src/lib/noco/meta/api/publicApis/publicDataApis.ts

@ -16,6 +16,7 @@ import { nanoid } from 'nanoid';
import { mimeIcons } from '../../../../utils/mimeTypes';
import slash from 'slash';
import { sanitizeUrlPath } from '../attachmentApis';
import getAst from '../../../../dataMapper/lib/sql/helpers/getAst';
export async function dataList(req: Request, res: Response) {
try {
@ -49,7 +50,11 @@ export async function dataList(req: Request, res: Response) {
} catch (e) {}
const data = await nocoExecute(
await baseModel.defaultResolverReq(req.query),
await getAst({
query: req.query,
model,
view
}),
await baseModel.list(listArgs),
{},
listArgs
@ -186,23 +191,18 @@ async function relDataList(req, res) {
dbDriver: NcConnectionMgrv2.get(base)
});
const key = `${model.title}List`;
const requestObj = {
[key]: await baseModel.defaultResolverReq(req.query, true)
};
const requestObj = await getAst({
query: req.query,
model,
extractOnlyPrimaries: true
});
const data = (
await nocoExecute(
const data = await nocoExecute(
requestObj,
{
[key]: async args => {
return await baseModel.list(args);
}
},
await baseModel.list(req.query),
{},
{ nested: { [key]: req.query } }
)
)?.[key];
req.query
);
const count = await baseModel.count(req.query);

29
packages/nocodb/src/lib/noco/meta/api/publicApis/publicDataExportApis.ts

@ -10,6 +10,7 @@ import Column from '../../../../noco-models/Column';
import LinkToAnotherRecordColumn from '../../../../noco-models/LinkToAnotherRecordColumn';
import LookupColumn from '../../../../noco-models/LookupColumn';
import catchError, { NcError } from '../../helpers/catchError';
import getAst from '../../../../dataMapper/lib/sql/helpers/getAst';
async function exportCsv(req: Request, res: Response) {
const view = await View.getByUUID(req.params.publicDataUuid);
@ -50,10 +51,12 @@ async function exportCsv(req: Request, res: Response) {
dbDriver: NcConnectionMgrv2.get(base)
});
const key = `${model.title}List`;
const requestObj = {
[key]: await baseModel.defaultResolverReq(req.query, false, false)
};
const requestObj = await getAst({
query: req.query,
model,
view,
includePkByDefault: false
});
let offset = +req.query.offset || 0;
const limit = 100;
@ -70,22 +73,12 @@ async function exportCsv(req: Request, res: Response) {
temp = process.hrtime(startTime),
elapsed = temp[0] * 1000 + temp[1] / 1000000
) {
const rows = (
await nocoExecute(
const rows = await nocoExecute(
requestObj,
{
[key]: async args => {
return await baseModel.list({ ...args, offset, limit });
}
},
await baseModel.list({ ...listArgs, offset, limit }),
{},
{
nested: {
[key]: listArgs
}
}
)
)?.[key];
listArgs
);
if (!rows?.length) {
offset = -1;

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

@ -26,10 +26,12 @@ export default async (
const colOpt = await c.getColOptions<LinkToAnotherRecordColumn>(
ncMeta
);
if (colOpt) {
const relTable = await colOpt.getRelatedTable(ncMeta);
field.type = undefined;
field.$ref = `#/components/schemas/${relTable.title}Request`;
}
}
break;
case UITypes.Formula:
case UITypes.Lookup:

2
packages/nocodb/src/lib/noco/meta/api/swagger/helpers/templates/params.ts

@ -184,6 +184,8 @@ export const getNestedParams = async (
nestedFieldParam(column.title),
nestedSortParam(column.title)
];
} else {
return [...(await paramsArr), nestedFieldParam(column.title)];
}
}

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

@ -13,7 +13,7 @@ async function swaggerJson(req, res) {
if (!project) NcError.notFound();
const models = await Model.list({
project_id: req.params.project_id,
project_id: req.params.projectId,
base_id: null
});

2
packages/nocodb/src/lib/noco/meta/api/swagger/swaggerHtml.ts

File diff suppressed because one or more lines are too long

35
packages/nocodb/src/lib/noco/meta/api/tableApis.ts

@ -5,10 +5,12 @@ import { Tele } from 'nc-help';
import {
AuditOperationSubTypes,
AuditOperationTypes,
isVirtualCol,
ModelTypes,
TableListType,
TableReqType,
TableType
TableType,
UITypes
} from 'nocodb-sdk';
import ProjectMgrv2 from '../../../sqlMgr/v2/ProjectMgrv2';
import Project from '../../../noco-models/Project';
@ -23,6 +25,7 @@ import getTableNameAlias, { getColumnNameAlias } from '../helpers/getTableName';
import Column from '../../../noco-models/Column';
import NcConnectionMgrv2 from '../../common/NcConnectionMgrv2';
import getColumnUiType from '../helpers/getColumnUiType';
import LinkToAnotherRecordColumn from '../../../noco-models/LinkToAnotherRecordColumn';
export async function tableGet(req: Request, res: Response<TableType>) {
const table = await Model.getWithInfo({
id: req.params.tableId
@ -157,7 +160,7 @@ export async function tableCreate(req: Request<any, any, TableReqType>, res) {
ip: (req as any).clientIp
}).then(() => {});
mapDefaultPrimaryValue(req.body.columns);
mapDefaultPrimaryValue(columns);
Tele.emit('evt', { evt_type: 'table:created' });
@ -199,15 +202,35 @@ export async function tableUpdate(req: Request<any, any>, res) {
res.json({ msg: 'success' });
}
export async function tableDelete(req: Request, res: Response, next) {
try {
export async function tableDelete(req: Request, res: Response) {
const table = await Model.getByIdOrName({ id: req.params.tableId });
await table.getColumns();
const relationColumns = table.columns.filter(
c => c.uidt === UITypes.LinkToAnotherRecord
);
if (relationColumns?.length) {
const referredTables = await Promise.all(
relationColumns.map(async c =>
c
.getColOptions<LinkToAnotherRecordColumn>()
.then(opt => opt.getRelatedTable())
.then()
)
);
NcError.badRequest(
`Table can't be deleted since Table is being referred in following tables : ${referredTables.join(
', '
)}. Delete LinkToAnotherRecord columns and try again.`
);
}
const project = await Project.getWithInfo(table.project_id);
const base = project.bases.find(b => b.id === table.base_id);
const sqlMgr = await ProjectMgrv2.getSqlMgr(project);
(table as any).tn = table.table_name;
table.columns = table.columns.filter(c => !isVirtualCol(c));
table.columns.forEach(c => {
(c as any).cn = c.column_name;
});
@ -233,10 +256,6 @@ export async function tableDelete(req: Request, res: Response, next) {
Tele.emit('evt', { evt_type: 'table:deleted' });
res.json(await table.delete());
} catch (e) {
console.log(e);
next(e);
}
}
const router = Router({ mergeParams: true });

2
packages/nocodb/src/lib/noco/meta/helpers/NcPluginMgrv2.ts

@ -123,7 +123,7 @@ class NcPluginMgrv2 {
}
await plugin.init(pluginData?.input);
return plugin.adapter as IStorageAdapter;
return plugin.getAdapter();
}
public static async emailAdapter(

19
packages/nocodb/src/lib/noco/meta/helpers/apiMetrics.ts

@ -0,0 +1,19 @@
import { Request } from 'express';
import { Tele } from 'nc-help';
const countMap = {};
const metrics = async (req: Request) => {
if (!req?.route?.path) return;
const event = `a:api:${req.route.path}:${req.method}`;
countMap[event] = (countMap[event] || 0) + 1;
if (countMap[event] >= 50) {
Tele.event({ event });
countMap[event] = 0;
}
};
export default async (req: Request, _res, next) => {
metrics(req).then(() => {});
next();
};

2
packages/nocodb/src/lib/noco/meta/helpers/extractProjectIdAndAuthenticate.ts

@ -18,7 +18,7 @@ export default async (req, res, next) => {
// extract project id based on request path params
if (params.projectName) {
const project = await Project.getByTitle(params.projectName);
const project = await Project.getByTitleOrId(params.projectName);
req.ncProjectId = project.id;
res.locals.project = project;
}

2
packages/nocodb/src/lib/noco/rest/ui/auth/swagger.ts

File diff suppressed because one or more lines are too long

32
packages/nocodb/src/lib/noco/upgrader/jobs/ncProjectUpgraderV2_0090000.ts

@ -1070,17 +1070,30 @@ async function migrateUIAcl(ctx: MigrateCtxV1, ncMeta: any) {
}> = await ncMeta.metaList(null, null, 'nc_disabled_models_for_role');
for (const acl of uiAclList) {
// if missing model name skip the view acl migration
if (!acl.title) continue;
let fk_view_id;
if (acl.type === 'vtable') {
// if missing parent model name skip the view acl migration
if (!acl.parent_model_title) continue;
fk_view_id =
ctx.objViewRef[acl.project_id][acl.parent_model_title][acl.title].id;
ctx.objViewRef[acl.project_id]?.[
(
ctx.objModelRef?.[acl.project_id]?.[acl.parent_model_title] ||
ctx.objModelAliasRef?.[acl.project_id]?.[acl.parent_model_title]
)?.table_name
]?.[acl.title]?.id;
} else {
fk_view_id =
ctx.objViewRef[acl.project_id][acl.title][
ctx.objModelRef[acl.project_id][acl.title].title
].id || ctx.objViewRef[acl.project_id][acl.title][acl.title].id;
ctx.objViewRef?.[acl.project_id]?.[acl.title]?.[
ctx.objModelRef?.[acl.project_id]?.[acl.title]?.title
].id || ctx.objViewRef[acl.project_id]?.[acl.title]?.[acl.title]?.id;
}
// if view id missing skip ui acl view migration
if (!fk_view_id) continue;
await ModelRoleVisibility.insert(
{
role: acl.role,
@ -1112,9 +1125,14 @@ async function migrateSharedViews(ctx: MigrateCtxV1, ncMeta: any) {
if (sharedView.view_type !== 'table' && sharedView.view_type !== 'view') {
fk_view_id =
ctx.objViewRef[sharedView.project_id]?.[sharedView.model_name]?.[
sharedView.view_name
]?.id;
ctx.objViewRef[sharedView.project_id]?.[
(
ctx.objModelRef?.[sharedView.project_id]?.[sharedView.model_name] ||
ctx.objModelAliasRef?.[sharedView.project_id]?.[
sharedView.model_name
]
)?.title
]?.[sharedView.view_name]?.id;
} else {
fk_view_id =
ctx.objViewRef[sharedView.project_id]?.[sharedView.model_name]?.[

2
packages/nocodb/src/lib/utils/NcHelp.ts

@ -5,7 +5,7 @@ export default class NcHelp {
fns: Array<() => Promise<any>>,
dbType: string
): Promise<any> {
if (dbType === 'oracledb') {
if (dbType === 'oracledb' || dbType === 'mssql') {
for (const fn of fns) {
await fn();
}

4
scripts/cypress/cypress.json

@ -28,7 +28,7 @@
"viewportHeight": 1000,
"video": false,
"retries": 0,
"screenshotOnRunFailure": true,
"screenshotOnRunFailure": false,
"numTestsKeptInMemory": 0,
"env": {
"testMode": [
@ -43,7 +43,7 @@
"user": "root",
"password": "password"
},
"screenshot": true
"screenshot": false
},
"fixturesFolder": "scripts/cypress/fixtures",
"integrationFolder": "scripts/cypress/integration",

4
scripts/cypress/integration/spec/roleValidation.spec.js

@ -214,7 +214,7 @@ export function _editComment(roleType, previewMode) {
// Rest: can create/edit
export function _viewMenu(roleType, previewMode) {
let columnName = "City";
let navDrawListCnt = 1;
let navDrawListCnt = 2;
// Download CSV
let actionsMenuItemsCnt = 1;
@ -231,7 +231,7 @@ export function _viewMenu(roleType, previewMode) {
// Owner, Creator will have two navigation drawer (on each side of center panel)
if (roleType == "owner" || roleType == "creator") {
navDrawListCnt = 2;
navDrawListCnt = 3;
// Download CSV / Upload CSV / Shared View List / Webhook
actionsMenuItemsCnt = 4;
} else if (roleType == "editor") {

Loading…
Cancel
Save