Browse Source

Merge pull request #5307 from nocodb/fix/project-errors

fix: project errors
pull/5322/head
աɨռɢӄաօռɢ 2 years ago committed by GitHub
parent
commit
aedbfaaa4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      packages/nc-gui/components/account/License.vue
  2. 4
      packages/nc-gui/components/account/SignupSettings.vue
  3. 14
      packages/nc-gui/components/account/Token.vue
  4. 12
      packages/nc-gui/components/account/UserList.vue
  5. 13
      packages/nc-gui/components/account/UsersModal.vue
  6. 13
      packages/nc-gui/components/cell/Checkbox.vue
  7. 4
      packages/nc-gui/components/cell/Currency.vue
  8. 3
      packages/nc-gui/components/cell/DatePicker.vue
  9. 5
      packages/nc-gui/components/cell/DateTimePicker.vue
  10. 7
      packages/nc-gui/components/cell/Decimal.vue
  11. 3
      packages/nc-gui/components/cell/Duration.vue
  12. 9
      packages/nc-gui/components/cell/Float.vue
  13. 2
      packages/nc-gui/components/cell/GeoData.vue
  14. 9
      packages/nc-gui/components/cell/Integer.vue
  15. 3
      packages/nc-gui/components/cell/MultiSelect.vue
  16. 4
      packages/nc-gui/components/cell/Rating.vue
  17. 8
      packages/nc-gui/components/cell/Url.vue
  18. 3
      packages/nc-gui/components/cell/attachment/utils.ts
  19. 5
      packages/nc-gui/components/dashboard/TreeView.vue
  20. 4
      packages/nc-gui/components/dashboard/settings/AuditTab.vue
  21. 15
      packages/nc-gui/components/dashboard/settings/app-store/AppInstall.vue
  22. 2
      packages/nc-gui/components/dlg/TableRename.vue
  23. 4
      packages/nc-gui/components/dlg/ViewCreate.vue
  24. 4
      packages/nc-gui/components/erd/View.vue
  25. 2
      packages/nc-gui/components/general/TableIcon.vue
  26. 2
      packages/nc-gui/components/smartsheet/ApiSnippet.vue
  27. 2
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  28. 2
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  29. 11
      packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts
  30. 5
      packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue
  31. 9
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  32. 7
      packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue
  33. 4
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  34. 5
      packages/nc-gui/components/smartsheet/toolbar/SharedViewList.vue
  35. 6
      packages/nc-gui/components/tabs/auth/UserManagement.vue
  36. 4
      packages/nc-gui/components/tabs/auth/user-management/ShareBase.vue
  37. 12
      packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue
  38. 2
      packages/nc-gui/components/virtual-cell/barcode/Barcode.vue
  39. 26
      packages/nc-gui/components/webhook/Editor.vue
  40. 4
      packages/nc-gui/components/webhook/List.vue
  41. 10
      packages/nc-gui/components/webhook/Test.vue
  42. 8
      packages/nc-gui/composables/useGlobal/actions.ts
  43. 4
      packages/nc-gui/composables/useKanbanViewStore.ts
  44. 6
      packages/nc-gui/composables/useMapViewDataStore.ts
  45. 4
      packages/nc-gui/composables/useMetas.ts
  46. 5
      packages/nc-gui/composables/useMultiSelect/convertCellData.ts
  47. 4
      packages/nc-gui/composables/useSharedView.ts
  48. 6
      packages/nc-gui/composables/useTable.ts
  49. 4
      packages/nc-gui/composables/useViewFilters.ts
  50. 2
      packages/nc-gui/context/index.ts
  51. 6
      packages/nc-gui/lib/types.ts
  52. 4
      packages/nc-gui/middleware/auth.global.ts
  53. 31
      packages/nc-gui/package-lock.json
  54. 1
      packages/nc-gui/package.json
  55. 2
      packages/nc-gui/pages/[projectType]/[projectId]/index.vue
  56. 5
      packages/nc-gui/pages/index/index/create-external.vue
  57. 5
      packages/nc-gui/pages/index/index/create.vue
  58. 7
      packages/nc-gui/pages/index/index/index.vue
  59. 4
      packages/nc-gui/store/project.ts
  60. 4
      packages/nc-gui/utils/dataUtils.ts
  61. 1
      packages/nc-gui/utils/index.ts
  62. 17
      packages/nc-gui/utils/parseUtils.ts
  63. 6
      packages/nc-gui/utils/virtualCell.ts
  64. 60
      packages/nocodb-sdk/src/lib/Api.ts
  65. 9
      packages/nocodb/src/lib/services/project.svc.ts
  66. 156
      packages/nocodb/src/schema/swagger.json

9
packages/nc-gui/components/account/License.vue

