Browse Source

Merge pull request #9660 from nocodb/nc-fix/api-corrections

Nc fix/api corrections
pull/9665/head
Pranav C 1 month ago committed by GitHub
parent
commit
004050f051
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      packages/nc-gui/components/cell/Checkbox.vue
  2. 6
      packages/nc-gui/components/cell/Rating.vue
  3. 32
      packages/nc-gui/components/smartsheet/column/CheckboxOptions.vue
  4. 11
      packages/nc-gui/components/smartsheet/column/RatingOptions.vue
  5. 23
      packages/nc-gui/helpers/columnDefaultMeta.ts
  6. 95
      packages/nc-gui/utils/columnUtils.ts
  7. 3
      packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.controller.ts
  8. 8
      packages/nocodb/src/schema/swagger-v2.json
  9. 11
      packages/nocodb/src/schema/swagger.json
  10. 12
      packages/nocodb/src/services/columns.service.ts
  11. 48
      packages/nocodb/src/services/tables.service.ts
  12. 5
      packages/nocodb/tests/unit/rest/tests/table.test.ts

7
packages/nc-gui/components/cell/Checkbox.vue

@ -38,13 +38,12 @@ const isSurveyForm = inject(IsSurveyFormInj, ref(false))
const isGrid = inject(IsGridInj, ref(false)) const isGrid = inject(IsGridInj, ref(false))
const checkboxMeta = computed(() => { const checkboxMeta = computed(() => {
const icon = extractCheckboxIcon(column?.value?.meta)
return { return {
icon: {
checked: 'mdi-check-circle-outline',
unchecked: 'mdi-checkbox-blank-circle-outline',
},
color: 'primary', color: 'primary',
...parseProp(column?.value?.meta), ...parseProp(column?.value?.meta),
icon,
} }
}) })

6
packages/nc-gui/components/cell/Rating.vue

@ -16,14 +16,12 @@ const rowHeight = inject(RowHeightInj, ref(undefined))
const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const ratingMeta = computed(() => { const ratingMeta = computed(() => {
const icon = extractRatingIcon(column?.value?.meta)
return { return {
icon: {
full: 'mdi-star',
empty: 'mdi-star-outline',
},
color: '#fcb401', color: '#fcb401',
max: 5, max: 5,
...parseProp(column.value?.meta), ...parseProp(column.value?.meta),
icon,
} }
}) })

32
packages/nc-gui/components/smartsheet/column/CheckboxOptions.vue

