Browse Source

Feat - UUID auto generate and bug fixes (#2069)

* wip: automatic nanoid generation

Signed-off-by: Pranav C <pranavxc@gmail.com>

* wip: auto-generated id

Signed-off-by: Pranav C <pranavxc@gmail.com>

* wip: table create ui

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: sanitize column name

Signed-off-by: Pranav C <pranavxc@gmail.com>

* feat: table creation with autogenerated string id

Signed-off-by: Pranav C <pranavxc@gmail.com>

* enhancement: populate table name

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: attachment cell rendering bug

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: flickering issue in dashboard tabs

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: type correction

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: hide system fields from expanded form view

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: in case of non auto_increment primary key sort by `created_at` if present

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/2074/head
Pranav C 3 years ago committed by GitHub
parent
commit
a11a5abf59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      packages/nc-gui/components/ProjectTabs.vue
  2. 2
      packages/nc-gui/components/global/NcSlider.vue
  3. 273
      packages/nc-gui/components/project/spreadsheet/RowsXcDataTable.vue
  4. 64
      packages/nc-gui/components/project/spreadsheet/components/EditColumn.vue
  5. 2
      packages/nc-gui/components/project/spreadsheet/components/EditableCell.vue
  6. 14
      packages/nc-gui/components/project/spreadsheet/components/ExpandedForm.vue
  7. 2
      packages/nc-gui/components/project/spreadsheet/components/Extras.vue
  8. 9
      packages/nc-gui/components/project/spreadsheet/components/HeaderCell.vue
  9. 15
      packages/nc-gui/components/project/spreadsheet/components/SpreadsheetNavDrawer.vue
  10. 0
      packages/nc-gui/components/project/spreadsheet/components/editColumn/CheckboxOptions.vue
  11. 11
      packages/nc-gui/components/project/spreadsheet/components/editColumn/NormalColumnOptions.vue
  12. 8
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/BelongsToCell.vue
  13. 4
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/HasManyCell.vue
  14. 8
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/ManyToManyCell.vue
  15. 13
      packages/nc-gui/components/settings/SettingsModal.vue
  16. 220
      packages/nc-gui/components/utils/DlgTableCreate.vue
  17. 2
      packages/nc-lib-gui/package.json
  18. 1
      packages/nocodb-sdk/src/lib/helperFunctions.ts
  19. 30
      packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts
  20. 27
      packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts
  21. 20
      packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts
  22. 40
      packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts
  23. 37
      packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts
  24. 2
      packages/nocodb-sdk/src/lib/sqlUi/index.ts
  25. 55
      packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSqlv2.ts
  26. 18
      packages/nocodb/src/lib/noco/meta/api/sync/helpers/job.ts
  27. 1
      packages/nocodb/src/lib/noco/meta/api/tableApis.ts
  28. 9
      packages/nocodb/src/lib/noco/meta/helpers/getColumnPropsFromUIDT.ts

21
packages/nc-gui/components/ProjectTabs.vue

@ -57,7 +57,6 @@
:value="`${(tab._nodes && tab._nodes.type) || ''}||${
(tab._nodes && tab._nodes.dbAlias) || ''
}||${tab.name}`"
eager
:transition="false"
style="height: 100%"
:reverse-transition="false"
@ -595,9 +594,6 @@ export default {
</script>
<style scoped>
/*/deep/ .project-tabs > .v-tabs-items {*/
/* border-top: 1px solid #7F828B33;*/
/*}*/
/deep/ .project-tabs .v-tabs-bar {
max-height: 30px;
@ -606,10 +602,9 @@ export default {
/deep/ .project-tabs > .v-tabs-bar {
max-height: 30px;
}
/*/deep/ .project-tabs .v-tabs-slider-wrapper {*/
/* display: none;*/
/*}*/
/deep/ .v-window__container .v-window-item {
height: 100%;
}
/deep/ .project-tabs .v-tab.project-tab {
text-transform: capitalize;
@ -630,12 +625,6 @@ export default {
color: white !important;
}
/*
/deep/ .project-tabs.dark-them > div > div > div > div > .v-tabs-slider {
color: #272727 !important;
}
*/
/deep/ .project-tabs > div > div > div > div > .v-tabs-slider {
color: transparent !important;
}
@ -680,6 +669,10 @@ export default {
/deep/ .project-tab:first-of-type {
margin-left: 0 !important;
}
/deep/ .v-window-item:not(.v-window-item--active){
display:none;
}
</style>
<!--
/**

2
packages/nc-gui/components/global/NcSlider.vue

@ -53,7 +53,7 @@ export default {
top: 0;
right: min(-50%, -700px);
transition: .3s right;
overflow-y: auto;
}
&.active {

273
packages/nc-gui/components/project/spreadsheet/RowsXcDataTable.vue

@ -332,135 +332,138 @@
>
<div class="flex-grow-1 h-100" style="overflow-y: auto">
<div
v-if="selectedViewId && selectedView"
ref="table"
:style="{ height: isForm ? '100%' : 'calc(100% - 36px)' }"
style="overflow: auto; width: 100%"
>
<!-- <v-skeleton-loader v-if="!dataLoaded && loadingData || !meta" type="table" />-->
<template v-if="selectedView.type === viewTypes.GRID">
<xc-grid-view
ref="ncgridview"
:loading="loadingData"
:is-view="isView"
droppable
:relation-type="relationType"
:columns-width.sync="columnsWidth"
:is-locked="isLocked"
:table="table"
:available-columns="availableColumns"
:show-fields="showFields"
:sql-ui="sqlUi"
:is-editable="isEditable"
:nodes="nodes"
:primary-value-column="primaryValueColumn"
:belongs-to="belongsTo"
:has-many="hasMany"
:data="data"
:visible-col-length="visibleColLength"
:meta="meta"
:is-virtual="selectedView && selectedView.type === 'vtable'"
:api="api"
:is-pk-avail="isPkAvail"
:view-id="selectedViewId"
@drop="onFileDrop"
@onNewColCreation="onNewColCreation"
@colDelete="onColDelete"
@onCellValueChange="onCellValueChange"
@insertNewRow="insertNewRow"
@showRowContextMenu="showRowContextMenu"
@expandRow="expandRow"
@onRelationDelete="loadMeta"
@loadTableData="loadTableData"
@loadMeta="loadMeta"
/>
</template>
<template v-else-if="selectedView.type === viewTypes.GALLERY">
<gallery-view
:is-locked="isLocked"
:nodes="nodes"
:table="table"
:show-fields="showFields"
:available-columns="availableColumns"
:meta="meta"
:data="data"
:sql-ui="sqlUi"
:view-id="selectedViewId"
:primary-value-column="primaryValueColumn"
:cover-image-field.sync="coverImageField"
@expandForm="
({ rowIndex, rowMeta }) => expandRow(rowIndex, rowMeta)
"
/>
</template>
<template v-else-if="isKanban">
<v-container v-if="kanban.loadingData" fluid>
<v-row>
<v-col v-for="idx in 5" :key="idx">
<v-skeleton-loader type="image@3" />
</v-col>
</v-row>
</v-container>
<kanban-view
v-if="!kanban.loadingData && kanban.data.length"
:nodes="nodes"
:table="table"
:show-fields="showFields"
:available-columns="availableColumns"
:meta="meta"
:kanban="kanban"
:sql-ui="sqlUi"
:primary-value-column="primaryValueColumn"
:grouping-field.sync="groupingField"
:api="api"
@expandKanbanForm="({ rowIdx }) => expandKanbanForm(rowIdx)"
@insertNewRow="insertNewRow"
@loadMoreKanbanData="
(groupingFieldVal) => loadMoreKanbanData(groupingFieldVal)
"
/>
</template>
<template
v-else-if="selectedView && selectedView.show_as === 'calendar'"
v-if="selectedViewId && selectedView"
>
<calendar-view
:nodes="nodes"
:table="table"
:show-fields="showFields"
:available-columns="availableColumns"
:meta="meta"
:data="data"
:primary-value-column="primaryValueColumn"
@expandForm="
({ rowIndex, rowMeta }) => expandRow(rowIndex, rowMeta)
"
/>
</template>
<template v-else-if="selectedView.type === viewTypes.FORM">
<form-view
:id="selectedViewId"
ref="formView"
:key="selectedViewId + viewKey"
:view-id="selectedViewId"
:nodes="nodes"
:table="table"
:available-columns="availableColumns"
:meta="meta"
:data="data"
:show-fields.sync="showFields"
:all-columns="allColumns"
:field-list="fieldList"
:is-locked="isLocked"
:db-alias="nodes.dbAlias"
:api="api"
:sql-ui="sqlUi"
:fields-order.sync="fieldsOrder"
:primary-value-column="primaryValueColumn"
:form-params.sync="extraViewParams.formParams"
:view.sync="selectedView"
:view-title="selectedView.title"
@onNewColCreation="loadMeta(false)"
/>
<!-- <v-skeleton-loader v-if="!dataLoaded && loadingData || !meta" type="table" />-->
<template v-if="selectedView.type === viewTypes.GRID">
<xc-grid-view
ref="ncgridview"
:loading="loadingData"
:is-view="isView"
droppable
:relation-type="relationType"
:columns-width.sync="columnsWidth"
:is-locked="isLocked"
:table="table"
:available-columns="availableColumns"
:show-fields="showFields"
:sql-ui="sqlUi"
:is-editable="isEditable"
:nodes="nodes"
:primary-value-column="primaryValueColumn"
:belongs-to="belongsTo"
:has-many="hasMany"
:data="data"
:visible-col-length="visibleColLength"
:meta="meta"
:is-virtual="selectedView && selectedView.type === 'vtable'"
:api="api"
:is-pk-avail="isPkAvail"
:view-id="selectedViewId"
@drop="onFileDrop"
@onNewColCreation="onNewColCreation"
@colDelete="onColDelete"
@onCellValueChange="onCellValueChange"
@insertNewRow="insertNewRow"
@showRowContextMenu="showRowContextMenu"
@expandRow="expandRow"
@onRelationDelete="loadMeta"
@loadTableData="loadTableData"
@loadMeta="loadMeta"
/>
</template>
<template v-else-if="selectedView.type === viewTypes.GALLERY">
<gallery-view
:is-locked="isLocked"
:nodes="nodes"
:table="table"
:show-fields="showFields"
:available-columns="availableColumns"
:meta="meta"
:data="data"
:sql-ui="sqlUi"
:view-id="selectedViewId"
:primary-value-column="primaryValueColumn"
:cover-image-field.sync="coverImageField"
@expandForm="
({ rowIndex, rowMeta }) => expandRow(rowIndex, rowMeta)
"
/>
</template>
<template v-else-if="isKanban">
<v-container v-if="kanban.loadingData" fluid>
<v-row>
<v-col v-for="idx in 5" :key="idx">
<v-skeleton-loader type="image@3" />
</v-col>
</v-row>
</v-container>
<kanban-view
v-if="!kanban.loadingData && kanban.data.length"
:nodes="nodes"
:table="table"
:show-fields="showFields"
:available-columns="availableColumns"
:meta="meta"
:kanban="kanban"
:sql-ui="sqlUi"
:primary-value-column="primaryValueColumn"
:grouping-field.sync="groupingField"
:api="api"
@expandKanbanForm="({ rowIdx }) => expandKanbanForm(rowIdx)"
@insertNewRow="insertNewRow"
@loadMoreKanbanData="
(groupingFieldVal) => loadMoreKanbanData(groupingFieldVal)
"
/>
</template>
<template
v-else-if="selectedView && selectedView.show_as === 'calendar'"
>
<calendar-view
:nodes="nodes"
:table="table"
:show-fields="showFields"
:available-columns="availableColumns"
:meta="meta"
:data="data"
:primary-value-column="primaryValueColumn"
@expandForm="
({ rowIndex, rowMeta }) => expandRow(rowIndex, rowMeta)
"
/>
</template>
<template v-else-if="selectedView.type === viewTypes.FORM">
<form-view
:id="selectedViewId"
ref="formView"
:key="selectedViewId + viewKey"
:view-id="selectedViewId"
:nodes="nodes"
:table="table"
:available-columns="availableColumns"
:meta="meta"
:data="data"
:show-fields.sync="showFields"
:all-columns="allColumns"
:field-list="fieldList"
:is-locked="isLocked"
:db-alias="nodes.dbAlias"
:api="api"
:sql-ui="sqlUi"
:fields-order.sync="fieldsOrder"
:primary-value-column="primaryValueColumn"
:form-params.sync="extraViewParams.formParams"
:view.sync="selectedView"
:view-title="selectedView.title"
@onNewColCreation="loadMeta(false)"
/>
</template>
</template>
</div>
<template v-if="data && (isGrid || isGallery)">
@ -636,6 +639,7 @@
width="1000px"
max-width="100%"
class="mx-auto"
transition="dialog-bottom-transition"
>
<expanded-form
v-if="isKanban && kanban.selectedExpandRow"
@ -726,7 +730,7 @@
<script>
import { mapActions } from 'vuex'
import debounce from 'debounce'
import { SqlUiFactory, ViewTypes } from 'nocodb-sdk'
import { SqlUiFactory, ViewTypes, UITypes } from 'nocodb-sdk'
import FileSaver from 'file-saver'
import FormView from './views/FormView'
import XcGridView from './views/GridView'
@ -1002,9 +1006,21 @@ export default {
if (this.nodes.newTable && !this.nodes.tableCreated) {
const columns = this.sqlUi
.getNewTableColumns()
.filter(col =>
this.nodes.newTable.columns.includes(col.column_name)
)
.filter((col) => {
if (col.column_name === 'id' && this.nodes.newTable.columns.includes('id_ag')) {
Object.assign(col, this.sqlUi.getDataTypeForUiType({ uidt: UITypes.ID }, 'AG'))
col.dtxp = this.sqlUi.getDefaultLengthForDatatype(
col.dt
)
col.dtxs = this.sqlUi.getDefaultScaleForDatatype(
col.dt
)
return true
}
return this.nodes.newTable.columns.includes(col.column_name)
})
await this.$api.dbTable.create(this.projectId, {
table_name: this.nodes.table_name,
title: this.nodes.title,
@ -1056,7 +1072,7 @@ export default {
pks.length &&
pks.every(
col =>
!rowObj[col.title] && !(col.columnDefault || col.default)
!rowObj[col.title] && !(col.columnDefault || col.default) && !(col.meta && col.meta.ag)
)
) {
return this.$toast
@ -1068,6 +1084,7 @@ export default {
return (
!col.ai &&
col.rqd &&
!(col.meta && col.meta.ag) &&
(rowObj[col.title] === undefined ||
rowObj[col.title] === null) &&
!col.cdf

64
packages/nc-gui/components/project/spreadsheet/components/EditColumn.vue

@ -88,7 +88,7 @@
dense
outlined
:items="uiTypes"
@change="onUiTypeChange"
@change="onUiOrIdTypeChange"
>
<template #selection="{ item }">
<div>
@ -110,6 +110,21 @@
</template>
</v-autocomplete>
<!--
<v-autocomplete
v-if="newColumn && newColumn.uidt === 'ID'"
v-model="idType"
outlined
class="caption mt-4"
hide-details
label="ID Type"
:items="idTypes"
required
dense
@change="onUiOrIdTypeChange"
/>
-->
<v-alert
v-if="
column &&
@ -175,8 +190,18 @@
<v-col v-show="advanceOptions || !accordion" cols="12">
<v-row>
<v-col v-if="newColumn.meta && columnToValidate.includes(newColumn.uidt)" cols="12" class="pt-0 pb-0">
<v-checkbox v-model="newColumn.meta.validate" dense hide-details :label="`Accept only valid ${newColumn.uidt}`" class="mt-0" />
<v-col
v-if="newColumn.meta && columnToValidate.includes(newColumn.uidt)"
cols="12"
class="pt-0 pb-0"
>
<v-checkbox
v-model="newColumn.meta.validate"
dense
hide-details
:label="`Accept only valid ${newColumn.uidt}`"
class="mt-0"
/>
</v-col>
<template v-if="newColumn.uidt !== 'Formula'">
@ -546,7 +571,8 @@ import DlgLabelSubmitCancel from '~/components/utils/DlgLabelSubmitCancel'
import LinkedToAnotherOptions from '~/components/project/spreadsheet/components/editColumn/LinkedToAnotherOptions'
import { validateColumnName } from '~/helpers'
import RatingOptions from '~/components/project/spreadsheet/components/editColumn/RatingOptions'
import CheckboxOptions from '~/components/project/spreadsheet/components/editColumn/checkboxOptions'
import CheckboxOptions from '~/components/project/spreadsheet/components/editColumn/CheckboxOptions'
const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber]
export default {
@ -576,9 +602,14 @@ export default {
valid: false,
relationDeleteDlg: false,
newColumn: {},
advanceOptions: false
advanceOptions: false,
idType: null,
idTypes: [{ value: 'AI', text: 'Auto increment number' }, { value: 'AG', text: 'Auto generated string' }]
}),
computed: {
isIDCol() {
return this.column && this.column.uidt === UITypes.ID
},
accordion() {
return ![
UITypes.LinkToAnotherRecord,
@ -589,7 +620,14 @@ export default {
].includes(this.newColumn && this.newColumn.uidt)
},
uiTypes() {
return uiTypes.filter(t => !this.editColumn || !t.virtual)
return [...uiTypes.filter(t => !this.editColumn || !t.virtual),
...((!this.editColumn && this.meta.columns.every(c => !c.pk))
? [{
name: 'ID',
icon: 'mdi-identifier'
}]
: [])
]
},
isEditDisabled() {
return this.editColumn && this.sqlUi === SqliteUi
@ -601,7 +639,7 @@ export default {
return this.sqlUi === MssqlUi
},
dataTypes() {
return this.sqlUi.getDataTypeListForUiType(this.newColumn)
return this.sqlUi.getDataTypeListForUiType(this.newColumn, this.idType)
},
isSelect() {
return (
@ -711,6 +749,14 @@ export default {
if (this.editColumn) {
await this.$api.dbTableColumn.update(this.column.id, this.newColumn)
} else {
if (this.newColumn.uidt === UITypes.ID) {
// based on id column type set autogenerated meta prop
if (this.isAutoGenId) {
this.newColumn.meta = {
ag: 'nc'
}
}
}
await this.$api.dbTableColumn.create(this.meta.id, this.newColumn)
}
@ -761,8 +807,8 @@ export default {
this.newColumn.altered = this.newColumn.altered || 2
},
onUiTypeChange() {
const colProp = this.sqlUi.getDataTypeForUiType(this.newColumn)
onUiOrIdTypeChange() {
const colProp = this.sqlUi.getDataTypeForUiType(this.newColumn, this.idType)
this.newColumn = {
...this.newColumn,
meta: null,

2
packages/nc-gui/components/project/spreadsheet/components/EditableCell.vue

@ -24,7 +24,7 @@
/>
<rating-cell
v-if="isRating"
v-else-if="isRating"
v-model="localState"
:active="active"
:is-form="isForm"

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

@ -319,7 +319,7 @@
import dayjs from 'dayjs'
import {
AuditOperationSubTypes,
AuditOperationTypes,
AuditOperationTypes, isSystemColumn,
isVirtualCol,
UITypes
} from 'nocodb-sdk'
@ -403,20 +403,14 @@ export default {
if (this.availableColumns) {
return this.availableColumns
}
const hideCols = ['created_at', 'updated_at']
//
// const hideCols = ['created_at', 'updated_at']
if (this.showSystemFields) {
return this.meta.columns || []
} else {
return (
this.meta.columns.filter(
c =>
!(c.pk && c.ai) &&
!hideCols.includes(c.column_name) &&
!(this.meta.v || []).some(
v => v.bt && v.bt.column_name === c.column_name
)
c => !isSystemColumn(c)
) || []
)
}

2
packages/nc-gui/components/project/spreadsheet/components/Extras.vue

@ -31,7 +31,7 @@
dense
>
<v-list-item>
<div class="justify-space-between d-100 pr-2">
<div class="d-flex 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>

9
packages/nc-gui/components/project/spreadsheet/components/HeaderCell.vue

@ -61,7 +61,7 @@
<v-list-item
class="nc-column-edit"
dense
@click="editColumnMenu = true"
@click="showColumnEdit"
>
<x-icon small class="mr-1" color="primary">
mdi-pencil
@ -164,6 +164,7 @@
</template>
<script>
import { UITypes } from 'nocodb-sdk'
import cell from '@/components/project/spreadsheet/mixins/cell'
import EditColumn from '~/components/project/spreadsheet/components/EditColumn'
@ -177,6 +178,12 @@ export default {
columnDeleteDialog: false
}),
methods: {
showColumnEdit() {
if (this.column.uidt === UITypes.ID) {
return this.$toast.info('Primary key column edit is not allowed.').goAway(3000)
}
this.editColumnMenu = true
},
async deleteColumn() {
try {
const column = { ...this.column, cno: this.column.column_name }

15
packages/nc-gui/components/project/spreadsheet/components/SpreadsheetNavDrawer.vue

@ -303,15 +303,26 @@
<div
v-if="time - $store.state.settings.miniSponsorCard > 15 * 60 * 1000"
class="pa-2 sponsor-wrapper"
class="py-2 sponsor-wrapper"
>
<v-icon small class="close-icon" @click="hideMiniSponsorCard">
mdi-close-circle-outline
</v-icon>
<!-- <extras />-->
<v-divider />
<v-divider class="my-2" />
<extras class="pl-1" />
<v-btn
v-t="['e:hiring']"
color="primary"
outlined
class="caption d-100 my-2 "
href="https://angel.co/company/nocodb"
target="_blank"
>
🚀 We are Hiring! 🚀
</v-btn>
<!-- <sponsor-mini nav />-->
</div>

0
packages/nc-gui/components/project/spreadsheet/components/editColumn/checkboxOptions.vue → packages/nc-gui/components/project/spreadsheet/components/editColumn/CheckboxOptions.vue

11
packages/nc-gui/components/project/spreadsheet/components/editColumn/NormalColumnOptions.vue

@ -0,0 +1,11 @@
<template />
<script>
export default {
name: 'NormalColumnOptions'
}
</script>
<style scoped>
</style>

8
packages/nc-gui/components/project/spreadsheet/components/virtualCell/BelongsToCell.vue

@ -109,7 +109,7 @@
<script>
// import ApiFactory from '@/components/project/spreadsheet/apis/apiFactory'
import { RelationTypes, UITypes } from 'nocodb-sdk'
import { isSystemColumn, RelationTypes, UITypes } from 'nocodb-sdk'
import ListItems from '~/components/project/spreadsheet/components/virtualCell/components/ListItems'
import ListChildItems from '~/components/project/spreadsheet/components/virtualCell/components/ListChildItems'
import ItemChip from '~/components/project/spreadsheet/components/virtualCell/components/ItemChip'
@ -194,17 +194,13 @@ export default {
}
},
parentAvailableColumns() {
const hideCols = ['created_at', 'updated_at']
if (!this.parentMeta) {
return []
}
const columns = []
if (this.parentMeta.columns) {
columns.push(...this.parentMeta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.column_name) && !((this.parentMeta.v || []).some(v => v.bt && v.bt.column_name === c.column_name))))
}
if (this.parentMeta.v) {
columns.push(...this.parentMeta.v.map(v => ({ ...v, virtual: 1 })))
columns.push(...this.parentMeta.columns.filter(c => !isSystemColumn(c)))
}
return columns
},

4
packages/nc-gui/components/project/spreadsheet/components/virtualCell/HasManyCell.vue

@ -249,16 +249,12 @@ export default {
return this.selectedChild && !this.isPublic ? () => import('~/components/project/spreadsheet/components/ExpandedForm') : 'span'
},
childAvailableColumns() {
// const hideCols = ['created_at', 'updated_at']
if (!this.childMeta) { return [] }
const columns = []
if (this.childMeta.columns) {
columns.push(...this.childMeta.columns.filter(c => !isSystemColumn(c)))
}
if (this.childMeta.v) {
columns.push(...this.childMeta.v.map(v => ({ ...v, virtual: 1 })))
}
return columns
},
childQueryParams() {

8
packages/nc-gui/components/project/spreadsheet/components/virtualCell/ManyToManyCell.vue

@ -126,7 +126,7 @@
</template>
<script>
import { RelationTypes, UITypes } from 'nocodb-sdk'
import { isSystemColumn, RelationTypes, UITypes } from 'nocodb-sdk'
import DlgLabelSubmitCancel from '~/components/utils/DlgLabelSubmitCancel'
import ListItems from '~/components/project/spreadsheet/components/virtualCell/components/ListItems'
import ListChildItems from '~/components/project/spreadsheet/components/virtualCell/components/ListChildItems'
@ -258,15 +258,11 @@ export default {
// }
},
childAvailableColumns() {
const hideCols = ['created_at', 'updated_at']
if (!this.childMeta) { return [] }
const columns = []
if (this.childMeta.columns) {
columns.push(...this.childMeta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.column_name) && !((this.childMeta.v || []).some(v => v.bt && v.bt.column_name === c.column_name))))
}
if (this.childMeta.v) {
columns.push(...this.childMeta.v.map(v => ({ ...v, virtual: 1 })))
columns.push(...this.childMeta.columns.filter(c => !isSystemColumn(c)))
}
return columns
},

13
packages/nc-gui/components/settings/SettingsModal.vue

@ -5,14 +5,21 @@
mdi-cog-outline
</v-icon>
</slot>
<v-dialog v-model="settingsModal" width="90%" overlay-opacity=".9">
<v-dialog
v-model="settingsModal"
width="90%"
overlay-opacity=".9"
transition="dialog-bottom-transition"
>
<v-card
v-if="settingsModal"
width="100%"
min-height="350px"
>
<div class="d-flex">
<div
class="d-flex"
>
<v-navigation-drawer
v-if="settingsModal"
left
permanent
height="90vh"

220
packages/nc-gui/components/utils/DlgTableCreate.vue

@ -28,80 +28,107 @@
class="mt-4 caption nc-table-name"
/>
<!--hint="Table name as saved in database"-->
<v-text-field
v-if="!projectPrefix"
v-model="table.name"
solo
flat
dense
persistent-hint
:rules="[validateDuplicate]"
:hint="$t('msg.info.tableNameInDb')"
class="mt-4 caption nc-table-name-alias"
/>
<div class="d-flex justify-end">
<div class="grey--text caption pointer" @click="isAdvanceOptVisible = !isAdvanceOptVisible">
{{ isAdvanceOptVisible? 'Hide' : 'Show' }} more
<v-icon x-small color="grey">
{{ isAdvanceOptVisible ? 'mdi-minus-circle-outline':'mdi-plus-circle-outline' }}
</v-icon>
</div>
</div>
<div class="nc-table-advanced-options" :class="{active: isAdvanceOptVisible}">
<!--hint="Table name as saved in database"-->
<v-text-field
v-if="!projectPrefix"
v-model="table.name"
solo
flat
dense
persistent-hint
:rules="[validateDuplicate]"
:hint="$t('msg.info.tableNameInDb')"
class="mt-4 caption nc-table-name-alias"
/>
<div class=" mt-5">
<label class="add-default-title grey--text">
<!--Add Default Columns-->
{{ $t('msg.info.addDefaultColumns') }}
</label>
<div class=" mt-5">
<label class="add-default-title grey--text">
<!--Add Default Columns-->
{{ $t('msg.info.addDefaultColumns') }}
</label>
<div class=" d-flex caption justify-space-between">
<v-checkbox
v-model="table.columns"
dense
class="mt-0 "
color="info"
hide-details
value="id"
@click.capture.prevent.stop="()=>{
$toast.info('ID column is required, you can rename this later if required.').goAway(3000);
if(!table.columns.includes('id')){
table.columns.push('id');
}
}"
>
<template #label>
<span class="caption">id</span>
</template>
</v-checkbox>
<v-checkbox
v-model="table.columns"
dense
class="mt-0 "
color="info"
hide-details
value="title"
>
<template #label>
<span class="caption">title</span>
</template>
</v-checkbox>
<v-checkbox
v-model="table.columns"
dense
class="mt-0 "
color="info"
hide-details
value="created_at"
>
<template #label>
<span class="caption">created_at</span>
</template>
</v-checkbox>
<v-checkbox
v-model="table.columns"
dense
class="mt-0 "
color="info"
hide-details
value="updated_at"
>
<template #label>
<span class="caption">updated_at</span>
</template>
</v-checkbox>
<div class=" d-flex caption justify-space-between align-center">
<v-checkbox
key="chk1"
v-model="table.columns"
dense
class="mt-0 "
color="info"
hide-details
value="id"
@click.capture.prevent.stop="()=>{
$toast.info('ID column is required, you can rename this later if required.').goAway(3000);
if(!table.columns.includes('id')){
table.columns.push('id');
}
}"
>
<template #label>
<div>
<span v-if="!isIdToggleAllowed" class="caption" @dblclick="isIdToggleAllowed=true">id</span>
<v-select
v-else
v-model="idType"
style="max-width:100px"
class="caption"
outlined
dense
hide-details
:items="idTypes"
/>
</div>
</template>
</v-checkbox>
<v-checkbox
key="chk2"
v-model="table.columns"
dense
class="mt-0 "
color="info"
hide-details
value="title"
>
<template #label>
<span class="caption">title</span>
</template>
</v-checkbox>
<v-checkbox
key="chk3"
v-model="table.columns"
dense
class="mt-0 "
color="info"
hide-details
value="created_at"
>
<template #label>
<span class="caption">created_at</span>
</template>
</v-checkbox>
<v-checkbox
key="chk4"
v-model="table.columns"
dense
class="mt-0 "
color="info"
hide-details
value="updated_at"
>
<template #label>
<span class="caption">updated_at</span>
</template>
</v-checkbox>
</div>
</div>
</div>
</v-card-text>
@ -116,7 +143,7 @@
:disabled="!(table.name && table.name.length) || !(table.alias && table.alias.length) || !valid"
color="primary"
class="nc-create-table-submit"
@click="$emit('create',table)"
@click="onCreateBtnClick"
>
{{ $t('general.submit') }}
</v-btn>
@ -136,14 +163,20 @@ export default {
props: ['value'],
data() {
return {
isAdvanceOptVisible: false,
table: {
name: '',
columns: ['id',
columns: [
'id',
'title',
'created_at',
'updated_at']
'updated_at'
]
},
valid: false
isIdToggleAllowed: false,
valid: false,
idType: 'AI',
idTypes: [{ value: 'AI', text: 'Auto increment number' }, { value: 'AG', text: 'Auto generated string' }]
}
},
computed: {
@ -157,6 +190,9 @@ export default {
},
projectPrefix() {
return this.$store.getters['project/GtrProjectPrefix']
},
tables() {
return this.$store.state.project.tables || []
}
},
watch: {
@ -164,20 +200,37 @@ export default {
this.$set(this.table, 'name', `${this.projectPrefix || ''}${inflection.underscore(v)}`)
}
},
created() {
this.populateDefaultTitle()
},
mounted() {
setTimeout(() => {
this.$refs.input.$el.querySelector('input').focus()
const el = this.$refs.input.$el
el.querySelector('input').focus()
el.querySelector('input').select()
}, 100)
},
methods: {
populateDefaultTitle() {
let c = 1
while (this.tables.some(t => t.title === `sheet${c}`)) { c++ }
this.$set(this.table, 'alias', `sheet${c}`)
},
validateTableName(v) {
return validateTableName(v, this.$store.getters['project/GtrProjectIsGraphql'])
},
validateDuplicateAlias(v) {
return (this.$store.state.project.tables || []).every(t => t.title !== (v || '')) || 'Duplicate table alias'
return (this.tables || []).every(t => t.title !== (v || '')) || 'Duplicate table alias'
},
validateDuplicate(v) {
return (this.$store.state.project.tables || []).every(t => t.table_name.toLowerCase() !== (v || '').toLowerCase()) || 'Duplicate table name'
return (this.tables || []).every(t => t.table_name.toLowerCase() !== (v || '').toLowerCase()) || 'Duplicate table name'
},
onCreateBtnClick() {
this.$emit('create', {
...this.table,
columns: this.table.columns.map(c => c === 'id' && this.idType === 'AG' ? 'id_ag' : c)
})
}
}
}
@ -198,6 +251,15 @@ export default {
.add-default-title{
font-size: .65rem;
}
.nc-table-advanced-options{
max-height:0;
transition:.3s max-height;
overflow: hidden;
&.active{
max-height:200px
}
}
</style>
<!--
/**

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

@ -30,4 +30,4 @@
"express": "^4.17.1",
"vuedraggable": "^2.24.3"
}
}
}

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

@ -16,6 +16,7 @@ const isSystemColumn = (col) =>
col.column_name === 'created_at' ||
col.column_name === 'updated_at' ||
(col.pk && (col.ai || col.cdf)) ||
(col.pk && col.meta && col.meta.ag) ||
col.system;
export {

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

@ -1,4 +1,5 @@
import UITypes from '../UITypes';
import { IDType } from './index';
const dbTypes = [
'bigint',
@ -1045,18 +1046,26 @@ export class MssqlUi {
}
}
static getDataTypeForUiType(col): {
static getDataTypeForUiType(
col: { uidt: UITypes },
idType?: IDType
): {
readonly dt: string;
readonly [key: string]: any;
} {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
colProp.dt = 'int';
colProp.pk = true;
colProp.un = true;
colProp.ai = true;
colProp.rqd = true;
{
const isAutoIncId = idType === 'AI';
const isAutoGenId = idType === 'AG';
colProp.dt = isAutoGenId ? 'varchar' : 'int';
colProp.pk = true;
colProp.un = isAutoIncId;
colProp.ai = isAutoIncId;
colProp.rqd = true;
colProp.meta = isAutoGenId ? { ag: 'nc' } : undefined;
}
break;
case 'ForeignKey':
colProp.dt = 'varchar';
@ -1177,9 +1186,16 @@ export class MssqlUi {
return colProp;
}
static getDataTypeListForUiType(col) {
static getDataTypeListForUiType(col, idType: IDType) {
switch (col.uidt) {
case 'ID':
if (idType === 'AG') {
return ['char', 'ntext', 'text', 'varchar', 'nvarchar'];
} else if (idType === 'AI') {
return ['int', 'bigint', 'bit', 'smallint', 'tinyint'];
} else {
return dbTypes;
}
case 'ForeignKey':
return dbTypes;

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

@ -1,4 +1,5 @@
import UITypes from '../UITypes';
import { IDType } from './index';
const dbTypes = [
'int',
@ -943,15 +944,20 @@ export class MysqlUi {
}
}
static getDataTypeForUiType(col) {
static getDataTypeForUiType(col: { uidt: UITypes }, idType?: IDType) {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
colProp.dt = 'int';
colProp.pk = true;
colProp.un = true;
colProp.ai = true;
colProp.rqd = true;
{
const isAutoIncId = idType === 'AI';
const isAutoGenId = idType === 'AG';
colProp.dt = isAutoGenId ? 'varchar' : 'int';
colProp.pk = true;
colProp.un = isAutoIncId;
colProp.ai = isAutoIncId;
colProp.rqd = true;
colProp.meta = isAutoGenId ? { ag: 'nc' } : undefined;
}
break;
case 'ForeignKey':
colProp.dt = 'varchar';
@ -1075,9 +1081,16 @@ export class MysqlUi {
return colProp;
}
static getDataTypeListForUiType(col) {
static getDataTypeListForUiType(col, idType: IDType) {
switch (col.uidt) {
case 'ID':
if (idType === 'AG') {
return ['varchar', 'char', 'nchar'];
} else if (idType === 'AI') {
return ['int', 'smallint', 'mediumint', 'bigint', 'bit', 'serial'];
} else {
return dbTypes;
}
case 'ForeignKey':
return dbTypes;

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

@ -1,3 +1,6 @@
import UITypes from '../UITypes';
import { IDType } from './index';
export class OracleUi {
static getNewTableColumns(): any[] {
return [
@ -791,15 +794,20 @@ export class OracleUi {
}
}
static getDataTypeForUiType(col) {
static getDataTypeForUiType(col: { uidt: UITypes }, idType?: IDType) {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
colProp.dt = 'integer';
colProp.pk = true;
colProp.un = true;
colProp.ai = true;
colProp.rqd = true;
{
const isAutoIncId = idType === 'AI';
const isAutoGenId = idType === 'AG';
colProp.dt = isAutoGenId ? 'varchar' : 'integer';
colProp.pk = true;
colProp.un = isAutoIncId;
colProp.ai = isAutoIncId;
colProp.rqd = true;
colProp.meta = isAutoGenId ? { ag: 'nc' } : undefined;
}
break;
case 'ForeignKey':
colProp.dt = 'varchar';

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

@ -1,4 +1,5 @@
import UITypes from '../UITypes';
import { IDType } from './index';
const dbTypes = [
'int',
@ -1568,15 +1569,20 @@ export class PgUi {
}
}
static getDataTypeForUiType(col) {
static getDataTypeForUiType(col: { uidt: UITypes }, idType?: IDType) {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
colProp.dt = 'int4';
colProp.pk = true;
colProp.un = true;
colProp.ai = true;
colProp.rqd = true;
{
const isAutoIncId = idType === 'AI';
const isAutoGenId = idType === 'AG';
colProp.dt = isAutoGenId ? 'character varying' : 'int4';
colProp.pk = true;
colProp.un = isAutoIncId;
colProp.ai = isAutoIncId;
colProp.rqd = true;
colProp.meta = isAutoGenId ? { ag: 'nc' } : undefined;
}
break;
case 'ForeignKey':
colProp.dt = 'character varying';
@ -1699,9 +1705,29 @@ export class PgUi {
return colProp;
}
static getDataTypeListForUiType(col) {
static getDataTypeListForUiType(col: { uidt: UITypes }, idType: IDType) {
switch (col.uidt) {
case 'ID':
if (idType === 'AG') {
return ['char', 'character', 'character varying'];
} else if (idType === 'AI') {
return [
'int',
'integer',
'bigint',
'bigserial',
'int2',
'int4',
'int8',
'serial',
'serial2',
'serial8',
'smallint',
'smallserial',
];
} else {
return dbTypes;
}
case 'ForeignKey':
return dbTypes;

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

@ -1,4 +1,5 @@
import UITypes from '../UITypes';
import { IDType } from './index';
const dbTypes = [
'int',
@ -758,15 +759,20 @@ export class SqliteUi {
}
}
static getDataTypeForUiType(col) {
static getDataTypeForUiType(col: { uidt: UITypes }, idType?: IDType) {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
colProp.dt = 'integer';
colProp.pk = true;
colProp.un = true;
colProp.ai = true;
colProp.rqd = true;
{
const isAutoIncId = idType === 'AI';
const isAutoGenId = idType === 'AG';
colProp.dt = isAutoGenId ? 'varchar' : 'integer';
colProp.pk = true;
colProp.un = isAutoIncId;
colProp.ai = isAutoIncId;
colProp.rqd = true;
colProp.meta = isAutoGenId ? { ag: 'nc' } : undefined;
}
break;
case 'ForeignKey':
colProp.dt = 'varchar';
@ -891,12 +897,27 @@ export class SqliteUi {
return colProp;
}
static getDataTypeListForUiType(col) {
static getDataTypeListForUiType(col: { uidt: UITypes }, idType: IDType) {
switch (col.uidt) {
case 'ID':
if (idType === 'AG') {
return ['character', 'text', 'varchar'];
} else if (idType === 'AI') {
return [
'int',
'integer',
'tinyint',
'smallint',
'mediumint',
'bigint',
'int2',
'int8',
];
} else {
return dbTypes;
}
case 'ForeignKey':
return dbTypes;
case 'SingleLineText':
case 'LongText':
case 'Attachment':

2
packages/nocodb-sdk/src/lib/sqlUi/index.ts

@ -1,5 +1,5 @@
// todo: move to a common library
export type IDType = 'AG' | 'AI';
export * from './MysqlUi';
export * from './PgUi';
export * from './MssqlUi';

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

@ -39,9 +39,22 @@ import {
} from '../../../noco/meta/helpers/webhookHelpers';
import Validator from 'validator';
import { NcError } from '../../../noco/meta/helpers/catchError';
import { customAlphabet } from 'nanoid';
const GROUP_COL = '__nc_group_id';
const nanoidv2 = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 14);
const { v4: uuidv4 } = require('uuid');
async function populatePk(model: Model, insertObj: any) {
await model.getColumns();
for (const pkCol of model.primaryKeys) {
if (!pkCol.meta?.ag || insertObj[pkCol.title]) continue;
insertObj[pkCol.title] =
pkCol.meta?.ag === 'nc' ? `rc_${nanoidv2()}` : uuidv4();
}
}
/**
* Base class for models
*
@ -181,11 +194,6 @@ class BaseModelSqlv2 {
: await Sort.list({ viewId: this.viewId });
await sortV2(sorts, qb, this.dbDriver);
// sort by primary key by default
if (!sorts?.length && this.model.primaryKey) {
qb.orderBy(this.model.primaryKey.column_name);
}
} else {
await conditionV2(
[
@ -208,12 +216,16 @@ class BaseModelSqlv2 {
if (!sorts) sorts = args.sortArr;
await sortV2(sorts, qb, this.dbDriver);
}
// sort by primary key by default
if (this.model.primaryKey) {
qb.orderBy(this.model.primaryKey.column_name);
}
// sort by primary key if not autogenerated string
// if autogenerated string sort by created_at column if present
if (this.model.primaryKey && this.model.primaryKey.ai) {
qb.orderBy(this.model.primaryKey.column_name);
} else if (this.model.columns.find(c => c.column_name === 'created_at')) {
qb.orderBy('created_at');
}
if (!ignoreFilterSort) applyPaginate(qb, rest);
const proto = await this.getProto();
@ -1170,7 +1182,10 @@ class BaseModelSqlv2 {
);
// todo: verify syntax of as ? / ??
qb.select(
this.dbDriver.raw(`?? as ??`, [selectQb.builder, column.title])
this.dbDriver.raw(`?? as ??`, [
selectQb.builder,
sanitize(column.title)
])
);
}
break;
@ -1183,13 +1198,13 @@ class BaseModelSqlv2 {
// column,
columnOptions: (await column.getColOptions()) as RollupColumn
})
).builder.as(column.title)
).builder.as(sanitize(column.title))
);
break;
default:
res[
(column.title || column.column_name).replace(/\?/g, '\\?')
] = `${this.model.table_name}.${column.column_name}`;
res[sanitize(column.title || column.column_name)] = sanitize(
`${this.model.table_name}.${column.column_name}`
);
break;
}
}
@ -1198,6 +1213,8 @@ class BaseModelSqlv2 {
async insert(data, trx?, cookie?) {
try {
await populatePk(this.model, data);
// todo: filter based on view
const insertObj = await this.model.mapAliasToColumn(data);
@ -1335,6 +1352,7 @@ class BaseModelSqlv2 {
async nestedInsert(data, _trx = null, cookie?) {
// const driver = trx ? trx : await this.dbDriver.transaction();
try {
await populatePk(this.model, data);
const insertObj = await this.model.mapAliasToColumn(data);
let rowId = null;
@ -1469,7 +1487,10 @@ class BaseModelSqlv2 {
async bulkInsert(datas: any[]) {
try {
const insertDatas = await Promise.all(
datas.map(d => this.model.mapAliasToColumn(d))
datas.map(async d => {
await populatePk(this.model, d);
return this.model.mapAliasToColumn(d);
})
);
// await this.beforeInsertb(insertDatas, null);
@ -2085,6 +2106,10 @@ function getCompositePk(primaryKeys: Column[], row) {
return primaryKeys.map(c => row[c.title]).join('___');
}
function sanitize(v) {
return v?.replace(/\?/g, '\\?');
}
export { BaseModelSqlv2 };
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd

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

@ -374,16 +374,22 @@ export default async (
{
title: '_aTbl_nc_rec_id',
column_name: '_aTbl_nc_rec_id',
// uidt: UITypes.ID
uidt: UITypes.SingleLineText,
pk: true,
// mysql additionally requires NOT-NULL to be explicitly set
rqd: true
uidt: UITypes.ID,
// idType: 'AG'
// uidt: UITypes.SingleLineText,
// pk: true,
// // mysql additionally requires NOT-NULL to be explicitly set
// rqd: true,
// system: true,
meta: {
ag: 'nc'
}
},
{
title: '_aTbl_nc_rec_hash',
column_name: '_aTbl_nc_rec_hash',
uidt: UITypes.SingleLineText
uidt: UITypes.SingleLineText,
system: true
}
];

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

@ -173,6 +173,7 @@ export async function tableCreate(req: Request<any, any, TableReqType>, res) {
c1 => c.cn === c1.column_name
);
return {
...colMetaFromReq,
uidt: colMetaFromReq?.uidt || c.uidt || getColumnUiType(base, c),
...c,
title: colMetaFromReq?.title || getColumnNameAlias(c.cn, base),

9
packages/nocodb/src/lib/noco/meta/helpers/getColumnPropsFromUIDT.ts

@ -1,13 +1,16 @@
import { SqlUIColumn, SqlUiFactory, UITypes } from 'nocodb-sdk';
import { ColumnReqType, SqlUIColumn, SqlUiFactory, UITypes } from 'nocodb-sdk';
import Base from '../../../noco-models/Base';
export default function getColumnPropsFromUIDT(
column: SqlUIColumn & { uidt: UITypes },
column: SqlUIColumn & { uidt: UITypes } & ColumnReqType,
base: Base
) {
const sqlUi = SqlUiFactory.create(base.getConnectionConfig());
const colProp = sqlUi.getDataTypeForUiType(column);
const colProp = sqlUi.getDataTypeForUiType(
column,
column?.['meta']?.['ag'] ? 'AG' : 'AI'
);
const newColumn = {
rqd: false,
pk: false,

Loading…
Cancel
Save