Browse Source

Merge pull request #1950 from nocodb/feat/code-snippet

Feat/code snippet
pull/1966/head
աɨռɢӄաօռɢ 3 years ago committed by GitHub
parent
commit
0f39a2167d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 128
      packages/nc-gui/components/monaco/CustomMonacoEditor.js
  2. 272
      packages/nc-gui/components/project/spreadsheet/components/codeSnippet.vue
  3. 327
      packages/nc-gui/components/project/spreadsheet/components/spreadsheetNavDrawer.vue
  4. 742
      packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue
  5. 17562
      packages/nc-gui/package-lock.json
  6. 2
      packages/nc-gui/package.json
  7. 4
      packages/nocodb/src/lib/noco/meta/api/apiTokenApis.ts
  8. 5
      packages/nocodb/src/lib/noco/meta/api/columnApis.ts
  9. 8
      packages/nocodb/src/lib/noco/meta/api/filterApis.ts
  10. 5
      packages/nocodb/src/lib/noco/meta/api/formViewApis.ts
  11. 2
      packages/nocodb/src/lib/noco/meta/api/formViewColumnApis.ts
  12. 4
      packages/nocodb/src/lib/noco/meta/api/galleryViewApis.ts
  13. 2
      packages/nocodb/src/lib/noco/meta/api/gridViewApis.ts
  14. 3
      packages/nocodb/src/lib/noco/meta/api/gridViewColumnApis.ts
  15. 7
      packages/nocodb/src/lib/noco/meta/api/hookApis.ts
  16. 12
      packages/nocodb/src/lib/noco/meta/api/hookFilterApis.ts
  17. 3
      packages/nocodb/src/lib/noco/meta/api/metaDiffApis.ts
  18. 3
      packages/nocodb/src/lib/noco/meta/api/modelVisibilityApis.ts
  19. 11
      packages/nocodb/src/lib/noco/meta/api/pluginApis.ts
  20. 6
      packages/nocodb/src/lib/noco/meta/api/projectApis.ts
  21. 6
      packages/nocodb/src/lib/noco/meta/api/projectUserApis.ts
  22. 11
      packages/nocodb/src/lib/noco/meta/api/sortApis.ts
  23. 7
      packages/nocodb/src/lib/noco/meta/api/tableApis.ts
  24. 9
      packages/nocodb/src/lib/noco/meta/api/viewApis.ts
  25. 4
      packages/nocodb/src/lib/noco/meta/api/viewColumnApis.ts
  26. 12
      packages/nocodb/src/lib/noco/meta/helpers/apiMetrics.ts
  27. 2
      scripts/cypress/integration/common/1a_table_operations.js
  28. 42
      scripts/cypress/support/commands.js

128
packages/nc-gui/components/monaco/CustomMonacoEditor.js

@ -0,0 +1,128 @@
/* eslint-disable */
// import assign from "nano-assign";
// import sqlAutoCompletions from "./sqlAutoCompletions";
// import {ext} from "vee-validate/dist/rules.esm";
export default {
name: "CustomMonacoEditor",
props: {
value: {
default: "",
type: String
},
theme: {
type: String,
default: "vs-dark"
},
lang: {type:String, default: 'typescript'},
readOnly:Boolean,
minimap:Boolean,
},
model: {
event: "change"
},
watch: {
value(newVal) {
if (newVal !== this.editor.getValue()) {
if (typeof newVal === 'object') {
this.editor.setValue(JSON.stringify(newVal, 0, 2));
} else {
this.editor.setValue(newVal);
}
}
}
},
mounted() {
this.$nextTick(() => {
if (this.amdRequire) {
this.amdRequire(["vs/editor/editor.main"], () => {
this.initMonaco(window.monaco);
});
} else {
// ESM format so it can't be resolved by commonjs `require` in eslint
// eslint-disable import/no-unresolved
const monaco = require("monaco-editor");
// monaco.editor.defineTheme('monokai', require('./Cobalt.json'))
// monaco.editor.setTheme('monokai')
this.monaco = monaco;
this.initMonaco(monaco);
}
});
},
unmounted() {
},
beforeDestroy() {
this.editor && this.editor.dispose();
},
methods: {
resizeLayout() {
this.editor.layout();
},
initMonaco(monaco) {
const code = this.value;
const model = monaco.editor.createModel(code, this.lang ||"json");
this.editor = monaco.editor.create(this.$el, {
model: model,
theme: this.theme, minimap: {
enabled: this.minimap
}
});
this.editor.onDidChangeModelContent(event => {
const value = this.editor.getValue();
if (this.value !== value) {
this.$emit("change", value, event);
}
});
this.editor.updateOptions({ readOnly: this.readOnly })
},
getMonaco() {
return this.editor;
},
getMonacoModule() {
return this.monaco;
},
},
render(h) {
return h("div");
},
created() {
},
destroyed() {
}
};
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

272
packages/nc-gui/components/project/spreadsheet/components/codeSnippet.vue