@ -10,36 +10,7 @@ const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit) const vModel = useVModel(props, 'value', emit)
// cater existing v1 cases // cater existing v1 cases
const iconList = [ const iconList = checkboxIconList
{
checked: 'mdi-check-bold',
unchecked: 'mdi-crop-square',
},
{
checked: 'mdi-check-circle-outline',
unchecked: 'mdi-checkbox-blank-circle-outline',
},
{
checked: 'mdi-star',
unchecked: 'mdi-star-outline',
},
{
checked: 'mdi-heart',
unchecked: 'mdi-heart-outline',
},
{
checked: 'mdi-moon-full',
unchecked: 'mdi-moon-new',
},
{
checked: 'mdi-thumb-up',
unchecked: 'mdi-thumb-up-outline',
},
{
checked: 'mdi-flag',
unchecked: 'mdi-flag-outline',
},
]
const picked = computed({ const picked = computed({
get: () => vModel.value.meta.color, get: () => vModel.value.meta.color,
@ -54,6 +25,7 @@ const isOpenColorPicker = ref(false)
vModel.value.meta = { vModel.value.meta = {
...columnDefaultMeta[UITypes.Checkbox], ...columnDefaultMeta[UITypes.Checkbox],
...(vModel.value.meta || {}), ...(vModel.value.meta || {}),
icon: extractCheckboxIcon(vModel.value.meta || {}),
} }
// antdv doesn't support object as value // antdv doesn't support object as value

11
packages/nc-gui/components/smartsheet/column/RatingOptions.vue

@ -22,11 +22,12 @@ const isOpenColorPicker = ref(false)
vModel.value.meta = { vModel.value.meta = {
...columnDefaultMeta[UITypes.Rating], ...columnDefaultMeta[UITypes.Rating],
...(vModel.value.meta || {}), ...(vModel.value.meta || {}),
icon: extractRatingIcon(vModel.value.meta || {}),
} }
// antdv doesn't support object as value // antdv doesn't support object as value
// use iconIdx as value and update back in watch // use iconIdx as value and update back in watch
const iconIdx = iconList.findIndex( const iconIdx = ratingIconList.findIndex(
(ele) => ele.full === vModel.value.meta.icon.full && ele.empty === vModel.value.meta.icon.empty, (ele) => ele.full === vModel.value.meta.icon.full && ele.empty === vModel.value.meta.icon.empty,
) )
@ -35,7 +36,7 @@ vModel.value.meta.iconIdx = iconIdx === -1 ? 0 : iconIdx
watch( watch(
() => vModel.value.meta.iconIdx, () => vModel.value.meta.iconIdx,
(v) => { (v) => {
vModel.value.meta.icon = iconList[v] vModel.value.meta.icon = ratingIconList[v]
}, },
) )
</script> </script>
@ -49,7 +50,7 @@ watch(
<GeneralIcon icon="arrowDown" class="text-gray-700" /> <GeneralIcon icon="arrowDown" class="text-gray-700" />
</template> </template>
<a-select-option v-for="(icon, i) of iconList" :key="i" :value="i"> <a-select-option v-for="(icon, i) of ratingIconList" :key="i" :value="i">
<div class="flex gap-2 w-full truncate items-center"> <div class="flex gap-2 w-full truncate items-center">
<div class="flex-1 flex items-center text-gray-700 gap-2 children:(h-4 w-4)"> <div class="flex-1 flex items-center text-gray-700 gap-2 children:(h-4 w-4)">
<component :is="getMdiIcon(icon.full)" /> <component :is="getMdiIcon(icon.full)" />
@ -84,13 +85,13 @@ watch(
> >
<div class="flex-1 flex items-center gap-2 children:(h-4 w-4)"> <div class="flex-1 flex items-center gap-2 children:(h-4 w-4)">
<component <component
:is="getMdiIcon(iconList[vModel.meta.iconIdx].full)" :is="getMdiIcon(ratingIconList[vModel.meta.iconIdx].full)"
:style="{ :style="{
color: vModel.meta.color, color: vModel.meta.color,
}" }"
/> />
<component <component
:is="getMdiIcon(iconList[vModel.meta.iconIdx].empty)" :is="getMdiIcon(ratingIconList[vModel.meta.iconIdx].empty)"
:style="{ :style="{
color: vModel.meta.color, color: vModel.meta.color,
}" }"

23
packages/nc-gui/helpers/columnDefaultMeta.ts

@ -2,29 +2,6 @@ import { UITypes, dateFormats, timeFormats } from 'nocodb-sdk'
export const precisionFormats = [1, 2, 3, 4, 5, 6, 7, 8] export const precisionFormats = [1, 2, 3, 4, 5, 6, 7, 8]
export const iconList = [
{
full: 'mdi-star',
empty: 'mdi-star-outline',
},
{
full: 'mdi-heart',
empty: 'mdi-heart-outline',
},
{
full: 'mdi-moon-full',
empty: 'mdi-moon-new',
},
{
full: 'mdi-thumb-up',
empty: 'mdi-thumb-up-outline',
},
{
full: 'mdi-flag',
empty: 'mdi-flag-outline',
},
]
export const supportedBarcodeFormats = [ export const supportedBarcodeFormats = [
{ value: 'CODE128', label: 'CODE128' }, { value: 'CODE128', label: 'CODE128' },
{ value: 'upc', label: 'UPC' }, { value: 'upc', label: 'UPC' },

95
packages/nc-gui/utils/columnUtils.ts

@ -257,6 +257,97 @@ const isColumnInvalid = (col: ColumnType) => {
} }
} }
// cater existing v1 cases
const checkboxIconList = [
{
checked: 'mdi-check-bold',
unchecked: 'mdi-crop-square',
},
{
checked: 'mdi-check-circle-outline',
unchecked: 'mdi-checkbox-blank-circle-outline',
},
{
checked: 'mdi-star',
unchecked: 'mdi-star-outline',
},
{
checked: 'mdi-heart',
unchecked: 'mdi-heart-outline',
},
{
checked: 'mdi-moon-full',
unchecked: 'mdi-moon-new',
},
{
checked: 'mdi-thumb-up',
unchecked: 'mdi-thumb-up-outline',
},
{
checked: 'mdi-flag',
unchecked: 'mdi-flag-outline',
},
]
const ratingIconList = [
{
full: 'mdi-star',
empty: 'mdi-star-outline',
},
{
full: 'mdi-heart',
empty: 'mdi-heart-outline',
},
{
full: 'mdi-moon-full',
empty: 'mdi-moon-new',
},
{
full: 'mdi-thumb-up',
empty: 'mdi-thumb-up-outline',
},
{
full: 'mdi-flag',
empty: 'mdi-flag-outline',
},
]
function extractCheckboxIcon(meta: string | Record<string, any> = null) {
const parsedMeta = parseProp(meta)
const icon = {
checked: 'mdi-check-circle-outline',
unchecked: 'mdi-checkbox-blank-circle-outline',
}
if (parsedMeta.icon) {
icon.checked = parsedMeta.icon.checked || icon.checked
icon.unchecked = parsedMeta.icon.unchecked || icon.unchecked
} else if (typeof parsedMeta.iconIdx === 'number' && checkboxIconList[parsedMeta.iconIdx]) {
icon.checked = checkboxIconList[parsedMeta.iconIdx].checked
icon.unchecked = checkboxIconList[parsedMeta.iconIdx].unchecked
}
return icon
}
function extractRatingIcon(meta: string | Record<string, any> = null) {
const parsedMeta = parseProp(meta)
const icon = {
full: 'mdi-star',
empty: 'mdi-star-outline',
}
if (parsedMeta.icon) {
icon.full = parsedMeta.icon.full || icon.full
icon.empty = parsedMeta.icon.empty || icon.empty
} else if (typeof parsedMeta.iconIdx === 'number' && ratingIconList[parsedMeta.iconIdx]) {
icon.full = ratingIconList[parsedMeta.iconIdx].full
icon.empty = ratingIconList[parsedMeta.iconIdx].empty
}
return icon
}
export { export {
uiTypes, uiTypes,
isTypableInputColumn, isTypableInputColumn,
@ -267,4 +358,8 @@ export {
isColumnRequiredAndNull, isColumnRequiredAndNull,
isColumnRequired, isColumnRequired,
isVirtualColRequired, isVirtualColRequired,
checkboxIconList,
ratingIconList,
extractCheckboxIcon,
extractRatingIcon,
} }

3
packages/nocodb/src/modules/jobs/jobs/export-import/duplicate.controller.ts

@ -194,6 +194,7 @@ export class DuplicateController {
@Param('modelId') modelId?: string, @Param('modelId') modelId?: string,
@Body() @Body()
body?: { body?: {
title?: string;
options?: { options?: {
excludeData?: boolean; excludeData?: boolean;
excludeViews?: boolean; excludeViews?: boolean;
@ -226,7 +227,7 @@ export class DuplicateController {
const models = await source.getModels(context); const models = await source.getModels(context);
const uniqueTitle = generateUniqueName( const uniqueTitle = generateUniqueName(
`${model.title} copy`, body.title || `${model.title} copy`,
models.map((p) => p.title), models.map((p) => p.title),
); );

8
packages/nocodb/src/schema/swagger-v2.json

@ -3883,6 +3883,11 @@
"excludeHooks": { "excludeHooks": {
"type": "boolean", "type": "boolean",
"required": false "required": false
},
"title": {
"type": "string",
"required": false,
"description": "New table title"
} }
} }
} }
@ -20568,7 +20573,6 @@
} }
}, },
"required": [ "required": [
"table_name",
"title" "title"
], ],
"x-stoplight": { "x-stoplight": {
@ -20839,7 +20843,7 @@
}, },
"required": [ "required": [
"columns", "columns",
"table_name" "title"
], ],
"title": "Table Request Model", "title": "Table Request Model",
"type": "object", "type": "object",

11
packages/nocodb/src/schema/swagger.json

@ -4489,6 +4489,11 @@
"excludeHooks": { "excludeHooks": {
"type": "boolean", "type": "boolean",
"required": false "required": false
},
"title": {
"type": "string",
"required": false,
"description": "New table title"
} }
} }
} }
@ -23693,7 +23698,7 @@
"title": "Normal Column Request Model", "title": "Normal Column Request Model",
"type": "object", "type": "object",
"required": [ "required": [
"column_name" "title"
], ],
"x-stoplight": { "x-stoplight": {
"id": "fn3gqmojvswv2" "id": "fn3gqmojvswv2"
@ -25625,7 +25630,7 @@
} }
}, },
"required": [ "required": [
"table_name", "title",
"title" "title"
], ],
"x-stoplight": { "x-stoplight": {
@ -25905,7 +25910,7 @@
}, },
"required": [ "required": [
"columns", "columns",
"table_name" "title"
], ],
"title": "Table Request Model", "title": "Table Request Model",
"type": "object", "type": "object",

