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 checkboxMeta = computed(() => {
const icon = extractCheckboxIcon(column?.value?.meta)
return {
icon: {
checked: 'mdi-check-circle-outline',
unchecked: 'mdi-checkbox-blank-circle-outline',
},
color: 'primary',
...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 ratingMeta = computed(() => {
const icon = extractRatingIcon(column?.value?.meta)
return {
icon: {
full: 'mdi-star',
empty: 'mdi-star-outline',
},
color: '#fcb401',
max: 5,
...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)
// cater existing v1 cases
const iconList = [
{
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 iconList = checkboxIconList
const picked = computed({
get: () => vModel.value.meta.color,
@ -54,6 +25,7 @@ const isOpenColorPicker = ref(false)
vModel.value.meta = {
...columnDefaultMeta[UITypes.Checkbox],
...(vModel.value.meta || {}),
icon: extractCheckboxIcon(vModel.value.meta || {}),
}
// 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 = {
...columnDefaultMeta[UITypes.Rating],
...(vModel.value.meta || {}),
icon: extractRatingIcon(vModel.value.meta || {}),
}
// antdv doesn't support object as value
// 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,
)
@ -35,7 +36,7 @@ vModel.value.meta.iconIdx = iconIdx === -1 ? 0 : iconIdx
watch(
() => vModel.value.meta.iconIdx,
(v) => {
vModel.value.meta.icon = iconList[v]
vModel.value.meta.icon = ratingIconList[v]
},
)
</script>
@ -49,7 +50,7 @@ watch(
<GeneralIcon icon="arrowDown" class="text-gray-700" />
</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-1 flex items-center text-gray-700 gap-2 children:(h-4 w-4)">
<component :is="getMdiIcon(icon.full)" />
@ -84,13 +85,13 @@ watch(
>
<div class="flex-1 flex items-center gap-2 children:(h-4 w-4)">
<component
:is="getMdiIcon(iconList[vModel.meta.iconIdx].full)"
:is="getMdiIcon(ratingIconList[vModel.meta.iconIdx].full)"
:style="{
color: vModel.meta.color,
}"
/>
<component
:is="getMdiIcon(iconList[vModel.meta.iconIdx].empty)"
:is="getMdiIcon(ratingIconList[vModel.meta.iconIdx].empty)"
:style="{
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 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 = [
{ value: 'CODE128', label: 'CODE128' },
{ 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 {
uiTypes,
isTypableInputColumn,
@ -267,4 +358,8 @@ export {
isColumnRequiredAndNull,
isColumnRequired,
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,
@Body()
body?: {
title?: string;
options?: {
excludeData?: boolean;
excludeViews?: boolean;
@ -226,7 +227,7 @@ export class DuplicateController {
const models = await source.getModels(context);
const uniqueTitle = generateUniqueName(
`${model.title} copy`,
body.title || `${model.title} copy`,
models.map((p) => p.title),
);

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

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

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

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

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

@ -16,6 +16,7 @@ import {
} from 'nocodb-sdk';
import { pluralize, singularize } from 'inflection';
import hash from 'object-hash';
import { parseMetaProp } from 'src/utils/modelUtils';
import type {
ColumnReqType,
LinkToAnotherColumnReqType,
@ -65,7 +66,6 @@ import Noco from '~/Noco';
import NcConnectionMgrv2 from '~/utils/common/NcConnectionMgrv2';
import { MetaTable } from '~/utils/globals';
import { MetaService } from '~/meta/meta.service';
import { parseMetaProp } from 'src/utils/modelUtils';
// todo: move
export enum Altered {
@ -1627,6 +1627,11 @@ export class ColumnsService {
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);
const reuse = param.reuse || {};
@ -1674,6 +1679,11 @@ export class ColumnsService {
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) {
// - 5 is a buffer for suffix
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;
},
) {
// 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);
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 (
!tableCreatePayLoad.table_name ||
(base.prefix && base.prefix === tableCreatePayLoad.table_name)
!(await Model.checkAliasAvailable(context, {
title: tableCreatePayLoad.title,
base_id: base.id,
source_id: source.id,
}))
) {
NcError.badRequest(
'Missing table name `table_name` property in request body',
);
NcError.badRequest('Duplicate table alias');
}
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 sqlClient = await NcConnectionMgrv2.getSqlClient(source);
@ -652,6 +665,11 @@ export class TablesService {
) {
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
column.column_name = sanitizeColumnName(
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;
});
it('Create table with no table name', async function () {
it('Create table with no table title', async function () {
const response = await request(context.app)
.post(`/api/v1/db/meta/projects/${base.id}/tables`)
.set('xc-auth', context.token)
.send({
table_name: undefined,
title: 'new_title',
title: undefined,
columns: defaultColumns(context),
})
.expect(400);

Loading…
Cancel
Save