@ -14,9 +14,8 @@ let key = $ref('')
const loadLicense = async () => {
try {
const response = await api.orgLicense.get()
key = response.key
} catch (e) {
key = response.key!
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
@ -25,7 +24,7 @@ const setLicense = async () => {
await api.orgLicense.set({ key: key })
message.success('License key updated')
await loadAppInfo()
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
$e('a:account:license')
@ -45,5 +44,3 @@ loadLicense()
</div>
</div>
</template>
<style scoped></style>

4
packages/nc-gui/components/account/SignupSettings.vue

@ -12,7 +12,7 @@ const loadSettings = async () => {
try {
const response = await api.orgAppSettings.get()
settings = response
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
@ -21,7 +21,7 @@ const saveSettings = async () => {
try {
await api.orgAppSettings.set(settings)
message.success(t('msg.success.settingsSaved'))
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}

14
packages/nc-gui/components/account/Token.vue

@ -1,4 +1,5 @@
<script lang="ts" setup>
import type { VNodeRef } from '@vue/runtime-core'
import { Empty, Modal, message } from 'ant-design-vue'
import type { ApiTokenType, RequestParams, UserType } from 'nocodb-sdk'
import { extractSdkResponseErrorMsg, useApi, useCopy, useNuxtApp } from '#imports'
@ -42,7 +43,7 @@ const loadTokens = async (page = currentPage, limit = currentLimit) => {
pagination.pageSize = 10
tokens = response.list as UserType[]
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
@ -55,11 +56,10 @@ const deleteToken = async (token: string) => {
type: 'warn',
onOk: async () => {
try {
// todo: delete token
await api.orgTokens.delete(token)
message.success(t('msg.success.tokenDeleted'))
await loadTokens()
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
$e('a:account:token:delete')
@ -75,7 +75,7 @@ const generateToken = async () => {
message.success(t('msg.success.tokenGenerated'))
selectedTokenData = {}
await loadTokens()
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
$e('a:api-token:generate')
@ -90,14 +90,12 @@ const copyToken = async (token: string | undefined) => {
message.info(t('msg.info.copiedToClipboard'))
$e('c:api-token:copy')
} catch (e) {
} catch (e: any) {
message.error(e.message)
}
}
const descriptionInput = (el) => {
el?.focus()
}
const descriptionInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script>
<template>

12
packages/nc-gui/components/account/UserList.vue

@ -50,7 +50,7 @@ const loadUsers = async (page = currentPage, limit = currentLimit) => {
pagination.pageSize = 10
users = response.list as UserType[]
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
@ -65,7 +65,7 @@ const updateRole = async (userId: string, roles: Role) => {
message.success(t('msg.success.roleUpdated'))
$e('a:org-user:role-updated', { role: roles })
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
@ -80,7 +80,7 @@ const deleteUser = async (userId: string) => {
message.success(t('msg.success.userDeleted'))
await loadUsers()
$e('a:org-user:user-deleted')
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
},
@ -94,7 +94,7 @@ const resendInvite = async (user: User) => {
// Invite email sent successfully
message.success(t('msg.success.inviteEmailSent'))
await loadUsers()
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@ -108,7 +108,7 @@ const copyInviteUrl = async (user: User) => {
// Invite URL copied to clipboard
message.success(t('msg.success.inviteURLCopied'))
} catch (e) {
} catch (e: any) {
message.error(e.message)
}
$e('c:user:copy-url')
@ -123,7 +123,7 @@ const copyPasswordResetUrl = async (user: User) => {
// Invite URL copied to clipboard
message.success(t('msg.success.passwordResetURLCopied'))
$e('c:user:copy-url')
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}

13
packages/nc-gui/components/account/UsersModal.vue

@ -1,5 +1,6 @@
<script setup lang="ts">
import type { UserType } from 'nocodb-sdk'
import type { VNodeRef } from '@vue/runtime-core'
import type { OrgUserReqType } from 'nocodb-sdk'
import {
Form,
computed,
@ -72,11 +73,10 @@ const saveUser = async () => {
await formRef.value?.validateFields()
try {
// todo: update sdk(swagger.json)
const res = await $api.orgUsers.add({
roles: usersData.role,
email: usersData.emails,
} as unknown as UserType)
} as unknown as OrgUserReqType)
usersData.invitationToken = res.invite_token
emit('reload')
@ -98,7 +98,7 @@ const copyUrl = async () => {
// Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied'))
} catch (e) {
} catch (e: any) {
message.error(e.message)
}
$e('c:shared-base:copy-url')
@ -110,9 +110,8 @@ const clickInviteMore = () => {
usersData.role = Role.OrgLevelViewer
usersData.emails = ''
}
const emailInput = ref((el) => {
el?.focus()
})
const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script>
<template>

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

@ -1,5 +1,14 @@
<script setup lang="ts">
import { ActiveCellInj, ColumnInj, IsFormInj, ReadonlyInj, getMdiIcon, inject, useSelectedCellKeyupListener } from '#imports'
import {
ActiveCellInj,
ColumnInj,
IsFormInj,
ReadonlyInj,
getMdiIcon,
inject,
parseProp,
useSelectedCellKeyupListener,
} from '#imports'
interface Props {
// If the previous cell value was a text, the initial checkbox value is a string type
@ -35,7 +44,7 @@ const checkboxMeta = $computed(() => {
unchecked: 'mdi-checkbox-blank-circle-outline',
},
color: 'primary',
...(column?.value?.meta || {}),
...parseProp(column?.value?.meta),
}
})

4
packages/nc-gui/components/cell/Currency.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core'
import { ColumnInj, EditModeInj, computed, inject, useVModel } from '#imports'
import { ColumnInj, EditModeInj, computed, inject, parseProp, useVModel } from '#imports'
interface Props {
modelValue: number | null | undefined
@ -35,7 +35,7 @@ const currencyMeta = computed(() => {
return {
currency_locale: 'en-US',
currency_code: 'USD',
...(column.value.meta ? column.value.meta : {}),
...parseProp(column?.value?.meta),
}
})

3
packages/nc-gui/components/cell/DatePicker.vue

@ -8,6 +8,7 @@ import {
computed,
inject,
isDrawerOrModalExist,
parseProp,
ref,
useSelectedCellKeyupListener,
watch,
@ -34,7 +35,7 @@ const editable = inject(EditModeInj, ref(false))
let isDateInvalid = $ref(false)
const dateFormat = $computed(() => columnMeta?.value?.meta?.date_format ?? 'YYYY-MM-DD')
const dateFormat = $computed(() => parseProp(columnMeta?.value?.meta)?.date_format ?? 'YYYY-MM-DD')
let localState = $computed({
get() {

5
packages/nc-gui/components/cell/DateTimePicker.vue

@ -7,6 +7,7 @@ import {
dateFormats,
inject,
isDrawerOrModalExist,
parseProp,
ref,
timeFormats,
useProject,
@ -38,8 +39,8 @@ const column = inject(ColumnInj)!
let isDateInvalid = $ref(false)
const dateTimeFormat = $computed(() => {
const dateFormat = column?.value?.meta?.date_format ?? dateFormats[0]
const timeFormat = column?.value?.meta?.time_format ?? timeFormats[0]
const dateFormat = parseProp(column?.value?.meta)?.date_format ?? dateFormats[0]
const timeFormat = parseProp(column?.value?.meta)?.time_format ?? timeFormats[0]
return `${dateFormat} ${timeFormat}`
})

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

@ -3,6 +3,9 @@ import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, inject, useVModel } from '#imports'
interface Props {
// when we set a number, then it is number type
// for sqlite, when we clear a cell or empty the cell, it returns ""
// otherwise, it is null type
modelValue?: number | null | string
}
@ -22,8 +25,10 @@ const _vModel = useVModel(props, 'modelValue', emits)
const vModel = computed({
get: () => _vModel.value,
set: (value: string) => {
set: (value) => {
if (value === '') {
// if we clear / empty a cell in sqlite,
// the value is considered as ''
_vModel.value = null
} else {
_vModel.value = value

3
packages/nc-gui/components/cell/Duration.vue

@ -8,6 +8,7 @@ import {
convertMS2Duration,
durationOptions,
inject,
parseProp,
ref,
} from '#imports'
@ -32,7 +33,7 @@ const durationInMS = ref(0)
const isEdited = ref(false)
const durationType = computed(() => column?.value?.meta?.duration || 0)
const durationType = computed(() => parseProp(column?.value?.meta)?.duration || 0)
const durationPlaceholder = computed(() => durationOptions[durationType.value].title)

9
packages/nc-gui/components/cell/Float.vue

@ -3,7 +3,10 @@ import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, inject, useVModel } from '#imports'
interface Props {
modelValue?: number | null
// when we set a number, then it is number type
// for sqlite, when we clear a cell or empty the cell, it returns ""
// otherwise, it is null type
modelValue?: number | null | string
}
interface Emits {
@ -22,8 +25,10 @@ const _vModel = useVModel(props, 'modelValue', emits)
const vModel = computed({
get: () => _vModel.value,
set: (value: string) => {
set: (value) => {
if (value === '') {
// if we clear / empty a cell in sqlite,
// the value is considered as ''
_vModel.value = null
} else {
_vModel.value = value

2
packages/nc-gui/components/cell/GeoData.vue

@ -56,7 +56,7 @@ const onClickSetCurrentLocation = () => {
isLoading = false
}
const onError: PositionErrorCallback = (err) => {
const onError: PositionErrorCallback = (err: GeolocationPositionError) => {
console.error(`ERROR(${err.code}): ${err.message}`)
isLoading = false
}

9
packages/nc-gui/components/cell/Integer.vue

@ -3,7 +3,10 @@ import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, inject, useVModel } from '#imports'
interface Props {
modelValue?: number | null
// when we set a number, then it is number type
// for sqlite, when we clear a cell or empty the cell, it returns ""
// otherwise, it is null type
modelValue?: number | null | string
}
interface Emits {
@ -22,8 +25,10 @@ const _vModel = useVModel(props, 'modelValue', emits)
const vModel = computed({
get: () => _vModel.value,
set: (value: string) => {
set: (value) => {
if (value === '') {
// if we clear / empty a cell in sqlite,
// the value is considered as ''
_vModel.value = null
} else {
_vModel.value = value

3
packages/nc-gui/components/cell/MultiSelect.vue

@ -260,8 +260,7 @@ async function addIfMissingAndSave() {
} else {
activeOptCreateInProgress.value--
}
} catch (e) {
// todo: handle error
} catch (e: any) {
console.log(e)
activeOptCreateInProgress.value--
message.error(await extractSdkResponseErrorMsg(e))

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

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ActiveCellInj, ColumnInj, computed, inject, useSelectedCellKeyupListener } from '#imports'
import { ActiveCellInj, ColumnInj, computed, inject, parseProp, useSelectedCellKeyupListener } from '#imports'
interface Props {
modelValue?: number | null | undefined
@ -19,7 +19,7 @@ const ratingMeta = computed(() => {
},
color: '#fcb401',
max: 5,
...(column.value?.meta || {}),
...parseProp(column.value?.meta),
}
})

8
packages/nc-gui/components/cell/Url.vue

@ -8,6 +8,7 @@ import {
inject,
isValidURL,
message,
parseProp,
ref,
useCellUrlConfig,
useI18n,
@ -39,7 +40,7 @@ const vModel = computed({
get: () => value,
set: (val) => {
localState.value = val
if (!column.value.meta?.validate || (val && isValidURL(val)) || !val) {
if (!parseProp(column.value.meta)?.validate || (val && isValidURL(val)) || !val) {
emit('update:modelValue', val)
}
},
@ -63,7 +64,7 @@ const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
watch(
() => editEnabled.value,
() => {
if (column.value.meta?.validate && !editEnabled.value && localState.value && !isValidURL(localState.value)) {
if (parseProp(column.value.meta)?.validate && !editEnabled.value && localState.value && !isValidURL(localState.value)) {
message.error(t('msg.error.invalidURL'))
localState.value = undefined
return
@ -126,6 +127,3 @@ watch(
</div>
</div>
</template>
<!--
-->

3
packages/nc-gui/components/cell/attachment/utils.ts

@ -12,6 +12,7 @@ import {
inject,
isImage,
message,
parseProp,
ref,
storeToRefs,
useApi,
@ -102,7 +103,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
const attachmentMeta = {
...defaultAttachmentMeta,
...(typeof column.value?.meta === 'string' ? JSON.parse(column.value.meta) : column.value?.meta),
...parseProp(column?.value?.meta),
}
const newAttachments = []

5
packages/nc-gui/components/dashboard/TreeView.vue

@ -14,6 +14,7 @@ import {
extractSdkResponseErrorMsg,
isDrawerOrModalExist,
isMac,
parseProp,
reactive,
ref,
resolveComponent,
@ -312,7 +313,7 @@ watch(
const setIcon = async (icon: string, table: TableType) => {
try {
table.meta = {
...(table.meta || {}),
...parseProp(table.meta),
icon,
}
tables.value.splice(tables.value.indexOf(table), 1, { ...table })
@ -324,7 +325,7 @@ const setIcon = async (icon: string, table: TableType) => {
})
$e('a:table:icon:navdraw', { icon })
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}

4
packages/nc-gui/components/dashboard/settings/AuditTab.vue

@ -28,8 +28,8 @@ async function loadAudits(page = currentPage, limit = currentLimit) {
isLoading = true
const { list, pageInfo } = await $api.project.auditList(project.value?.id, {
offset: (limit * (page - 1)).toString(),
limit: limit.toString(),
offset: limit * (page - 1),
limit,
})
audits = list

15
packages/nc-gui/components/dashboard/settings/app-store/AppInstall.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { PluginType } from 'nocodb-sdk'
import type { PluginTestReqType, PluginType } from 'nocodb-sdk'
import { extractSdkResponseErrorMsg, message, onMounted, ref, useI18n, useNuxtApp } from '#imports'
const { id } = defineProps<{
@ -64,12 +64,12 @@ const testSettings = async () => {
loadingAction = Action.Test
try {
if (plugin) {
const res = await $api.plugin.test({
input: pluginFormData,
id: plugin?.id,
category: plugin?.category,
title: plugin?.title,
})
input: JSON.stringify(pluginFormData),
title: plugin.title,
category: plugin.category,
} as PluginTestReqType)
if (res) {
// Successfully tested plugin settings
@ -78,6 +78,7 @@ const testSettings = async () => {
// Invalid credentials
message.info(t('msg.info.invalidCredentials'))
}
}
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
} finally {
@ -106,7 +107,7 @@ const readPluginDetails = async () => {
const res = await $api.plugin.read(id)
const formDetails = JSON.parse(res.input_schema ?? '{}')
const emptyParsedInput = formDetails.array ? [{}] : {}
const parsedInput = res.input ? JSON.parse(res.input) : emptyParsedInput
const parsedInput = typeof res.input === 'string' ? JSON.parse(res.input) : emptyParsedInput
// the type of 'secure' was XcType.SingleLineText in 0.0.1
// and it has been changed to XcType.Checkbox, since 0.0.2

2
packages/nc-gui/components/dlg/TableRename.vue

@ -41,7 +41,7 @@ const { updateTab } = useTabs()
const projectStore = useProject()
const { loadTables, isMysql, isMssql, isPg } = projectStore
const { project } = storeToRefs(projectStore)
const { tables, project } = storeToRefs(projectStore)
const inputEl = $ref<ComponentPublicInstance>()

4
packages/nc-gui/components/dlg/ViewCreate.vue

@ -75,9 +75,7 @@ const viewNameRules = [
{
validator: (_: unknown, v: string) =>
new Promise((resolve, reject) => {
views.every((v1) => ((v1 as GridType | KanbanType | GalleryType | MapType).alias || v1.title) !== v)
? resolve(true)
: reject(new Error(`View name should be unique`))
views.every((v1) => v1.title !== v) ? resolve(true) : reject(new Error(`View name should be unique`))
}),
message: 'View name should be unique',
},

4
packages/nc-gui/components/erd/View.vue

@ -40,8 +40,8 @@ const populateTables = async () => {
// if table is provided only get the table and its related tables
localTables = projectTables.value.filter(
(t) =>
t.id === props.table.id ||
props.table.columns?.find(
t.id === props.table?.id ||
props.table?.columns?.find(
(column) =>
column.uidt === UITypes.LinkToAnotherRecord &&
(column.colOptions as LinkToAnotherRecordType)?.fk_related_model_id === t.id,

2
packages/nc-gui/components/general/TableIcon.vue

@ -18,5 +18,3 @@ const { meta: tableMeta } = defineProps<{
<MdiEyeCircleOutline v-else-if="tableMeta?.type === 'view'" class="w-5" />
<MdiTableLarge v-else class="w-5" />
</template>
<style scoped></style>

2
packages/nc-gui/components/smartsheet/ApiSnippet.vue

@ -132,7 +132,7 @@ const onCopyToClipboard = async () => {
await copy(code)
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
} catch (e) {
} catch (e: any) {
message.error(e.message)
}
}

2
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -57,7 +57,7 @@ const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber]
const onlyNameUpdateOnEditColumns = [UITypes.LinkToAnotherRecord, UITypes.Lookup, UITypes.Rollup]
const geoDataToggleCondition = (t) => {
const geoDataToggleCondition = (t: { name: UITypes }) => {
return geodataToggleState.show ? geodataToggleState.show : !t.name.includes(UITypes.GeoData)
}

2
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -215,7 +215,7 @@ useActiveKeyupListener(
// on alt + s save record
else if (e.code === 'KeyS') {
// remove focus from the active input if any
document.activeElement?.blur()
;(document.activeElement as HTMLElement)?.blur()
e.stopPropagation()

11
packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts

@ -1,5 +1,5 @@
import type { PropType } from '@vue/runtime-core'
import type { ColumnType, LinkToAnotherRecordType, LookupType } from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType, LookupType, RollupType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { RelationTypes, UITypes } from 'nocodb-sdk'
import { ColumnInj, MetaInj, defineComponent, h, inject, isBt, isHm, isLookup, isMm, isRollup, ref, toRef } from '#imports'
@ -73,9 +73,9 @@ export default defineComponent({
setup(props) {
const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, columnMeta) as Ref<ColumnType & { colOptions: LookupType }>
const column = inject(ColumnInj, columnMeta) as Ref<ColumnType & { colOptions: LookupType | RollupType }>
let relationColumn: ColumnType & { colOptions: LookupType }
let relationColumn: ColumnType
return () => {
if (!column.value) return null
@ -83,12 +83,9 @@ export default defineComponent({
if (column && column.value) {
if (isMm(column.value) || isHm(column.value) || isBt(column.value) || isLookup(column.value) || isRollup(column.value)) {
const meta = inject(MetaInj, ref())
relationColumn = meta.value?.columns?.find(
(c) => c.id === column.value?.colOptions?.fk_relation_column_id,
) as ColumnType & {
colOptions: LinkToAnotherRecordType
}
) as ColumnType
}
}

5
packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue

@ -10,6 +10,7 @@ import {
inject,
message,
onMounted,
parseProp,
ref,
resolveComponent,
useApi,
@ -218,7 +219,7 @@ const setIcon = async (icon: string, view: ViewType) => {
try {
// modify the icon property in meta
view.meta = {
...(view.meta || {}),
...parseProp(view.meta),
icon,
}
@ -227,7 +228,7 @@ const setIcon = async (icon: string, view: ViewType) => {
})
$e('a:view:icon:sidebar', { icon })
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}

9
packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue

@ -109,7 +109,7 @@ const filterUpdateCondition = (filter: FilterType, i: number) => {
}
}
saveOrUpdate(filter, i)
filterPrevComparisonOp.value[filter.id] = filter.comparison_op
filterPrevComparisonOp.value[filter.id!] = filter.comparison_op!
$e('a:filter:update', {
logical: filter.logical_op,
comparison: filter.comparison_op,
@ -165,11 +165,10 @@ const selectFilterField = (filter: Filter, index: number) => {
// since the existing one may not be supported for the new field
// e.g. `eq` operator is not supported in checkbox field
// hence, get the first option of the supported operators of the new field
filter.comparison_op = comparisonOpList(col.uidt as UITypes).filter((compOp) =>
isComparisonOpAllowed(filter, compOp),
)?.[0].value
filter.comparison_op = comparisonOpList(col.uidt as UITypes).find((compOp) => isComparisonOpAllowed(filter, compOp))
?.value as FilterType['comparison_op']
if ([UITypes.Date, UITypes.DateTime].includes(col.uidt as UITypes) && !['blank', 'notblank'].includes(filter.comparison_op)) {
if ([UITypes.Date, UITypes.DateTime].includes(col.uidt as UITypes) && !['blank', 'notblank'].includes(filter.comparison_op!)) {
if (filter.comparison_op === 'isWithin') {
filter.comparison_sub_op = 'pastNumberOfDays'
} else {

7
packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue

@ -84,10 +84,11 @@ const checkTypeFunctions = {
type FilterType = keyof typeof checkTypeFunctions
// todo: replace with sqlUis
const { sqlUi } = $(storeToRefs(useProject()))
const { sqlUis } = storeToRefs(useProject())
const abstractType = $computed(() => (column.value?.dt && sqlUi ? sqlUi.getAbstractType(column.value) : null))
const sqlUi = ref(column.value?.base_id ? sqlUis.value[column.value?.base_id] : Object.values(sqlUis.value)[0])
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))
const checkType = (filterType: FilterType) => {
const checkTypeFunction = checkTypeFunctions[filterType]

4
packages/nc-gui/components/smartsheet/toolbar/ShareView.vue

@ -195,7 +195,7 @@ const copyLink = async () => {
// Copied to clipboard
message.success(t('msg.info.copiedToClipboard'))
} catch (e) {
} catch (e: any) {
message.error(e.message)
}
}
@ -230,7 +230,7 @@ const copyIframeCode = async () => {
// Copied to clipboard
message.success(t('msg.info.copiedToClipboard'))
} catch (e) {
} catch (e: any) {
message.error(e.message)
}
}

5
packages/nc-gui/components/smartsheet/toolbar/SharedViewList.vue

@ -5,6 +5,7 @@ import {
extractSdkResponseErrorMsg,
message,
onMounted,
parseProp,
ref,
useCopy,
useDashboard,
@ -70,7 +71,7 @@ const sharedViewUrl = (view: SharedViewType) => {
const renderAllowCSVDownload = (view: SharedViewType) => {
if (view.type === ViewTypes.GRID) {
view.meta = (view.meta && typeof view.meta === 'string' ? JSON.parse(view.meta) : view.meta) as Record<string, any>
view.meta = (view.meta && parseProp(view.meta)) as Record<string, any>
return view.meta?.allowCSVDownload ? '✔' : '❌'
} else {
return 'N/A'
@ -82,7 +83,7 @@ const copyLink = (view: SharedViewType) => {
copy(`${dashboardUrl?.value as string}#${sharedViewUrl(view)}`)
// Copied to clipboard
message.success(t('msg.info.copiedToClipboard'))
} catch (e) {
} catch (e: any) {
message.error(e.message)
}
}

6
packages/nc-gui/components/tabs/auth/UserManagement.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import { OrgUserRoles } from 'nocodb-sdk'
import type { RequestParams } from 'nocodb-sdk'
import type { ProjectUserReqType, RequestParams } from 'nocodb-sdk'
import {
extractSdkResponseErrorMsg,
message,
@ -82,7 +82,7 @@ const inviteUser = async (user: User) => {
user.roles = 'editor'
}
await api.auth.projectUserAdd(project.value.id, user)
await api.auth.projectUserAdd(project.value.id, user as ProjectUserReqType)
// Successfully added user to project
message.success(t('msg.success.userAddedToProject'))
@ -153,7 +153,7 @@ const copyInviteUrl = async (user: User) => {
// Invite URL copied to clipboard
message.success(t('msg.success.inviteURLCopied'))
} catch (e) {
} catch (e: any) {
message.error(e.message)
}
$e('c:user:copy-url')

4
packages/nc-gui/components/tabs/auth/user-management/ShareBase.vue

@ -109,7 +109,7 @@ const copyUrl = async () => {
// Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied'))
} catch (e) {
} catch (e: any) {
message.error(e.message)
}
@ -137,7 +137,7 @@ style="background: transparent; border: 1px solid #ddd"></iframe>`)
// Copied embeddable html code!
message.success(t('msg.success.embeddableHTMLCodeCopied'))
} catch (e) {
} catch (e: any) {
message.error(e.message)
}
$e('c:shared-base:copy-embed-frame')

12
packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue

@ -1,5 +1,6 @@
<script setup lang="ts">
import type { Input } from 'ant-design-vue'
import type { ProjectUserReqType } from 'nocodb-sdk'
import {
Form,
computed,
@ -107,10 +108,11 @@ const saveUser = async () => {
const res = await $api.auth.projectUserAdd(project.value.id, {
roles: usersData.role,
email: usersData.emails,
project_id: project.value.id,
projectName: project.value.title,
})
usersData.invitationToken = res.invite_token
} as ProjectUserReqType)
// for inviting one user, invite_token will only be returned when invitation email fails to send
// for inviting multiple users, invite_token will be returned anyway
usersData.invitationToken = res?.invite_token
}
emit('reload')
@ -131,7 +133,7 @@ const copyUrl = async () => {
// Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied'))
} catch (e) {
} catch (e: any) {
message.error(e.message)
}
$e('c:shared-base:copy-url')

2
packages/nc-gui/components/virtual-cell/barcode/Barcode.vue

@ -22,7 +22,7 @@ const showBarcodeModal = () => {
const barcodeMeta = $computed(() => {
return {
barcodeFormat: 'CODE128',
...(column?.value?.meta || {}),
...parseProp(column?.value?.meta),
}
})

26
packages/nc-gui/components/webhook/Editor.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { Ref } from 'vue'
import type { AuditType } from 'nocodb-sdk'
import type { AuditType, HookType } from 'nocodb-sdk'
import {
Form,
MetaInj,
@ -10,6 +10,7 @@ import {
inject,
message,
onMounted,
parseProp,
reactive,
ref,
useApi,
@ -20,7 +21,7 @@ import {
} from '#imports'
interface Props {
hook?: Record<string, any>
hook?: HookType
}
const props = defineProps<Props>()
@ -39,11 +40,13 @@ const meta = inject(MetaInj, ref())
const useForm = Form.useForm
const hook = reactive<Record<string, any>>({
const hook = reactive<
Omit<HookType, 'notification'> & { notification: Record<string, any>; eventOperation: string; condition: boolean }
>({
id: '',
title: '',
event: '',
operation: '',
event: undefined,
operation: undefined,
eventOperation: '',
notification: {
type: 'URL',
@ -256,12 +259,13 @@ function onNotTypeChange(reset = false) {
}
}
function setHook(newHook: any) {
function setHook(newHook: HookType) {
const notification = newHook.notification as Record<string, any>
Object.assign(hook, {
...newHook,
notification: {
...newHook.notification,
payload: newHook.notification.payload,
...notification,
payload: notification.payload,
},
})
}
@ -325,7 +329,7 @@ async function loadPluginList() {
...(p as any),
}
plugin.tags = p.tags ? p.tags.split(',') : []
plugin.parsedInput = typeof p.input === 'string' ? JSON.parse(p.input) : p.input
plugin.parsedInput = parseProp(p.input)
o[plugin.title] = plugin
return o
@ -400,8 +404,8 @@ watch(
if (!hook.eventOperation) return
const [event, operation] = hook.eventOperation.split(' ')
hook.event = event
hook.operation = operation
hook.event = event as HookType['event']
hook.operation = operation as HookType['operation']
},
)

4
packages/nc-gui/components/webhook/List.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { HookType } from 'nocodb-sdk'
import { MetaInj, extractSdkResponseErrorMsg, inject, message, onMounted, ref, useI18n, useNuxtApp } from '#imports'
import { MetaInj, extractSdkResponseErrorMsg, inject, message, onMounted, parseProp, ref, useI18n, useNuxtApp } from '#imports'
const emit = defineEmits(['edit', 'add'])
@ -16,7 +16,7 @@ async function loadHooksList() {
try {
const hookList = (await $api.dbTableWebhook.list(meta.value?.id as string)).list as HookType[]
hooks.value = hookList.map((hook) => {
hook.notification = typeof hook.notification === 'string' ? JSON.parse(hook.notification) : hook.notification
hook.notification = parseProp(hook.notification)
return hook
})
} catch (e: any) {

10
packages/nc-gui/components/webhook/Test.vue

@ -1,8 +1,9 @@
<script setup lang="ts">
import type { HookTestReqType, HookType } from 'nocodb-sdk'
import { MetaInj, extractSdkResponseErrorMsg, inject, message, onMounted, ref, useI18n, useNuxtApp, watch } from '#imports'
interface Props {
hook: Record<string, any>
hook: HookType
}
const { hook } = defineProps<Props>()
@ -33,10 +34,13 @@ async function loadSampleData() {
async function testWebhook() {
try {
await $api.dbTableWebhook.test(meta.value?.id as string, {
await $api.dbTableWebhook.test(
meta.value?.id as string,
{
hook,
payload: sampleData.value,
})
} as HookTestReqType,
)
// Webhook tested successfully
message.success(t('msg.success.webhookTested'))

8
packages/nc-gui/composables/useGlobal/actions.ts

@ -1,4 +1,4 @@
import type { Actions, State } from './types'
import type { Actions, AppInfo, State } from './types'
import { message, useNuxtApp } from '#imports'
export function useGlobalActions(state: State): Actions {
@ -7,10 +7,8 @@ export function useGlobalActions(state: State): Actions {
state.token.value = null
state.user.value = null
try {
if (state.token.value) {
const nuxtApp = useNuxtApp()
await nuxtApp.$api.auth.signout()
}
} catch {}
}
@ -48,14 +46,14 @@ export function useGlobalActions(state: State): Actions {
message.error(err.message || t('msg.error.youHaveBeenSignedOut'))
await signOut()
})
.finally(resolve)
.finally(() => resolve())
})
}
const loadAppInfo = async () => {
try {
const nuxtApp = useNuxtApp()
state.appInfo.value = await nuxtApp.$api.utils.appInfo()
state.appInfo.value = (await nuxtApp.$api.utils.appInfo()) as AppInfo
} catch (e) {
console.error(e)
}

4
packages/nc-gui/composables/useKanbanViewStore.ts

@ -11,6 +11,7 @@ import {
getHTMLEncodedText,
inject,
message,
parseProp,
provide,
ref,
storeToRefs,
@ -198,8 +199,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
// set groupingField
groupingFieldColumn.value = !isPublic.value
? (meta.value.columns as ColumnType[]).filter((f) => f.id === kanbanMetaData.value.fk_grp_col_id)[0] || {}
: ((typeof sharedView.value?.meta === 'string' ? JSON.parse(sharedView.value?.meta) : sharedView.value?.meta)
.groupingFieldColumn! as ColumnType)
: (parseProp(sharedView.value?.meta).groupingFieldColumn! as ColumnType)
groupingField.value = groupingFieldColumn.value.title!

6
packages/nc-gui/composables/useMapViewDataStore.ts

@ -1,6 +1,6 @@
import { reactive } from 'vue'
import type { ComputedRef, Ref } from 'vue'
import type { ColumnType, MapType, PaginatedType, ViewType } from 'nocodb-sdk'
import type { ColumnType, MapType, PaginatedType, TableType, ViewType } from 'nocodb-sdk'
import { IsPublicInj, ref, storeToRefs, useInjectionState, useMetas, useProject } from '#imports'
import type { Row } from '~/lib'
@ -22,7 +22,7 @@ const formatData = (list: Record<string, any>[]) =>
const [useProvideMapViewStore, useMapViewStore] = useInjectionState(
(
meta: Ref<MapType | undefined>,
meta: Ref<(MapType & { id: string }) | undefined>,
viewMeta: Ref<(ViewType | MapType | undefined) & { id: string }> | ComputedRef<(ViewType & { id: string }) | undefined>,
shared = false,
where?: ComputedRef<string | undefined>,
@ -111,7 +111,7 @@ const [useProvideMapViewStore, useMapViewStore] = useInjectionState(
if (currentRow.rowMeta) currentRow.rowMeta.saving = true
try {
const { missingRequiredColumns, insertObj } = await populateInsertObject({
meta: metaValue!,
meta: metaValue as TableType,
ltarState,
getMeta,
row,

4
packages/nc-gui/composables/useMetas.ts

@ -1,6 +1,6 @@
import { message } from 'ant-design-vue'
import type { WatchStopHandle } from 'vue'
import type { TableInfoType, TableType } from 'nocodb-sdk'
import type { TableType } from 'nocodb-sdk'
import { extractSdkResponseErrorMsg, storeToRefs, useNuxtApp, useProject, useState, watch } from '#imports'
export function useMetas() {
@ -26,7 +26,7 @@ export function useMetas() {
}
// todo: this needs a proper refactor, arbitrary waiting times are usually not a good idea
const getMeta = async (tableIdOrTitle: string, force = false): Promise<TableType | TableInfoType | null> => {
const getMeta = async (tableIdOrTitle: string, force = false): Promise<TableType | null> => {
if (!tableIdOrTitle) return null
/** wait until loading is finished if requesting same meta */
if (!force && loadingState.value[tableIdOrTitle]) {

5
packages/nc-gui/composables/useMultiSelect/convertCellData.ts

@ -2,6 +2,7 @@ import dayjs from 'dayjs'
import type { ColumnType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import type { AppInfo } from '~/composables/useGlobal'
import { parseProp } from '#imports'
export default function convertCellData(
args: { from: UITypes; to: UITypes; value: any; column: ColumnType; appInfo: AppInfo },
@ -73,7 +74,7 @@ export default function convertCellData(
case UITypes.Attachment: {
let parsedVal
try {
parsedVal = typeof value === 'string' ? JSON.parse(value) : value
parsedVal = parseProp(value)
parsedVal = Array.isArray(parsedVal) ? parsedVal : [parsedVal]
} catch (e) {
throw new Error('Invalid attachment data')
@ -94,7 +95,7 @@ export default function convertCellData(
const attachmentMeta = {
...defaultAttachmentMeta,
...(typeof args.column?.meta === 'string' ? JSON.parse(args.column.meta) : args.column?.meta),
...parseProp(args.column?.meta),
}
const attachments = []

4
packages/nc-gui/composables/useSharedView.ts

@ -10,7 +10,7 @@ import type {
ViewType,
} from 'nocodb-sdk'
import { UITypes, ViewTypes } from 'nocodb-sdk'
import { computed, storeToRefs, useGlobal, useMetas, useNuxtApp, useState } from '#imports'
import { computed, parseProp, storeToRefs, useGlobal, useMetas, useNuxtApp, useState } from '#imports'
export function useSharedView() {
const nestedFilters = ref<(FilterType & { status?: 'update' | 'delete' | 'create'; parentId?: string })[]>([])
@ -63,7 +63,7 @@ export function useSharedView() {
},
})
try {
allowCSVDownload.value = (typeof viewMeta.meta === 'string' ? JSON.parse(viewMeta.meta) : viewMeta.meta)?.allowCSVDownload
allowCSVDownload.value = parseProp(viewMeta.meta)?.allowCSVDownload
} catch {
allowCSVDownload.value = false
}

6
packages/nc-gui/composables/useTable.ts

@ -1,4 +1,4 @@
import type { LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes, isSystemColumn } from 'nocodb-sdk'
import {
Modal,
@ -40,14 +40,14 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void, baseId?
const createTable = async () => {
if (!sqlUi?.value) return
const columns = sqlUi?.value?.getNewTableColumns().filter((col) => {
const columns = sqlUi?.value?.getNewTableColumns().filter((col: ColumnType) => {
if (col.column_name === 'id' && table.columns.includes('id_ag')) {
Object.assign(col, sqlUi?.value?.getDataTypeForUiType({ uidt: UITypes.ID }, 'AG'))
col.dtxp = sqlUi?.value?.getDefaultLengthForDatatype(col.dt)
col.dtxs = sqlUi?.value?.getDefaultScaleForDatatype(col.dt)
return true
}
return table.columns.includes(col.column_name)
return table.columns.includes(col.column_name!)
})
try {

4
packages/nc-gui/composables/useViewFilters.ts

@ -162,10 +162,9 @@ export function useViewFilters(
const placeholderFilter = (): Filter => {
return {
// TODO: fix type
comparison_op: comparisonOpList(options.value?.[0].uidt as UITypes).filter((compOp) =>
isComparisonOpAllowed({ fk_column_id: options.value?.[0].id }, compOp),
)?.[0].value,
)?.[0].value as FilterType['comparison_op'],
value: '',
status: 'create',
logical_op: 'and',
@ -281,7 +280,6 @@ export function useViewFilters(
comparison: filter.comparison_op,
})
} else {
// todo: return type of dbTableFilter is void?
filters.value[i] = await $api.dbTableFilter.create(view.value.id!, {
...filter,
fk_parent_id: parentId,

2
packages/nc-gui/context/index.ts

@ -7,7 +7,7 @@ import type { Row, TabItem } from '~/lib'
export const ActiveCellInj: InjectionKey<Ref<boolean>> = Symbol('active-cell')
export const IsPublicInj: InjectionKey<Ref<boolean>> = Symbol('is-public')
export const RowInj: InjectionKey<Ref<Row>> = Symbol('row')
export const ColumnInj: InjectionKey<Ref<ColumnType & { meta: any }>> = Symbol('column-injection')
export const ColumnInj: InjectionKey<Ref<ColumnType>> = Symbol('column-injection')
export const MetaInj: InjectionKey<ComputedRef<TableType>> = Symbol('meta-injection')
export const TabMetaInj: InjectionKey<ComputedRef<TabItem>> = Symbol('tab-meta-injection')
export const PaginationDataInj: InjectionKey<ReturnType<typeof useViewData>['paginationData']> =

6
packages/nc-gui/lib/types.ts

@ -1,4 +1,4 @@
import type { FilterType, ViewTypes } from 'nocodb-sdk'
import type { FilterType, MetaType, ViewTypes } from 'nocodb-sdk'
import type { I18n } from 'vue-i18n'
import type { Theme as AntTheme } from 'ant-design-vue/es/config-provider'
import type { UploadFile } from 'ant-design-vue'
@ -21,7 +21,7 @@ export interface ProjectMetaInfo {
Platform?: string
Docker?: boolean
Database?: string
ProjectOnRootDB?: string
ProjectOnRootDB?: boolean
RootDB?: string
PackageVersion?: string
}
@ -79,7 +79,7 @@ export interface TabItem {
viewId?: string
sortsState?: Map<string, any>
filterState?: Map<string, any>
meta?: Record<string, any>
meta?: MetaType
}
export interface SharedViewMeta extends Record<string, any> {

4
packages/nc-gui/middleware/auth.global.ts

@ -102,8 +102,8 @@ async function tryGoogleAuth(api: Api<any>, signIn: Actions['signIn']) {
)
signIn(token)
} catch (e) {
message.error({ content: await extractSdkResponseErrorMsg(e) })
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
const newURL = window.location.href.split('?')[0]

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

@ -73,6 +73,7 @@
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
"@nuxt/image-edge": "^1.0.0-27657146.da85542",
"@types/axios": "^0.14.0",
"@types/d3-scale": "^4.0.3",
"@types/dagre": "^0.7.48",
"@types/file-saver": "^2.0.5",
"@types/leaflet": "^1.9.0",
@ -3307,11 +3308,26 @@
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-scale": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.3.tgz",
"integrity": "sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==",
"dev": true,
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-selection": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz",
"integrity": "sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA=="
},
"node_modules/@types/d3-time": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
"integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==",
"dev": true
},
"node_modules/@types/d3-zoom": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz",
@ -20323,11 +20339,26 @@
"@types/d3-color": "*"
}
},
"@types/d3-scale": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.3.tgz",
"integrity": "sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==",
"dev": true,
"requires": {
"@types/d3-time": "*"
}
},
"@types/d3-selection": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz",
"integrity": "sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA=="
},
"@types/d3-time": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
"integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==",
"dev": true
},
"@types/d3-zoom": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz",

1
packages/nc-gui/package.json

@ -96,6 +96,7 @@
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
"@nuxt/image-edge": "^1.0.0-27657146.da85542",
"@types/axios": "^0.14.0",
"@types/d3-scale": "^4.0.3",
"@types/dagre": "^0.7.48",
"@types/file-saver": "^2.0.5",
"@types/leaflet": "^1.9.0",

2
packages/nc-gui/pages/[projectType]/[projectId]/index.vue

@ -142,7 +142,7 @@ const copyProjectInfo = async () => {
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
}
} catch (e) {
} catch (e: any) {
console.error(e)
message.error(e.message)
}

5
packages/nc-gui/pages/index/index/create-external.vue

@ -1,4 +1,5 @@
<script lang="ts" setup>
import type { ProjectType } from 'nocodb-sdk'
import type { SelectHandler } from 'ant-design-vue/es/vc-select/Select'
import type { DefaultConnection, ProjectCreateForm } from '#imports'
import {
@ -233,7 +234,7 @@ const createProject = async () => {
const config = { ...formState.dataSource, connection }
const result = await api.project.create({
const result = (await api.project.create({
title: formState.title,
bases: [
{
@ -244,7 +245,7 @@ const createProject = async () => {
},
],
external: true,
})
})) as Partial<ProjectType>
$e('a:project:create:extdb')

5
packages/nc-gui/pages/index/index/create.vue

@ -2,6 +2,7 @@
import type { Form, Input } from 'ant-design-vue'
import type { RuleObject } from 'ant-design-vue/es/form'
import type { VNodeRef } from '@vue/runtime-core'
import type { ProjectType } from 'nocodb-sdk'
import {
extractSdkResponseErrorMsg,
generateUniqueName,
@ -44,9 +45,9 @@ const createProject = async () => {
try {
creating.value = true
const result = await api.project.create({
const result = (await api.project.create({
title: formState.title,
})
})) as Partial<ProjectType>
await navigateTo(`/nc/${result.id}`)
} catch (e: any) {

7
packages/nc-gui/pages/index/index/index.vue

@ -11,6 +11,7 @@ import {
message,
navigateTo,
onBeforeMount,
parseProp,
projectThemeColors,
ref,
themeV2Colors,
@ -80,7 +81,7 @@ const handleProjectColor = async (projectId: string, color: string) => {
const project: ProjectType = await $api.project.read(projectId)
const meta = project?.meta && typeof project.meta === 'string' ? JSON.parse(project.meta) : project.meta || {}
const meta = parseProp(project?.meta)
await $api.project.update(projectId, {
color,
@ -113,7 +114,7 @@ const handleProjectColor = async (projectId: string, color: string) => {
const getProjectPrimary = (project: ProjectType) => {
if (!project) return
const meta = project.meta && typeof project.meta === 'string' ? JSON.parse(project.meta) : project.meta || {}
const meta = parseProp(project.meta)
return meta.theme?.primaryColor || themeV2Colors['royal-blue'].DEFAULT
}
@ -136,7 +137,7 @@ const copyProjectMeta = async () => {
const aggregatedMetaInfo = await $api.utils.aggregatedMetaInfo()
await copy(JSON.stringify(aggregatedMetaInfo))
message.info('Copied aggregated project meta to clipboard')
} catch (e) {
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}

4
packages/nc-gui/store/project.ts

@ -98,7 +98,7 @@ export const useProject = defineStore('projectStore', () => {
async function loadProjectMetaInfo(force?: boolean) {
if (!projectMetaInfo.value || force) {
projectMetaInfo.value = await api.project.metaGet(project.value.id!, {}, {})
projectMetaInfo.value = await api.project.metaGet(project.value.id!, {})
}
}
@ -150,7 +150,7 @@ export const useProject = defineStore('projectStore', () => {
if (data.meta && typeof data.meta === 'string') {
await api.project.update(projectId.value, data)
} else {
await api.project.update(projectId.value, { ...data, meta: JSON.stringify(data.meta) })
await api.project.update(projectId.value, { ...data, meta: stringifyProp(data.meta) })
}
}

4
packages/nc-gui/utils/dataUtils.ts

@ -1,5 +1,5 @@
import { RelationTypes, UITypes } from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType, TableInfoType, TableType } from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
export const extractPkFromRow = (row: Record<string, any>, columns: ColumnType[]) => {
return (
@ -21,7 +21,7 @@ export async function populateInsertObject({
}: {
meta: TableType
ltarState: Record<string, any>
getMeta: (tableIdOrTitle: string, force?: boolean) => Promise<TableType | TableInfoType | null>
getMeta: (tableIdOrTitle: string, force?: boolean) => Promise<TableType | null>
row: Record<string, any>
throwError?: boolean
}) {

1
packages/nc-gui/utils/index.ts

@ -22,3 +22,4 @@ export * from './memStorage'
export * from './browserUtils'
export * from './geoDataUtils'
export * from './mimeTypeUtils'
export * from './parseUtils'

17
packages/nc-gui/utils/parseUtils.ts

@ -0,0 +1,17 @@
export function parseProp(v: any): any {
if (!v) return {}
try {
return typeof v === 'string' ? JSON.parse(v) : v
} catch {
return {}
}
}
export function stringifyProp(v: any): string | undefined {
if (!v) return undefined
try {
return typeof v === 'string' ? v : JSON.stringify(v)
} catch {
return '{}'
}
}

6
packages/nc-gui/utils/virtualCell.ts

@ -5,13 +5,13 @@ export const isLTAR = (uidt: string, colOptions: unknown): colOptions is LinkToA
uidt === UITypes.LinkToAnotherRecord
export const isHm = (column: ColumnType) =>
isLTAR(column.uidt, column.colOptions) && column.colOptions.type === RelationTypes.HAS_MANY
isLTAR(column.uidt!, column.colOptions) && column.colOptions.type === RelationTypes.HAS_MANY
export const isMm = (column: ColumnType) =>
isLTAR(column.uidt, column.colOptions) && column.colOptions.type === RelationTypes.MANY_TO_MANY
isLTAR(column.uidt!, column.colOptions) && column.colOptions.type === RelationTypes.MANY_TO_MANY
export const isBt = (column: ColumnType) =>
isLTAR(column.uidt, column.colOptions) && column.colOptions.type === RelationTypes.BELONGS_TO
isLTAR(column.uidt!, column.colOptions) && column.colOptions.type === RelationTypes.BELONGS_TO
export const isLookup = (column: ColumnType) => column.uidt === UITypes.Lookup
export const isRollup = (column: ColumnType) => column.uidt === UITypes.Rollup

60
packages/nocodb-sdk/src/lib/Api.ts

@ -1697,7 +1697,9 @@ export interface PluginTestReqType {
/** Plugin Title */
title: string;
/** Plugin Input as JSON string */
input: string;
input: string | object;
/** @example Email */
category: string;
}
/**
@ -1775,6 +1777,24 @@ export interface ProjectReqType {
title: string;
}
/**
* Model for Project Update Request
*/
export interface ProjectUpdateReqType {
/**
* Primary Theme Color
* @example #24716E
*/
color?: string;
/** Project Meta */
meta?: MetaType;
/**
* Project Title
* @example My Project
*/
title?: string;
}
/**
* Model for Project User Request
*/
@ -2927,10 +2947,21 @@ export class Api<
* @request POST:/api/v1/db/meta/projects/{projectId}/users
* @response `200` `{
\**
* Success Message
* Success Message for inviting single email
* @example The user has been invited successfully
*\
msg?: string,
\** @example 8354ddba-a769-4d64-8397-eccb2e2b3c06 *\
invite_token?: string,
error?: ({
\** @example w@nocodb.com *\
email?: string,
\** @example <ERROR_MESSAGE> *\
error?: string,
})[],
\** @example w@nocodb.com *\
email?: string,
}` OK
* @response `400` `{
@ -2947,10 +2978,20 @@ export class Api<
this.request<
{
/**
* Success Message
* Success Message for inviting single email
* @example The user has been invited successfully
*/
msg?: string;
/** @example 8354ddba-a769-4d64-8397-eccb2e2b3c06 */
invite_token?: string;
error?: {
/** @example w@nocodb.com */
email?: string;
/** @example <ERROR_MESSAGE> */
error?: string;
}[];
/** @example w@nocodb.com */
email?: string;
},
{
/** @example BadRequest [Error]: <ERROR MESSAGE> */
@ -3904,16 +3945,20 @@ export class Api<
* @name Update
* @summary Update Project
* @request PATCH:/api/v1/db/meta/projects/{projectId}
* @response `200` `void` OK
* @response `200` `number` OK
* @response `400` `{
\** @example BadRequest [Error]: <ERROR MESSAGE> *\
msg: string,
}`
*/
update: (projectId: IdType, data: number, params: RequestParams = {}) =>
update: (
projectId: IdType,
data: ProjectUpdateReqType,
params: RequestParams = {}
) =>
this.request<
void,
number,
{
/** @example BadRequest [Error]: <ERROR MESSAGE> */
msg: string;
@ -3923,6 +3968,7 @@ export class Api<
method: 'PATCH',
body: data,
type: ContentType.Json,
format: 'json',
...params,
}),
@ -8319,6 +8365,7 @@ export class Api<
ee?: boolean,
ncAttachmentFieldSize?: number,
ncMaxAttachmentsAllowed?: number,
isCloud?: boolean,
}` OK
* @response `400` `{
@ -8347,6 +8394,7 @@ export class Api<
ee?: boolean;
ncAttachmentFieldSize?: number;
ncMaxAttachmentsAllowed?: number;
isCloud?: boolean;
},
{
/** @example BadRequest [Error]: <ERROR MESSAGE> */

9
packages/nocodb/src/lib/services/project.svc.ts

@ -11,7 +11,7 @@ import Project from '../models/Project';
import syncMigration from '../meta/helpers/syncMigration';
import { populateMeta, validatePayload } from '../meta/api/helpers';
import { extractPropsAndSanitize } from '../meta/helpers/extractProps';
import type { ProjectReqType } from 'nocodb-sdk';
import type { ProjectReqType, ProjectUpdateReqType } from 'nocodb-sdk';
export async function projectCreate(param: {
project: ProjectReqType;
@ -136,8 +136,13 @@ export function sanitizeProject(project: any) {
export async function projectUpdate(param: {
projectId: string;
project: ProjectReqType;
project: ProjectUpdateReqType;
}) {
validatePayload(
'swagger.json#/components/schemas/ProjectUpdateReq',
param.project
);
const project = await Project.getWithInfo(param.projectId);
const data: Partial<Project> = extractPropsAndSanitize(

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

@ -1461,16 +1461,75 @@
"x-stoplight": {
"id": "waau9tvy75zsd"
},
"description": "Success Message",
"description": "Success Message for inviting single email",
"example": "The user has been invited successfully"
},
"invite_token": {
"type": "string",
"x-stoplight": {
"id": "yx0s35u8ds3p7"
},
"example": "8354ddba-a769-4d64-8397-eccb2e2b3c06"
},
"error": {
"type": "array",
"x-stoplight": {
"id": "yhfi6wzhr6zr1"
},
"items": {
"x-stoplight": {
"id": "ce0hlv3d0f96j"
},
"type": "object",
"properties": {
"email": {
"type": "string",
"x-stoplight": {
"id": "dgnh01j4lxvl1"
},
"example": "w@nocodb.com"
},
"error": {
"type": "string",
"x-stoplight": {
"id": "7dgttqiijg8no"
},
"example": "<ERROR_MESSAGE>"
}
}
}
},
"email": {
"type": "string",
"x-stoplight": {
"id": "08pqst2q30vot"
},
"example": "w@nocodb.com"
}
}
},
"examples": {
"Example 1": {
"Inviting a user without any errors": {
"value": {
"msg": "The user has been invited successfully"
}
},
"Inviting a user but invitation email failed to send": {
"value": {
"invite_token": "8354ddba-a769-4d64-8397-eccb2e2b3c06",
"email": "w@nocodb.com"
}
},
"Inviting multiple users": {
"value": {
"invite_token": "8354ddba-a769-4d64-8397-eccb2e2b3c06",
"error": [
{
"email": "w@nocodb.com",
"error": "<ERROR MESSAGE>"
}
]
}
}
}
}
@ -2149,7 +2208,20 @@
"operationId": "project-update",
"responses": {
"200": {
"description": "OK"
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "number",
"example": 1
},
"examples": {
"Example 1": {
"value": 1
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
@ -2160,11 +2232,15 @@
"content": {
"application/json": {
"schema": {
"type": "number"
"$ref": "#/components/schemas/ProjectUpdateReq"
},
"examples": {
"Example 1": {
"value": 1
"value": {
"color": "#24716E",
"meta": null,
"title": "My Project"
}
}
}
}
@ -12211,6 +12287,15 @@
"application/json": {
"schema": {
"$ref": "#/components/schemas/PluginTestReq"
},
"examples": {
"Example 1": {
"value": {
"title": "Plugin Foo",
"input": "{\"bucket\":\"my-bucket\",\"region\":\"us-west-004\",\"access_key\":\"redacted\",\"access_secret\":\"redacted\"}",
"category": "Email"
}
}
}
}
}
@ -12628,6 +12713,12 @@
},
"ncMaxAttachmentsAllowed": {
"type": "number"
},
"isCloud": {
"type": "boolean",
"x-stoplight": {
"id": "bstdkpky2131f"
}
}
}
},
@ -12650,7 +12741,8 @@
"ncSiteUrl": "http://localhost:8080",
"ee": false,
"ncAttachmentFieldSize": 20971520,
"ncMaxAttachmentsAllowed": 10
"ncMaxAttachmentsAllowed": 10,
"isCloud": false
}
}
}
@ -17211,7 +17303,8 @@
"examples": [
{
"title": "Plugin Foo",
"input": "{\"bucket\":\"my-bucket\",\"region\":\"us-west-004\",\"access_key\":\"redacted\",\"access_secret\":\"redacted\"}"
"input": "{\"bucket\":\"my-bucket\",\"region\":\"us-west-004\",\"access_key\":\"redacted\",\"access_secret\":\"redacted\"}",
"category": "Email"
}
],
"title": "Plugin Test Request Model",
@ -17229,12 +17322,20 @@
"description": "Plugin Input as JSON string"
},
{
"description": "Plugin Input"
"description": "Plugin Input",
"type": "object"
}
]
},
"category": {
"x-stoplight": {
"id": "rg3i3ov9rs6d0"
},
"type": "string",
"example": "Email"
}
},
"required": ["title", "input"]
"required": ["title", "input", "category"]
},
"Project": {
"description": "Model for Project",
@ -17466,6 +17567,43 @@
"title": "Project Request Model",
"type": "object"
},
"ProjectUpdateReq": {
"description": "Model for Project Update Request",
"x-stoplight": {
"id": "4zgrec70wyz4c"
},
"examples": [
{
"color": "#24716E",
"meta": null,
"title": "My Project"
}
],
"title": "Project Update Request Model",
"type": "object",
"properties": {
"color": {
"description": "Primary Theme Color",
"example": "#24716E",
"maxLength": 50,
"type": "string"
},
"meta": {
"$ref": "#/components/schemas/Meta",
"description": "Project Meta",
"x-stoplight": {
"id": "m05w9sbwqgul3"
}
},
"title": {
"description": "Project Title",
"example": "My Project",
"maxLength": 128,
"minLength": 1,
"type": "string"
}
}
},
"ProjectUserReq": {
"description": "Model for Project User Request",
"examples": [

Loading…
Cancel
Save