@ -0,0 +1,272 @@
<template>
<div class="nc-container" :class="{active:modal}" @click="modal=false">
<div class="nc-snippet elevation-3 pa-4" @click.stop>
<h3 class="font-weight-medium mb-4">
Code Snippet
</h3>
<v-icon class="nc-snippet-close" @click="modal=false">
mdi-close
</v-icon>
<div v-if="modal">
<v-tabs v-model="tab" height="30" show-arrows @change="client=null">
<v-tab
v-for="{lang} in langs"
:key="lang"
v-t="['c:snippet:tab', {lang}]"
class="caption"
>
{{ lang }}
</v-tab>
</v-tabs>
<div class="nc-snippet-wrapper mt-4">
<div class="nc-snippet-actions d-flex">
<v-btn
v-t="['c:snippet:copy', {client: langs[tab].clients && (client || langs[tab].clients[0]), lang: langs[tab ||0].lang}]"
color="primary"
class="rounded caption "
@click="copyToClipboard"
>
<v-icon small>
mdi-clipboard-outline
</v-icon>
Copy To Clipboard
</v-btn>
<div
v-if="langs[tab].clients"
class=" ml-2 d-flex align-center"
>
<v-menu bottom offset-y>
<template #activator="{on}">
<v-btn class="caption text-uppercase" color="primary" v-on="on">
{{ client || langs[tab].clients[0] }}
<v-icon small>
mdi-chevron-down
</v-icon>
</v-btn>
</template>
<v-list dense>
<v-list-item
v-for="c in langs[tab].clients"
:key="c"
dense
@click="client = c"
>
<v-list-item-title class="text-uppercase">
{{ c }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
</div>
<custom-monaco-editor
hide-line-num
:theme="$store.state.windows.darkTheme ? 'vs-dark' : 'vs-light'"
style="min-height:500px;max-width: 100%"
:value="code"
read-only
/>
</div>
</div>
</div>
</div>
</template>
<script>
import HTTPSnippet from 'httpsnippet'
import CustomMonacoEditor from '~/components/monaco/CustomMonacoEditor'
import { copyTextToClipboard } from '~/helpers/xutils'
export default {
name: 'CodeSnippet',
components: { CustomMonacoEditor },
props: {
meta: Object,
view: Object,
filters: [Object, Array],
sorts: [Object, Array],
fileds: [Object, Array],
queryParams: Object,
value: Boolean
},
data: () => ({
tab: 0,
client: null,
langs: [
{
lang: 'shell',
clients: ['curl', 'wget']
},
{
lang: 'javascript',
clients: ['axios', 'fetch', 'jquery', 'xhr']
},
{
lang: 'node',
clients: ['axios', 'fetch', 'request', 'native', 'unirest']
},
{
lang: 'nocodb-sdk',
clients: ['javascript', 'node']
},
{
lang: 'php'
},
{
lang: 'python',
clients: ['python3',
'requests']
},
{
lang: 'ruby'
},
{
lang: 'java'
},
{
lang: 'c'
}
]
}),
computed: {
modal: {
get() {
return this.value
},
set(v) {
this.$emit('input', v)
}
},
apiUrl() {
return new URL(`/api/v1/db/data/noco/${this.projectId}/${this.meta.title}/views/${this.view.title}`, (this.$store.state.project.projectInfo && this.$store.state.project.projectInfo.ncSiteUrl) || '/').href
},
snippet() {
return new HTTPSnippet({
method: 'GET',
headers: [
{ name: 'xc-auth', value: this.$store.state.users.token, comment: 'JWT Auth token' }
],
url: this.apiUrl,
queryString: Object.entries(this.queryParams || {}).map(([name, value]) => {
return {
name, value: String(value)
}
})
})
},
code() {
if (this.langs[this.tab].lang === 'nocodb-sdk') {
return `${
this.client === 'node'
? 'const { Api } require("nocodb-sdk");'
: 'import { Api } from "nocodb-sdk";'
}
const api = new Api({
baseURL: ${JSON.stringify(this.apiUrl)},
headers: {
"xc-auth": ${JSON.stringify(this.$store.state.users.token)}
}
})
api.dbViewRow.list(
"noco",
${JSON.stringify(this.projectName)},
${JSON.stringify(this.meta.title)},
${JSON.stringify(this.view.title)}, ${JSON.stringify(this.queryParams, null, 4)}).then(function (data) {
console.log(data);
}).catch(function (error) {
console.error(error);
});`
}
return this.snippet.convert(this.langs[this.tab].lang, this.client || (this.langs[this.tab].clients && this.langs[this.tab].clients[0]), {})
}
},
mounted() {
(document.querySelector('[data-app]') || this.$root.$el).append(this.$el)
},
destroyed() {
this.$el.parentNode && this.$el.parentNode.removeChild(this.$el)
},
methods: {
copyToClipboard() {
copyTextToClipboard(this.code)
this.$toast.success('Code copied to clipboard successfully.').goAway(3000)
}
}
}
</script>
<style scoped lang="scss">
.nc-snippet-wrapper {
position: relative;
border: 1px solid #7773;
border-radius: 4px;
overflow: hidden;
}
.nc-snippet-actions {
position: absolute;
right: 10px;
bottom: 10px;
z-index: 99999;
}
.nc-container {
position: fixed;
pointer-events: none;
width: 100vw;
height: 100vh;
z-index: 9999;
right: 0;
top: 0;
.nc-snippet {
background-color: var(--v-backgroundColorDefault-base);
height: 100%;
width: max(50%, 700px);
position: absolute;
bottom: 0;
top: 0;
right: min(-50%, -700px);
transition: .3s right;
}
&.active {
pointer-events: all;
& > .nc-snippet {
right: 0
}
}
.nc-snippet-close {
position: absolute;
right: 16px;
top: 16px;
}
}
::v-deep {
.v-tabs {
height: 100%;
.v-tabs-items {
height: calc(100% - 30px);
.v-window__container {
height: 100%;
}
}
}
.v-slide-group__prev--disabled {
display: none
}
}
</style>

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

@ -61,7 +61,9 @@
>
{{ viewIcons[view.type].icon }}
</v-icon>
<v-icon v-else color="primary" small> mdi-table </v-icon>
<v-icon v-else color="primary" small>
mdi-table
</v-icon>
</v-list-item-icon>
<v-list-item-title>
<v-tooltip bottom>
@ -77,7 +79,7 @@
@click.stop
@keydown.enter.stop="updateViewName(view, i)"
@blur="updateViewName(view, i)"
/>
>
<template v-else>
<span v-on="on">{{
view.alias || view.title
@ -183,7 +185,9 @@
@click="openCreateViewDlg(viewTypes.GRID)"
>
<v-list-item-icon class="mr-n1">
<v-icon color="blue" x-small> mdi-grid-large </v-icon>
<v-icon color="blue" x-small>
mdi-grid-large
</v-icon>
</v-list-item-icon>
<v-list-item-title>
<span class="font-weight-regular">
@ -192,7 +196,9 @@
</span>
</v-list-item-title>
<v-spacer />
<v-icon class="mr-1" small> mdi-plus </v-icon>
<v-icon class="mr-1" small>
mdi-plus
</v-icon>
</v-list-item>
</template>
<!-- Add Grid View -->
@ -207,7 +213,9 @@
@click="openCreateViewDlg(viewTypes.GALLERY)"
>
<v-list-item-icon class="mr-n1">
<v-icon color="orange" x-small> mdi-camera-image </v-icon>
<v-icon color="orange" x-small>
mdi-camera-image
</v-icon>
</v-list-item-icon>
<v-list-item-title>
<span class="font-weight-regular">
@ -217,7 +225,9 @@
</v-list-item-title>
<v-spacer />
<v-icon class="mr-1" small> mdi-plus </v-icon>
<v-icon class="mr-1" small>
mdi-plus
</v-icon>
</v-list-item>
</template>
<!-- Add Gallery View -->
@ -251,7 +261,9 @@
</v-list-item-title>
<v-spacer />
<v-icon class="mr-1" small> mdi-plus </v-icon>
<v-icon class="mr-1" small>
mdi-plus
</v-icon>
</v-list-item>
</template>
<!-- Add Form View -->
@ -261,6 +273,20 @@
</template>
</div>
<div>
<v-btn
v-t="['c:snippet:open']"
color="primary"
class="caption d-100"
@click="codeSnippetModal=true"
>
<v-icon small class="mr-2">
mdi-xml
</v-icon> Get API Snippet
</v-btn>
<code-snippet v-model="codeSnippetModal" :query-params="queryParams" :meta="meta" :view="selectedView" />
</div>
<div
v-if="time - $store.state.windows.miniSponsorCard > 15 * 60 * 1000"
class="pa-2 sponsor-wrapper"
@ -490,17 +516,18 @@
</template>
<script>
import draggable from "vuedraggable";
import { ViewTypes } from "nocodb-sdk";
import CreateViewDialog from "@/components/project/spreadsheet/dialog/createViewDialog";
import Extras from "~/components/project/spreadsheet/components/extras";
import viewIcons from "~/helpers/viewIcons";
import { copyTextToClipboard } from "~/helpers/xutils";
import SponsorMini from "~/components/sponsorMini";
import draggable from 'vuedraggable'
import { ViewTypes } from 'nocodb-sdk'
import CreateViewDialog from '@/components/project/spreadsheet/dialog/createViewDialog'
import Extras from '~/components/project/spreadsheet/components/extras'
import viewIcons from '~/helpers/viewIcons'
import { copyTextToClipboard } from '~/helpers/xutils'
import SponsorMini from '~/components/sponsorMini'
import CodeSnippet from '~/components/project/spreadsheet/components/codeSnippet'
export default {
name: "SpreadsheetNavDrawer",
components: { SponsorMini, Extras, CreateViewDialog, draggable },
name: 'SpreadsheetNavDrawer',
components: { CodeSnippet, SponsorMini, Extras, CreateViewDialog, draggable },
props: {
extraViewParams: Object,
showAdvanceOptions: Boolean,
@ -509,7 +536,7 @@ export default {
primaryValueColumn: [Number, String],
toggleDrawer: {
type: Boolean,
default: false,
default: false
},
nodes: Object,
table: String,
@ -523,7 +550,7 @@ export default {
sortList: [Object, Array],
load: {
default: true,
type: Boolean,
type: Boolean
},
currentApiUrl: String,
fieldsOrder: Array,
@ -533,22 +560,24 @@ export default {
groupingField: String,
// showSystemFields: Boolean,
views: Array,
queryParams: Object
},
data: () => ({
codeSnippetModal: false,
drag: false,
dragOptions: {
animation: 200,
group: "description",
group: 'description',
disabled: false,
ghostClass: "ghost",
ghostClass: 'ghost'
},
time: Date.now(),
sponsorMiniVisible: true,
enableDummyFeat: false,
searchQueryVal: "",
searchQueryVal: '',
showShareLinkPassword: false,
passwordProtect: false,
sharedViewPassword: "",
sharedViewPassword: '',
overAdvShieldIcon: false,
overShieldIcon: false,
viewIcons,
@ -558,120 +587,120 @@ export default {
showCreateView: false,
loading: false,
viewTypeAlias: {
[ViewTypes.GRID]: "grid",
[ViewTypes.FORM]: "form",
[ViewTypes.GALLERY]: "gallery",
},
[ViewTypes.GRID]: 'grid',
[ViewTypes.FORM]: 'form',
[ViewTypes.GALLERY]: 'gallery'
}
}),
computed: {
viewsList: {
set(v) {
this.$emit("update:views", v);
this.$emit('update:views', v)
},
get() {
return this.views;
},
return this.views
}
},
viewTypes() {
return ViewTypes;
return ViewTypes
},
newViewParams() {
if (!this.showFields) {
return {};
return {}
}
const showFields = { ...this.showFields };
const showFields = { ...this.showFields }
Object.keys(showFields).forEach((k) => {
showFields[k] = true;
});
return { showFields };
showFields[k] = true
})
return { showFields }
},
selectedViewIdLocal: {
set(val) {
const view = (this.views || []).find((v) => v.id === val);
const view = (this.views || []).find(v => v.id === val)
this.$router.push({
query: {
...this.$route.query,
view: view && view.id,
},
});
view: view && view.id
}
})
},
get() {
let id;
let id
if (this.views) {
const view = this.views.find((v) => v.id === this.$route.query.view);
id = (view && view.id) || ((this.views && this.views[0]) || {}).id;
const view = this.views.find(v => v.id === this.$route.query.view)
id = (view && view.id) || ((this.views && this.views[0]) || {}).id
}
return id
}
return id;
},
},
sharedViewUrl() {
let viewType;
let viewType
switch (this.shareLink.type) {
case this.viewTypes.FORM:
viewType = "form";
break;
viewType = 'form'
break
case this.viewTypes.KANBAN:
viewType = "kanban";
break;
viewType = 'kanban'
break
default:
viewType = "view";
viewType = 'view'
}
return `${this.dashboardUrl}#/nc/${viewType}/${this.shareLink.uuid}`
}
return `${this.dashboardUrl}#/nc/${viewType}/${this.shareLink.uuid}`;
},
},
watch: {
async load(v) {
if (v) {
await this.loadViews();
this.onViewIdChange(this.selectedViewIdLocal);
await this.loadViews()
this.onViewIdChange(this.selectedViewIdLocal)
}
},
selectedViewIdLocal(id) {
this.onViewIdChange(id);
},
this.onViewIdChange(id)
}
},
async created() {
if (this.load) {
await this.loadViews();
await this.loadViews()
}
this.onViewIdChange(this.selectedViewIdLocal);
this.onViewIdChange(this.selectedViewIdLocal)
},
methods: {
async onMove(event) {
if (this.viewsList.length - 1 === event.moved.newIndex) {
this.$set(
this.viewsList[event.moved.newIndex],
"order",
'order',
this.viewsList[event.moved.newIndex - 1].order + 1
);
)
} else if (event.moved.newIndex === 0) {
this.$set(
this.viewsList[event.moved.newIndex],
"order",
'order',
this.viewsList[1].order / 2
);
)
} else {
this.$set(
this.viewsList[event.moved.newIndex],
"order",
'order',
(this.viewsList[event.moved.newIndex - 1].order +
this.viewsList[event.moved.newIndex + 1].order) /
2
);
)
}
await this.$api.dbView.update(this.viewsList[event.moved.newIndex].id, {
title: this.viewsList[event.moved.newIndex].title,
order: this.viewsList[event.moved.newIndex].order,
});
order: this.viewsList[event.moved.newIndex].order
})
this.$e("a:view:reorder");
this.$e('a:view:reorder')
},
onViewIdChange(id) {
const selectedView = this.views && this.views.find((v) => v.id === id);
const selectedView = this.views && this.views.find(v => v.id === id)
// const queryParams = {}
this.$emit("update:selectedViewId", id);
this.$emit("update:selectedView", selectedView);
this.$emit('update:selectedViewId', id)
this.$emit('update:selectedView', selectedView)
// if (selectedView.type === 'table') {
// return;
// }
@ -694,52 +723,52 @@ export default {
// } else {
// this.$emit('mapFieldsAndShowFields')
// }
this.$emit("loadTableData");
this.$emit('loadTableData')
},
hideMiniSponsorCard() {
this.$store.commit("windows/MutMiniSponsorCard", Date.now());
this.$store.commit('windows/MutMiniSponsorCard', Date.now())
},
openCreateViewDlg(type) {
const mainView = this.viewsList.find(
(v) => v.type === "table" || v.type === "view"
);
v => v.type === 'table' || v.type === 'view'
)
try {
this.copyViewRef = this.copyViewRef || {
query_params: JSON.stringify({
...this.newViewParams,
fieldsOrder: JSON.parse(mainView.query_params).fieldsOrder,
}),
};
fieldsOrder: JSON.parse(mainView.query_params).fieldsOrder
})
}
} catch {}
this.createViewType = type;
this.showCreateView = true;
this.$e("c:view:create", { view: type });
this.createViewType = type
this.showCreateView = true
this.$e('c:view:create', { view: type })
},
isCentrallyAligned(col) {
return ![
"SingleLineText",
"LongText",
"Attachment",
"Date",
"Time",
"Email",
"URL",
"DateTime",
"CreateTime",
"LastModifiedTime",
].includes(col.uidt);
'SingleLineText',
'LongText',
'Attachment',
'Date',
'Time',
'Email',
'URL',
'DateTime',
'CreateTime',
'LastModifiedTime'
].includes(col.uidt)
},
onPasswordProtectChange() {
if (!this.passwordProtect) {
this.shareLink.password = null;
this.saveShareLinkPassword();
this.shareLink.password = null
this.saveShareLinkPassword()
}
},
async saveShareLinkPassword() {
try {
await this.$api.dbViewShare.update(this.shareLink.id, {
password: this.shareLink.password,
});
password: this.shareLink.password
})
// await this.$store.dispatch('sqlMgr/ActSqlOp', [
// { dbAlias: this.nodes.dbAlias },
@ -749,14 +778,14 @@ export default {
// password: this.shareLink.password
// }
// ])
this.$toast.success("Successfully updated").goAway(3000);
this.$toast.success('Successfully updated').goAway(3000)
} catch (e) {
this.$toast
.error(await this._extractSdkResponseErrorMsg(e))
.goAway(3000);
.goAway(3000)
}
this.$e("a:view:share:enable-pwd");
this.$e('a:view:share:enable-pwd')
},
async loadViews() {
// this.viewsList = await this.sqlOp(
@ -772,8 +801,8 @@ export default {
// this.viewsList = []
const views = (await this.$api.dbView.list(this.meta.id)).list;
this.$emit("update:views", views);
const views = (await this.$api.dbView.list(this.meta.id)).list
this.$emit('update:views', views)
},
// async onViewChange() {
// let query_params = {}
@ -793,27 +822,27 @@ export default {
// this.$emit('loadTableData');
// },
copyapiUrlToClipboard() {
copyTextToClipboard(this.currentApiUrl);
this.clipboardSuccessHandler();
copyTextToClipboard(this.currentApiUrl)
this.clipboardSuccessHandler()
},
async updateViewName(view, index) {
if (!view.edit) {
return;
return
}
// const oldTitle = view.title
this.$set(view, "edit", false);
this.$set(view, 'edit', false)
if (view.title_temp === view.title) {
return;
return
}
if (
this.viewsList.some(
(v, i) => i !== index && (v.alias || v.title) === view.title_temp
)
) {
this.$toast.info("View name should be unique").goAway(3000);
return;
this.$toast.info('View name should be unique').goAway(3000)
return
}
try {
// if (this.selectedViewIdLocal === view.id) {
@ -824,39 +853,39 @@ export default {
// }
// })
// }
this.$set(view, "title", view.title_temp);
this.$set(view, 'title', view.title_temp)
await this.$api.dbView.update(view.id, {
title: view.title,
order: view.order,
});
this.$toast.success("View renamed successfully").goAway(3000);
order: view.order
})
this.$toast.success('View renamed successfully').goAway(3000)
} catch (e) {
this.$toast
.error(await this._extractSdkResponseErrorMsg(e))
.goAway(3000);
.goAway(3000)
}
},
showRenameTextBox(view, i) {
this.$set(view, "edit", true);
this.$set(view, "title_temp", view.title);
this.$set(view, 'edit', true)
this.$set(view, 'title_temp', view.title)
this.$nextTick(() => {
const input = this.$refs[`input${i}`][0];
input.focus();
input.setSelectionRange(0, input.value.length);
});
this.$e("c:view:rename", { view: view.type });
const input = this.$refs[`input${i}`][0]
input.focus()
input.setSelectionRange(0, input.value.length)
})
this.$e('c:view:rename', { view: view.type })
},
async deleteView(view) {
try {
await this.$api.dbView.delete(view.id);
this.$toast.success("View deleted successfully").goAway(3000);
await this.loadViews();
await this.$api.dbView.delete(view.id)
this.$toast.success('View deleted successfully').goAway(3000)
await this.loadViews()
} catch (e) {
this.$toast
.error(await this._extractSdkResponseErrorMsg(e))
.goAway(3000);
.goAway(3000)
}
this.$e("a:view:delete", { view: view.type });
this.$e('a:view:delete', { view: view.type })
},
async genShareLink() {
// const sharedViewUrl = await this.$store.dispatch('sqlMgr/ActSqlOp', [
@ -883,44 +912,44 @@ export default {
// password: this.sharedViewPassword
// }
// ])
const shared = await this.$api.dbViewShare.create(this.selectedViewId);
const shared = await this.$api.dbViewShare.create(this.selectedViewId)
// todo: url
this.shareLink = shared;
this.showShareModel = true;
this.shareLink = shared
this.showShareModel = true
},
copyView(view, i) {
this.createViewType = view.type;
this.showCreateView = true;
this.copyViewRef = view;
this.$e("c:view:copy", { view: view.type });
this.createViewType = view.type
this.showCreateView = true
this.copyViewRef = view
this.$e('c:view:copy', { view: view.type })
},
async onViewCreate(viewMeta) {
this.copyViewRef = null;
await this.loadViews();
this.selectedViewIdLocal = viewMeta.id;
this.copyViewRef = null
await this.loadViews()
this.selectedViewIdLocal = viewMeta.id
// await this.onViewChange();
this.$e("a:view:create", { view: viewMeta.type });
this.$e('a:view:create', { view: viewMeta.type })
},
clipboard(str) {
const el = document.createElement("textarea");
el.addEventListener("focusin", (e) => e.stopPropagation());
el.value = str;
document.body.appendChild(el);
el.select();
document.execCommand("copy");
document.body.removeChild(el);
const el = document.createElement('textarea')
el.addEventListener('focusin', e => e.stopPropagation())
el.value = str
document.body.appendChild(el)
el.select()
document.execCommand('copy')
document.body.removeChild(el)
},
clipboardSuccessHandler() {
this.$toast.info("Copied to clipboard").goAway(1000);
this.$toast.info('Copied to clipboard').goAway(1000)
},
copyShareUrlToClipboard() {
this.clipboard(this.sharedViewUrl);
this.clipboardSuccessHandler();
this.$e("c:view:share:copy-url");
},
},
};
this.clipboard(this.sharedViewUrl)
this.clipboardSuccessHandler()
this.$e('c:view:share:copy-url')
}
}
}
</script>
<style scoped lang="scss">