12
packages/nocodb/src/services/columns.service.ts

@ -16,6 +16,7 @@ import {
} from 'nocodb-sdk'; } from 'nocodb-sdk';
import { pluralize, singularize } from 'inflection'; import { pluralize, singularize } from 'inflection';
import hash from 'object-hash'; import hash from 'object-hash';
import { parseMetaProp } from 'src/utils/modelUtils';
import type { import type {
ColumnReqType, ColumnReqType,
LinkToAnotherColumnReqType, LinkToAnotherColumnReqType,
@ -65,7 +66,6 @@ import Noco from '~/Noco';
import NcConnectionMgrv2 from '~/utils/common/NcConnectionMgrv2'; import NcConnectionMgrv2 from '~/utils/common/NcConnectionMgrv2';
import { MetaTable } from '~/utils/globals'; import { MetaTable } from '~/utils/globals';
import { MetaService } from '~/meta/meta.service'; import { MetaService } from '~/meta/meta.service';
import { parseMetaProp } from 'src/utils/modelUtils';
// todo: move // todo: move
export enum Altered { export enum Altered {
@ -1627,6 +1627,11 @@ export class ColumnsService {
reuse?: ReusableParams; reuse?: ReusableParams;
}, },
) { ) {
// if column_name is defined and title is not defined, set title to column_name
if (param.column.column_name && !param.column.title) {
param.column.title = param.column.column_name;
}
validatePayload('swagger.json#/components/schemas/ColumnReq', param.column); validatePayload('swagger.json#/components/schemas/ColumnReq', param.column);
const reuse = param.reuse || {}; const reuse = param.reuse || {};
@ -1674,6 +1679,11 @@ export class ColumnsService {
param.column.title = param.column.title.trim(); param.column.title = param.column.title.trim();
} }
// if column_name missing then generate it from title
if (!param.column.column_name) {
param.column.column_name = param.column.title;
}
if (param.column.column_name) { if (param.column.column_name) {
// - 5 is a buffer for suffix // - 5 is a buffer for suffix
let colName = param.column.column_name.slice(0, mxColumnLength - 5); let colName = param.column.column_name.slice(0, mxColumnLength - 5);

48
packages/nocodb/src/services/tables.service.ts

@ -466,6 +466,20 @@ export class TablesService {
req?: any; req?: any;
}, },
) { ) {
// before validating add title for columns if only column name is present
if (param.table.columns) {
param.table.columns.forEach((c) => {
if (!c.title && c.column_name) {
c.title = c.column_name;
}
});
}
// before validating add title for table if only table name is present
if (!param.table.title && param.table.table_name) {
param.table.title = param.table.table_name
}
validatePayload('swagger.json#/components/schemas/TableReq', param.table); validatePayload('swagger.json#/components/schemas/TableReq', param.table);
const tableCreatePayLoad: Omit<TableReqType, 'columns'> & { const tableCreatePayLoad: Omit<TableReqType, 'columns'> & {
@ -558,13 +572,22 @@ export class TablesService {
} }
} }
if (!tableCreatePayLoad.title) {
NcError.badRequest('Missing table `title` property in request body');
}
if (!tableCreatePayLoad.table_name) {
tableCreatePayLoad.table_name = tableCreatePayLoad.title;
}
if ( if (
!tableCreatePayLoad.table_name || !(await Model.checkAliasAvailable(context, {
(base.prefix && base.prefix === tableCreatePayLoad.table_name) title: tableCreatePayLoad.title,
base_id: base.id,
source_id: source.id,
}))
) { ) {
NcError.badRequest( NcError.badRequest('Duplicate table alias');
'Missing table name `table_name` property in request body',
);
} }
if (source.type === 'databricks') { if (source.type === 'databricks') {
@ -608,16 +631,6 @@ export class TablesService {
); );
} }
if (
!(await Model.checkAliasAvailable(context, {
title: tableCreatePayLoad.title,
base_id: base.id,
source_id: source.id,
}))
) {
NcError.badRequest('Duplicate table alias');
}
const sqlMgr = await ProjectMgrv2.getSqlMgr(context, base); const sqlMgr = await ProjectMgrv2.getSqlMgr(context, base);
const sqlClient = await NcConnectionMgrv2.getSqlClient(source); const sqlClient = await NcConnectionMgrv2.getSqlClient(source);
@ -652,6 +665,11 @@ export class TablesService {
) { ) {
const mxColumnLength = Column.getMaxColumnNameLength(sqlClientType); const mxColumnLength = Column.getMaxColumnNameLength(sqlClientType);
// set column name using title if not present
if (!column.column_name && column.title) {
column.column_name = column.title;
}
// - 5 is a buffer for suffix // - 5 is a buffer for suffix
column.column_name = sanitizeColumnName( column.column_name = sanitizeColumnName(
column.column_name.slice(0, mxColumnLength - 5), column.column_name.slice(0, mxColumnLength - 5),

5
packages/nocodb/tests/unit/rest/tests/table.test.ts

@ -48,13 +48,12 @@ function tableStaticTests() {
expect(response.body.list).to.be.an('array').not.empty; expect(response.body.list).to.be.an('array').not.empty;
}); });
it('Create table with no table name', async function () { it('Create table with no table title', async function () {
const response = await request(context.app) const response = await request(context.app)
.post(`/api/v1/db/meta/projects/${base.id}/tables`) .post(`/api/v1/db/meta/projects/${base.id}/tables`)
.set('xc-auth', context.token) .set('xc-auth', context.token)
.send({ .send({
table_name: undefined, title: undefined,
title: 'new_title',
columns: defaultColumns(context), columns: defaultColumns(context),
}) })
.expect(400); .expect(400);

Loading…
Cancel
Save