742
packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

2
packages/nc-gui/package.json

@ -23,6 +23,7 @@
"debounce": "^1.2.0",
"file-saver": "^2.0.5",
"fix-path": "^3.0.0",
"httpsnippet": "^2.0.0",
"inflection": "^1.12.0",
"jsep": "^0.4.0",
"material-design-icons-iconfont": "^5.0.1",
@ -65,6 +66,7 @@
"@intlify/eslint-plugin-vue-i18n": "^0.11.1",
"@nuxtjs/eslint-config": "^6.0.1",
"@nuxtjs/vuetify": "^1.11.2",
"@types/httpsnippet": "^1.23.1",
"babel-eslint": "^10.1.0",
"eslint": "^7.31.0",
"monaco-editor-webpack-plugin": "^1.9.1",

4
packages/nocodb/src/lib/noco/meta/api/apiTokenApis.ts

@ -2,6 +2,7 @@ import { Request, Response, Router } from 'express';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import ApiToken from '../../../noco-models/ApiToken';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
export async function apiTokenList(_req: Request, res: Response) {
res.json(await ApiToken.list());
@ -19,14 +20,17 @@ const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/meta/projects/:projectId/api-tokens',
metaApiMetrics,
ncMetaAclMw(apiTokenList, 'apiTokenList')
);
router.post(
'/api/v1/db/meta/projects/:projectId/api-tokens',
metaApiMetrics,
ncMetaAclMw(apiTokenCreate, 'apiTokenCreate')
);
router.delete(
'/api/v1/db/meta/projects/:projectId/api-tokens/:token',
metaApiMetrics,
ncMetaAclMw(apiTokenDelete, 'apiTokenDelete')
);

5
packages/nocodb/src/lib/noco/meta/api/columnApis.ts

@ -31,6 +31,7 @@ import { NcError } from '../helpers/catchError';
import getColumnPropsFromUIDT from '../helpers/getColumnPropsFromUIDT';
import mapDefaultPrimaryValue from '../helpers/mapDefaultPrimaryValue';
import NcConnectionMgrv2 from '../../common/NcConnectionMgrv2';
import { metaApiMetrics } from '../helpers/apiMetrics';
const randomID = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 10);
@ -955,18 +956,22 @@ const deleteHmOrBtRelation = async (
const router = Router({ mergeParams: true });
router.post(
'/api/v1/db/meta/tables/:tableId/columns/',
metaApiMetrics,
ncMetaAclMw(columnAdd, 'columnAdd')
);
router.patch(
'/api/v1/db/meta/columns/:columnId',
metaApiMetrics,
ncMetaAclMw(columnUpdate, 'columnUpdate')
);
router.delete(
'/api/v1/db/meta/columns/:columnId',
metaApiMetrics,
ncMetaAclMw(columnDelete, 'columnDelete')
);
router.post(
'/api/v1/db/meta/columns/:columnId/primary',
metaApiMetrics,
ncMetaAclMw(columnSetAsPrimary, 'columnSetAsPrimary')
);
export default router;

8
packages/nocodb/src/lib/noco/meta/api/filterApis.ts

@ -12,6 +12,7 @@ import Project from '../../../noco-models/Project';
import Filter from '../../../noco-models/Filter';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
// @ts-ignore
export async function filterGet(req: Request, res: Response, next) {
@ -129,10 +130,12 @@ export async function hookFilterCreate(req: Request<any, any, TableReq>, res) {
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/meta/views/:viewId/filters',
metaApiMetrics,
ncMetaAclMw(filterList, 'filterList')
);
router.post(
'/api/v1/db/meta/views/:viewId/filters',
metaApiMetrics,
ncMetaAclMw(filterCreate, 'filterCreate')
);
@ -142,23 +145,28 @@ router.get(
);
router.post(
'/api/v1/db/meta/hooks/:hookId/filters',
metaApiMetrics,
ncMetaAclMw(hookFilterCreate, 'filterCreate')
);
router.get(
'/api/v1/db/meta/filters/:filterId',
metaApiMetrics,
ncMetaAclMw(filterGet, 'filterGet')
);
router.patch(
'/api/v1/db/meta/filters/:filterId',
metaApiMetrics,
ncMetaAclMw(filterUpdate, 'filterUpdate')
);
router.delete(
'/api/v1/db/meta/filters/:filterId',
metaApiMetrics,
ncMetaAclMw(filterDelete, 'filterDelete')
);
router.get(
'/api/v1/db/meta/filters/:filterParentId/children',
metaApiMetrics,
ncMetaAclMw(filterChildrenRead, 'filterChildrenRead')
);
export default router;

5
packages/nocodb/src/lib/noco/meta/api/formViewApis.ts

@ -12,6 +12,7 @@ import View from '../../../noco-models/View';
import FormView from '../../../noco-models/FormView';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
// @ts-ignore
export async function formViewGet(req: Request, res: Response<FormType>) {
@ -41,18 +42,22 @@ export async function formViewDelete(req: Request, res: Response, next) {}
const router = Router({ mergeParams: true });
router.post(
'/api/v1/db/meta/tables/:tableId/forms',
metaApiMetrics,
ncMetaAclMw(formViewCreate, 'formViewCreate')
);
router.get(
'/api/v1/db/meta/forms/:formViewId',
metaApiMetrics,
ncMetaAclMw(formViewGet, 'formViewGet')
);
router.patch(
'/api/v1/db/meta/forms/:formViewId',
metaApiMetrics,
ncMetaAclMw(formViewUpdate, 'formViewUpdate')
);
router.delete(
'/api/v1/db/meta/forms/:formViewId',
metaApiMetrics,
ncMetaAclMw(formViewDelete, 'formViewDelete')
);
export default router;

2
packages/nocodb/src/lib/noco/meta/api/formViewColumnApis.ts

@ -2,6 +2,7 @@ import { Request, Response, Router } from 'express';
import FormViewColumn from '../../../noco-models/FormViewColumn';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
export async function columnUpdate(req: Request, res: Response) {
Tele.emit('evt', { evt_type: 'formViewColumn:updated' });
@ -11,6 +12,7 @@ export async function columnUpdate(req: Request, res: Response) {
const router = Router({ mergeParams: true });
router.patch(
'/api/v1/db/meta/form-columns/:formViewColumnId',
metaApiMetrics,
ncMetaAclMw(columnUpdate, 'columnUpdate')
);
export default router;

4
packages/nocodb/src/lib/noco/meta/api/galleryViewApis.ts

@ -4,6 +4,7 @@ import View from '../../../noco-models/View';
import GalleryView from '../../../noco-models/GalleryView';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
export async function galleryViewGet(req: Request, res: Response<GalleryType>) {
res.json(await GalleryView.get(req.params.galleryViewId));
}
@ -27,14 +28,17 @@ export async function galleryViewUpdate(req, res) {
const router = Router({ mergeParams: true });
router.post(
'/api/v1/db/meta/tables/:tableId/galleries',
metaApiMetrics,
ncMetaAclMw(galleryViewCreate, 'galleryViewCreate')
);
router.patch(
'/api/v1/db/meta/galleries/:galleryViewId',
metaApiMetrics,
ncMetaAclMw(galleryViewUpdate, 'galleryViewUpdate')
);
router.get(
'/api/v1/db/meta/galleries/:galleryViewId',
metaApiMetrics,
ncMetaAclMw(galleryViewGet, 'galleryViewGet')
);
export default router;

2
packages/nocodb/src/lib/noco/meta/api/gridViewApis.ts

@ -11,6 +11,7 @@ import Project from '../../../noco-models/Project';
import View from '../../../noco-models/View';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
// @ts-ignore
export async function gridViewCreate(req: Request<any, any>, res) {
@ -27,6 +28,7 @@ export async function gridViewCreate(req: Request<any, any>, res) {
const router = Router({ mergeParams: true });
router.post(
'/api/v1/db/meta/tables/:tableId/grids/',
metaApiMetrics,
ncMetaAclMw(gridViewCreate, 'gridViewCreate')
);
export default router;

3
packages/nocodb/src/lib/noco/meta/api/gridViewColumnApis.ts

@ -2,6 +2,7 @@ import { Request, Response, Router } from 'express';
import GridViewColumn from '../../../noco-models/GridViewColumn';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
export async function columnList(req: Request, res: Response) {
res.json(await GridViewColumn.list(req.params.gridViewId));
@ -15,10 +16,12 @@ export async function gridColumnUpdate(req: Request, res: Response) {
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/meta/grids/:gridViewId/grid-columns',
metaApiMetrics,
ncMetaAclMw(columnList, 'columnList')
);
router.patch(
'/api/v1/db/meta/grid-columns/:gridViewColumnId',
metaApiMetrics,
ncMetaAclMw(gridColumnUpdate, 'gridColumnUpdate')
);
export default router;

7
packages/nocodb/src/lib/noco/meta/api/hookApis.ts

@ -8,6 +8,7 @@ import Model from '../../../noco-models/Model';
import populateSamplePayload from '../helpers/populateSamplePayload';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
export async function hookList(
req: Request<any, any, any>,
@ -77,26 +78,32 @@ export async function tableSampleData(req: Request, res: Response) {
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/meta/tables/:tableId/hooks',
metaApiMetrics,
ncMetaAclMw(hookList, 'hookList')
);
router.post(
'/api/v1/db/meta/tables/:tableId/hooks/test',
metaApiMetrics,
ncMetaAclMw(hookTest, 'hookTest')
);
router.post(
'/api/v1/db/meta/tables/:tableId/hooks',
metaApiMetrics,
ncMetaAclMw(hookCreate, 'hookCreate')
);
router.delete(
'/api/v1/db/meta/hooks/:hookId',
metaApiMetrics,
ncMetaAclMw(hookDelete, 'hookDelete')
);
router.patch(
'/api/v1/db/meta/hooks/:hookId',
metaApiMetrics,
ncMetaAclMw(hookUpdate, 'hookUpdate')
);
router.get(
'/api/v1/db/meta/tables/:tableId/hooks/samplePayload/:operation',
metaApiMetrics,
catchError(tableSampleData)
);
export default router;

12
packages/nocodb/src/lib/noco/meta/api/hookFilterApis.ts

@ -12,6 +12,7 @@ import Project from '../../../noco-models/Project';
import Filter from '../../../noco-models/Filter';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
// @ts-ignore
export async function filterGet(req: Request, res: Response, next) {
@ -108,25 +109,34 @@ export async function filterDelete(req: Request, res: Response, next) {
}
const router = Router({ mergeParams: true });
router.get('/hooks/:hookId/filters/', ncMetaAclMw(filterList, 'filterList'));
router.get(
'/hooks/:hookId/filters/',
metaApiMetrics,
ncMetaAclMw(filterList, 'filterList')
);
router.post(
'/hooks/:hookId/filters/',
metaApiMetrics,
ncMetaAclMw(filterCreate, 'filterCreate')
);
router.get(
'/hooks/:hookId/filters/:filterId',
metaApiMetrics,
ncMetaAclMw(filterGet, 'filterGet')
);
router.patch(
'/hooks/:hookId/filters/:filterId',
metaApiMetrics,
ncMetaAclMw(filterUpdate, 'filterUpdate')
);
router.delete(
'/hooks/:hookId/filters/:filterId',
metaApiMetrics,
ncMetaAclMw(filterDelete, 'filterDelete')
);
router.get(
'/hooks/:hookId/filters/:filterParentId/children',
metaApiMetrics,
ncMetaAclMw(filterChildrenRead, 'filterChildrenRead')
);
export default router;

3
packages/nocodb/src/lib/noco/meta/api/metaDiffApis.ts

@ -16,6 +16,7 @@ import getTableNameAlias, { getColumnNameAlias } from '../helpers/getTableName';
import mapDefaultPrimaryValue from '../helpers/mapDefaultPrimaryValue';
import { Tele } from 'nc-help';
import getColumnUiType from '../helpers/getColumnUiType';
import { metaApiMetrics } from '../helpers/apiMetrics';
export enum MetaDiffType {
TABLE_NEW = 'TABLE_NEW',
@ -849,10 +850,12 @@ export async function extractAndGenerateManyToManyRelations(
const router = Router();
router.get(
'/api/v1/db/meta/projects/:projectId/meta-diff',
metaApiMetrics,
ncMetaAclMw(metaDiff, 'metaDiff')
);
router.post(
'/api/v1/db/meta/projects/:projectId/meta-diff',
metaApiMetrics,
ncMetaAclMw(metaDiffSync, 'metaDiffSync')
);
export default router;

3
packages/nocodb/src/lib/noco/meta/api/modelVisibilityApis.ts

@ -4,6 +4,7 @@ import { Router } from 'express';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import Project from '../../../noco-models/Project';
import { metaApiMetrics } from '../helpers/apiMetrics';
async function xcVisibilityMetaSetAll(req, res) {
Tele.emit('evt', { evt_type: 'uiAcl:updated' });
for (const d of req.body) {
@ -108,6 +109,7 @@ export async function xcVisibilityMetaGet(
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/meta/projects/:projectId/visibility-rules',
metaApiMetrics,
ncMetaAclMw(async (req, res) => {
res.json(
await xcVisibilityMetaGet(
@ -120,6 +122,7 @@ router.get(
);
router.post(
'/api/v1/db/meta/projects/:projectId/visibility-rules',
metaApiMetrics,
ncMetaAclMw(xcVisibilityMetaSetAll, 'modelVisibilitySet')
);
export default router;

11
packages/nocodb/src/lib/noco/meta/api/pluginApis.ts

@ -5,6 +5,7 @@ import { PluginType } from 'nocodb-sdk';
import NcPluginMgrv2 from '../helpers/NcPluginMgrv2';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
export async function pluginList(_req: Request, res: Response) {
res.json(new PagedResponseImpl(await Plugin.list()));
@ -34,21 +35,29 @@ export async function isPluginActive(req: Request, res: Response) {
}
const router = Router({ mergeParams: true });
router.get('/api/v1/db/meta/plugins', ncMetaAclMw(pluginList, 'pluginList'));
router.get(
'/api/v1/db/meta/plugins',
metaApiMetrics,
ncMetaAclMw(pluginList, 'pluginList')
);
router.post(
'/api/v1/db/meta/plugins/test',
metaApiMetrics,
ncMetaAclMw(pluginTest, 'pluginTest')
);
router.get(
'/api/v1/db/meta/plugins/:pluginId',
metaApiMetrics,
ncMetaAclMw(pluginRead, 'pluginRead')
);
router.patch(
'/api/v1/db/meta/plugins/:pluginId',
metaApiMetrics,
ncMetaAclMw(pluginUpdate, 'pluginUpdate')
);
router.get(
'/api/v1/db/meta/plugins/:pluginTitle/status',
metaApiMetrics,
ncMetaAclMw(isPluginActive, 'isPluginActive')
);
export default router;

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

@ -22,6 +22,7 @@ import { NcError } from '../helpers/catchError';
import getColumnUiType from '../helpers/getColumnUiType';
import mapDefaultPrimaryValue from '../helpers/mapDefaultPrimaryValue';
import { extractAndGenerateManyToManyRelations } from './metaDiffApis';
import { metaApiMetrics } from '../helpers/apiMetrics';
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4);
@ -392,22 +393,27 @@ export async function projectInfoGet(req, res) {
export default router => {
router.get(
'/api/v1/db/meta/projects/:projectId/info',
metaApiMetrics,
ncMetaAclMw(projectInfoGet, 'projectInfoGet')
);
router.get(
'/api/v1/db/meta/projects/:projectId',
metaApiMetrics,
ncMetaAclMw(projectGet, 'projectGet')
);
router.delete(
'/api/v1/db/meta/projects/:projectId',
metaApiMetrics,
ncMetaAclMw(projectDelete, 'projectDelete')
);
router.post(
'/api/v1/db/meta/projects',
metaApiMetrics,
ncMetaAclMw(projectCreate, 'projectCreate')
);
router.get(
'/api/v1/db/meta/projects',
metaApiMetrics,
ncMetaAclMw(projectList, 'projectList')
);
};

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

@ -14,6 +14,7 @@ import * as ejs from 'ejs';
import NcPluginMgrv2 from '../helpers/NcPluginMgrv2';
import Noco from '../../Noco';
import { PluginCategory } from 'nocodb-sdk';
import { metaApiMetrics } from '../helpers/apiMetrics';
async function userList(req, res) {
res.json({
@ -297,22 +298,27 @@ async function sendInviteEmail(
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/meta/projects/:projectId/users',
metaApiMetrics,
ncMetaAclMw(userList, 'userList')
);
router.post(
'/api/v1/db/meta/projects/:projectId/users',
metaApiMetrics,
ncMetaAclMw(userInvite, 'userInvite')
);
router.patch(
'/api/v1/db/meta/projects/:projectId/users/:userId',
metaApiMetrics,
ncMetaAclMw(projectUserUpdate, 'projectUserUpdate')
);
router.delete(
'/api/v1/db/meta/projects/:projectId/users/:userId',
metaApiMetrics,
ncMetaAclMw(projectUserDelete, 'projectUserDelete')
);
router.post(
'/api/v1/db/meta/projects/:projectId/users/:userId/resend-invite',
metaApiMetrics,
ncMetaAclMw(projectUserInviteResend, 'projectUserInviteResend')
);
export default router;

11
packages/nocodb/src/lib/noco/meta/api/sortApis.ts

@ -11,6 +11,7 @@ import Project from '../../../noco-models/Project';
import Sort from '../../../noco-models/Sort';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
// @ts-ignore
export async function sortGet(req: Request, res: Response<TableType>) {}
@ -51,19 +52,27 @@ export async function sortDelete(req: Request, res: Response) {
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/meta/views/:viewId/sorts/',
metaApiMetrics,
ncMetaAclMw(sortList, 'sortList')
);
router.post(
'/api/v1/db/meta/views/:viewId/sorts/',
metaApiMetrics,
ncMetaAclMw(sortCreate, 'sortCreate')
);
router.get('/api/v1/db/meta/sorts/:sortId', ncMetaAclMw(sortGet, 'sortGet'));
router.get(
'/api/v1/db/meta/sorts/:sortId',
metaApiMetrics,
ncMetaAclMw(sortGet, 'sortGet')
);
router.patch(
'/api/v1/db/meta/sorts/:sortId',
metaApiMetrics,
ncMetaAclMw(sortUpdate, 'sortUpdate')
);
router.delete(
'/api/v1/db/meta/sorts/:sortId',
metaApiMetrics,
ncMetaAclMw(sortDelete, 'sortDelete')
);
export default router;

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

@ -26,6 +26,7 @@ import Column from '../../../noco-models/Column';
import NcConnectionMgrv2 from '../../common/NcConnectionMgrv2';
import getColumnUiType from '../helpers/getColumnUiType';
import LinkToAnotherRecordColumn from '../../../noco-models/LinkToAnotherRecordColumn';
import { metaApiMetrics } from '../helpers/apiMetrics';
export async function tableGet(req: Request, res: Response<TableType>) {
const table = await Model.getWithInfo({
id: req.params.tableId
@ -264,26 +265,32 @@ export async function tableDelete(req: Request, res: Response) {
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/meta/projects/:projectId/tables',
metaApiMetrics,
ncMetaAclMw(tableList, 'tableList')
);
router.post(
'/api/v1/db/meta/projects/:projectId/tables',
metaApiMetrics,
ncMetaAclMw(tableCreate, 'tableCreate')
);
router.get(
'/api/v1/db/meta/tables/:tableId',
metaApiMetrics,
ncMetaAclMw(tableGet, 'tableGet')
);
router.patch(
'/api/v1/db/meta/tables/:tableId',
metaApiMetrics,
ncMetaAclMw(tableUpdate, 'tableUpdate')
);
router.delete(
'/api/v1/db/meta/tables/:tableId',
metaApiMetrics,
ncMetaAclMw(tableDelete, 'tableDelete')
);
router.post(
'/api/v1/db/meta/tables/:tableId/reorder',
metaApiMetrics,
ncMetaAclMw(tableReorder, 'tableReorder')
);
export default router;

9
packages/nocodb/src/lib/noco/meta/api/viewApis.ts

@ -13,6 +13,7 @@ import View from '../../../noco-models/View';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { xcVisibilityMetaGet } from './modelVisibilityApis';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
// @ts-ignore
export async function viewGet(req: Request, res: Response<Table>) {}
@ -103,27 +104,33 @@ async function shareViewList(req: Request<any, any>, res) {
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/meta/tables/:tableId/views',
metaApiMetrics,
ncMetaAclMw(viewList, 'viewList')
);
router.patch(
'/api/v1/db/meta/views/:viewId',
metaApiMetrics,
ncMetaAclMw(viewUpdate, 'viewUpdate')
);
router.delete(
'/api/v1/db/meta/views/:viewId',
metaApiMetrics,
ncMetaAclMw(viewDelete, 'viewDelete')
);
router.post(
'/api/v1/db/meta/views/:viewId/show-all',
metaApiMetrics,
ncMetaAclMw(showAllColumns, 'showAllColumns')
);
router.post(
'/api/v1/db/meta/views/:viewId/hide-all',
metaApiMetrics,
ncMetaAclMw(hideAllColumns, 'hideAllColumns')
);
router.get(
'/api/v1/db/meta/tables/:tableId/share',
metaApiMetrics,
ncMetaAclMw(shareViewList, 'shareViewList')
);
router.post(
@ -132,10 +139,12 @@ router.post(
);
router.patch(
'/api/v1/db/meta/views/:viewId/share',
metaApiMetrics,
ncMetaAclMw(shareViewPasswordUpdate, 'shareViewPasswordUpdate')
);
router.delete(
'/api/v1/db/meta/views/:viewId/share',
metaApiMetrics,
ncMetaAclMw(shareViewDelete, 'shareViewDelete')
);

4
packages/nocodb/src/lib/noco/meta/api/viewColumnApis.ts

@ -2,6 +2,7 @@ import { Request, Response, Router } from 'express';
import View from '../../../noco-models/View';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
export async function columnList(req: Request, res: Response) {
res.json(await View.getColumns(req.params.viewId));
@ -33,14 +34,17 @@ export async function columnUpdate(req: Request, res: Response) {
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/meta/views/:viewId/columns/',
metaApiMetrics,
ncMetaAclMw(columnList, 'columnList')
);
router.post(
'/api/v1/db/meta/views/:viewId/columns/',
metaApiMetrics,
ncMetaAclMw(columnAdd, 'columnAdd')
);
router.patch(
'/api/v1/db/meta/views/:viewId/columns/:columnId',
metaApiMetrics,
ncMetaAclMw(columnUpdate, 'viewColumnUpdate')
);
export default router;

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

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

2
scripts/cypress/integration/common/1a_table_operations.js

@ -12,7 +12,7 @@ export const genTest = (apiType, dbType) => {
});
after(() => {
cy.get(".mdi-close").click({ multiple: true });
cy.get(".mdi-close").click({ multiple: true, force: true });
});
const name = "tablex";

42
scripts/cypress/support/commands.js

@ -162,14 +162,16 @@ Cypress.Commands.add("openTableTab", (tn, rc) => {
.click({ force: true });
cy.get(".nc-project-tree")
.contains(tn, { timeout: 6000 })
.contains(tn)
.should('exist')
.first()
.click({ force: true });
cy.get(`.project-tab`).contains(tn, { timeout: 10000 }).should("exist");
cy.get(`.project-tab`).contains(tn).should("exist");
cy.get(".nc-project-tree")
.find(".v-list-item__title:contains(Tables)", { timeout: 10000 })
.find(".v-list-item__title:contains(Tables)")
.should('exist')
.first()
.click({ force: true });
@ -221,7 +223,7 @@ Cypress.Commands.add("openOrCreateGqlProject", (_args) => {
cy.contains("GRAPHQL APIs").closest("label").click();
cy.get(".database-field input").click().clear().type("sakila");
cy.contains("Test Database Connection").click();
cy.contains("Ok & Save Project", { timeout: 3000 }).click();
cy.contains("Ok & Save Project").should('exist').click();
}
}
});
@ -280,11 +282,13 @@ Cypress.Commands.add("createTable", (name) => {
Cypress.Commands.add("deleteTable", (name, dbType) => {
cy.get(".nc-project-tree")
.find(".v-list-item__title:contains(Tables)", { timeout: 10000 })
.find(".v-list-item__title:contains(Tables)")
.should('exist')
.first()
.click();
cy.get(".nc-project-tree")
.contains(name, { timeout: 6000 })
.contains(name)
.should('exist')
.first()
.click({ force: true });
cy.get(`.project-tab:contains(${name}):visible`).should("exist");
@ -301,13 +305,15 @@ Cypress.Commands.add("deleteTable", (name, dbType) => {
Cypress.Commands.add("renameTable", (oldName, newName) => {
// expand project tree
cy.get(".nc-project-tree")
.find(".v-list-item__title:contains(Tables)", { timeout: 10000 })
.find(".v-list-item__title:contains(Tables)")
.should('exist')
.first()
.click();
// right click on project table name
cy.get(".nc-project-tree")
.contains(oldName, { timeout: 6000 })
.contains(oldName)
.should('exist')
.first()
.rightclick();
@ -328,18 +334,23 @@ Cypress.Commands.add("renameTable", (oldName, newName) => {
// close expanded project tree
cy.get(".nc-project-tree")
.find(".v-list-item__title:contains(Tables)", { timeout: 10000 })
.find(".v-list-item__title:contains(Tables)")
.should('exist')
.first()
.click();
cy.wait(8000)
});
Cypress.Commands.add("createColumn", (table, columnName) => {
cy.get(".nc-project-tree")
.find(".v-list-item__title:contains(Tables)", { timeout: 10000 })
.find(".v-list-item__title:contains(Tables)")
.should('exist')
.first()
.click();
cy.get(".nc-project-tree")
.contains(table, { timeout: 6000 })
.contains(table)
.should('exist')
.first()
.click({ force: true });
cy.get(`.project-tab:contains(${table}):visible`).should("exist");
@ -366,7 +377,7 @@ Cypress.Commands.add("openViewsTab", (vn, rc) => {
cy.task("log", `[openViewsTab] ${vn} ${rc}`);
cy.get(".nc-project-tree")
.find(".v-list-item__title:contains(Tables)", { timeout: 10000 })
.find(".v-list-item__title:contains(Tables)")
.should("exist")
.first()
.click({ force: true });
@ -376,10 +387,11 @@ Cypress.Commands.add("openViewsTab", (vn, rc) => {
.first()
.click({ force: true });
cy.get(`.project-tab`).contains(vn, { timeout: 10000 }).should("exist");
cy.get(`.project-tab`).contains(vn).should("exist");
cy.get(".nc-project-tree")
.find(".v-list-item__title:contains(Tables)", { timeout: 10000 })
.find(".v-list-item__title:contains(Tables)")
.should('exist')
.first()
.click({ force: true });
@ -391,7 +403,7 @@ Cypress.Commands.add("openViewsTab", (vn, rc) => {
Cypress.Commands.add("closeViewsTab", (vn) => {
cy.task("log", `[closeViewsTab] ${vn}`);
cy.get(`.project-tab`).contains(vn, { timeout: 10000 }).should("exist");
cy.get(`.project-tab`).contains(vn).should("exist");
cy.get(`[href="#view||||${vn}"]`)
.find("button.mdi-close")
.click({ force: true });

Loading…
Cancel
Save