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

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

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

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

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

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

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

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

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

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

@ -1,5 +1,14 @@
<script setup lang="ts"> <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 { interface Props {
// If the previous cell value was a text, the initial checkbox value is a string type // 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', unchecked: 'mdi-checkbox-blank-circle-outline',
}, },
color: 'primary', 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"> <script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core' 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 { interface Props {
modelValue: number | null | undefined modelValue: number | null | undefined
@ -35,7 +35,7 @@ const currencyMeta = computed(() => {
return { return {
currency_locale: 'en-US', currency_locale: 'en-US',
currency_code: 'USD', 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, computed,
inject, inject,
isDrawerOrModalExist, isDrawerOrModalExist,
parseProp,
ref, ref,
useSelectedCellKeyupListener, useSelectedCellKeyupListener,
watch, watch,
@ -34,7 +35,7 @@ const editable = inject(EditModeInj, ref(false))
let isDateInvalid = $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({ let localState = $computed({
get() { get() {

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

@ -7,6 +7,7 @@ import {
dateFormats, dateFormats,
inject, inject,
isDrawerOrModalExist, isDrawerOrModalExist,
parseProp,
ref, ref,
timeFormats, timeFormats,
useProject, useProject,
@ -38,8 +39,8 @@ const column = inject(ColumnInj)!
let isDateInvalid = $ref(false) let isDateInvalid = $ref(false)
const dateTimeFormat = $computed(() => { const dateTimeFormat = $computed(() => {
const dateFormat = column?.value?.meta?.date_format ?? dateFormats[0] const dateFormat = parseProp(column?.value?.meta)?.date_format ?? dateFormats[0]
const timeFormat = column?.value?.meta?.time_format ?? timeFormats[0] const timeFormat = parseProp(column?.value?.meta)?.time_format ?? timeFormats[0]
return `${dateFormat} ${timeFormat}` 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' import { EditModeInj, inject, useVModel } from '#imports'
interface Props { 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 modelValue?: number | null | string
} }
@ -22,8 +25,10 @@ const _vModel = useVModel(props, 'modelValue', emits)
const vModel = computed({ const vModel = computed({
get: () => _vModel.value, get: () => _vModel.value,
set: (value: string) => { set: (value) => {
if (value === '') { if (value === '') {
// if we clear / empty a cell in sqlite,
// the value is considered as ''
_vModel.value = null _vModel.value = null
} else { } else {
_vModel.value = value _vModel.value = value

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

@ -8,6 +8,7 @@ import {
convertMS2Duration, convertMS2Duration,
durationOptions, durationOptions,
inject, inject,
parseProp,
ref, ref,
} from '#imports' } from '#imports'
@ -32,7 +33,7 @@ const durationInMS = ref(0)
const isEdited = ref(false) 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) 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' import { EditModeInj, inject, useVModel } from '#imports'
interface Props { 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 { interface Emits {
@ -22,8 +25,10 @@ const _vModel = useVModel(props, 'modelValue', emits)
const vModel = computed({ const vModel = computed({
get: () => _vModel.value, get: () => _vModel.value,
set: (value: string) => { set: (value) => {
if (value === '') { if (value === '') {
// if we clear / empty a cell in sqlite,
// the value is considered as ''
_vModel.value = null _vModel.value = null
} else { } else {
_vModel.value = value _vModel.value = value

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

@ -56,7 +56,7 @@ const onClickSetCurrentLocation = () => {
isLoading = false isLoading = false
} }
const onError: PositionErrorCallback = (err) => { const onError: PositionErrorCallback = (err: GeolocationPositionError) => {
console.error(`ERROR(${err.code}): ${err.message}`) console.error(`ERROR(${err.code}): ${err.message}`)
isLoading = false 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' import { EditModeInj, inject, useVModel } from '#imports'
interface Props { 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 { interface Emits {
@ -22,8 +25,10 @@ const _vModel = useVModel(props, 'modelValue', emits)
const vModel = computed({ const vModel = computed({
get: () => _vModel.value, get: () => _vModel.value,
set: (value: string) => { set: (value) => {
if (value === '') { if (value === '') {
// if we clear / empty a cell in sqlite,
// the value is considered as ''
_vModel.value = null _vModel.value = null
} else { } else {
_vModel.value = value _vModel.value = value

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

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

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

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

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

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

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

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

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

@ -14,6 +14,7 @@ import {
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
isDrawerOrModalExist, isDrawerOrModalExist,
isMac, isMac,
parseProp,
reactive, reactive,
ref, ref,
resolveComponent, resolveComponent,
@ -312,7 +313,7 @@ watch(
const setIcon = async (icon: string, table: TableType) => { const setIcon = async (icon: string, table: TableType) => {
try { try {
table.meta = { table.meta = {
...(table.meta || {}), ...parseProp(table.meta),
icon, icon,
} }
tables.value.splice(tables.value.indexOf(table), 1, { ...table }) 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 }) $e('a:table:icon:navdraw', { icon })
} catch (e) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) 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 isLoading = true
const { list, pageInfo } = await $api.project.auditList(project.value?.id, { const { list, pageInfo } = await $api.project.auditList(project.value?.id, {
offset: (limit * (page - 1)).toString(), offset: limit * (page - 1),
limit: limit.toString(), limit,
}) })
audits = list audits = list

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

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

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

@ -75,9 +75,7 @@ const viewNameRules = [
{ {
validator: (_: unknown, v: string) => validator: (_: unknown, v: string) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
views.every((v1) => ((v1 as GridType | KanbanType | GalleryType | MapType).alias || v1.title) !== v) views.every((v1) => v1.title !== v) ? resolve(true) : reject(new Error(`View name should be unique`))
? resolve(true)
: reject(new Error(`View name should be unique`))
}), }),
message: '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 // if table is provided only get the table and its related tables
localTables = projectTables.value.filter( localTables = projectTables.value.filter(
(t) => (t) =>
t.id === props.table.id || t.id === props.table?.id ||
props.table.columns?.find( props.table?.columns?.find(
(column) => (column) =>
column.uidt === UITypes.LinkToAnotherRecord && column.uidt === UITypes.LinkToAnotherRecord &&
(column.colOptions as LinkToAnotherRecordType)?.fk_related_model_id === t.id, (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" /> <MdiEyeCircleOutline v-else-if="tableMeta?.type === 'view'" class="w-5" />
<MdiTableLarge v-else class="w-5" /> <MdiTableLarge v-else class="w-5" />
</template> </template>
<style scoped></style>

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

@ -132,7 +132,7 @@ const onCopyToClipboard = async () => {
await copy(code) await copy(code)
// Copied to clipboard // Copied to clipboard
message.info(t('msg.info.copiedToClipboard')) message.info(t('msg.info.copiedToClipboard'))
} catch (e) { } catch (e: any) {
message.error(e.message) 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 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) 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 // on alt + s save record
else if (e.code === 'KeyS') { else if (e.code === 'KeyS') {
// remove focus from the active input if any // remove focus from the active input if any
document.activeElement?.blur() ;(document.activeElement as HTMLElement)?.blur()
e.stopPropagation() e.stopPropagation()

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

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

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

@ -10,6 +10,7 @@ import {
inject, inject,
message, message,
onMounted, onMounted,
parseProp,
ref, ref,
resolveComponent, resolveComponent,
useApi, useApi,
@ -218,7 +219,7 @@ const setIcon = async (icon: string, view: ViewType) => {
try { try {
// modify the icon property in meta // modify the icon property in meta
view.meta = { view.meta = {
...(view.meta || {}), ...parseProp(view.meta),
icon, icon,
} }
@ -227,7 +228,7 @@ const setIcon = async (icon: string, view: ViewType) => {
}) })
$e('a:view:icon:sidebar', { icon }) $e('a:view:icon:sidebar', { icon })
} catch (e) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) 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) saveOrUpdate(filter, i)
filterPrevComparisonOp.value[filter.id] = filter.comparison_op filterPrevComparisonOp.value[filter.id!] = filter.comparison_op!
$e('a:filter:update', { $e('a:filter:update', {
logical: filter.logical_op, logical: filter.logical_op,
comparison: filter.comparison_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 // since the existing one may not be supported for the new field
// e.g. `eq` operator is not supported in checkbox field // e.g. `eq` operator is not supported in checkbox field
// hence, get the first option of the supported operators of the new field // hence, get the first option of the supported operators of the new field
filter.comparison_op = comparisonOpList(col.uidt as UITypes).filter((compOp) => filter.comparison_op = comparisonOpList(col.uidt as UITypes).find((compOp) => isComparisonOpAllowed(filter, compOp))
isComparisonOpAllowed(filter, compOp), ?.value as FilterType['comparison_op']
)?.[0].value
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') { if (filter.comparison_op === 'isWithin') {
filter.comparison_sub_op = 'pastNumberOfDays' filter.comparison_sub_op = 'pastNumberOfDays'
} else { } else {

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

@ -84,10 +84,11 @@ const checkTypeFunctions = {
type FilterType = keyof typeof checkTypeFunctions type FilterType = keyof typeof checkTypeFunctions
// todo: replace with sqlUis const { sqlUis } = storeToRefs(useProject())
const { sqlUi } = $(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 checkType = (filterType: FilterType) => {
const checkTypeFunction = checkTypeFunctions[filterType] const checkTypeFunction = checkTypeFunctions[filterType]

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

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

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

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

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

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { OrgUserRoles } from 'nocodb-sdk' import { OrgUserRoles } from 'nocodb-sdk'
import type { RequestParams } from 'nocodb-sdk' import type { ProjectUserReqType, RequestParams } from 'nocodb-sdk'
import { import {
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
message, message,
@ -82,7 +82,7 @@ const inviteUser = async (user: User) => {
user.roles = 'editor' 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 // Successfully added user to project
message.success(t('msg.success.userAddedToProject')) message.success(t('msg.success.userAddedToProject'))
@ -153,7 +153,7 @@ const copyInviteUrl = async (user: User) => {
// Invite URL copied to clipboard // Invite URL copied to clipboard
message.success(t('msg.success.inviteURLCopied')) message.success(t('msg.success.inviteURLCopied'))
} catch (e) { } catch (e: any) {
message.error(e.message) message.error(e.message)
} }
$e('c:user:copy-url') $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! // Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied')) message.success(t('msg.success.shareableURLCopied'))
} catch (e) { } catch (e: any) {
message.error(e.message) message.error(e.message)
} }
@ -137,7 +137,7 @@ style="background: transparent; border: 1px solid #ddd"></iframe>`)
// Copied embeddable html code! // Copied embeddable html code!
message.success(t('msg.success.embeddableHTMLCodeCopied')) message.success(t('msg.success.embeddableHTMLCodeCopied'))
} catch (e) { } catch (e: any) {
message.error(e.message) message.error(e.message)
} }
$e('c:shared-base:copy-embed-frame') $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"> <script setup lang="ts">
import type { Input } from 'ant-design-vue' import type { Input } from 'ant-design-vue'
import type { ProjectUserReqType } from 'nocodb-sdk'
import { import {
Form, Form,
computed, computed,
@ -107,10 +108,11 @@ const saveUser = async () => {
const res = await $api.auth.projectUserAdd(project.value.id, { const res = await $api.auth.projectUserAdd(project.value.id, {
roles: usersData.role, roles: usersData.role,
email: usersData.emails, email: usersData.emails,
project_id: project.value.id, } as ProjectUserReqType)
projectName: project.value.title,
}) // for inviting one user, invite_token will only be returned when invitation email fails to send
usersData.invitationToken = res.invite_token // for inviting multiple users, invite_token will be returned anyway
usersData.invitationToken = res?.invite_token
} }
emit('reload') emit('reload')
@ -131,7 +133,7 @@ const copyUrl = async () => {
// Copied shareable base url to clipboard! // Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied')) message.success(t('msg.success.shareableURLCopied'))
} catch (e) { } catch (e: any) {
message.error(e.message) message.error(e.message)
} }
$e('c:shared-base:copy-url') $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(() => { const barcodeMeta = $computed(() => {
return { return {
barcodeFormat: 'CODE128', 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"> <script setup lang="ts">
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { AuditType } from 'nocodb-sdk' import type { AuditType, HookType } from 'nocodb-sdk'
import { import {
Form, Form,
MetaInj, MetaInj,
@ -10,6 +10,7 @@ import {
inject, inject,
message, message,
onMounted, onMounted,
parseProp,
reactive, reactive,
ref, ref,
useApi, useApi,
@ -20,7 +21,7 @@ import {
} from '#imports' } from '#imports'
interface Props { interface Props {
hook?: Record<string, any> hook?: HookType
} }
const props = defineProps<Props>() const props = defineProps<Props>()
@ -39,11 +40,13 @@ const meta = inject(MetaInj, ref())
const useForm = Form.useForm 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: '', id: '',
title: '', title: '',
event: '', event: undefined,
operation: '', operation: undefined,
eventOperation: '', eventOperation: '',
notification: { notification: {
type: 'URL', 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, { Object.assign(hook, {
...newHook, ...newHook,
notification: { notification: {
...newHook.notification, ...notification,
payload: newHook.notification.payload, payload: notification.payload,
}, },
}) })
} }
@ -325,7 +329,7 @@ async function loadPluginList() {
...(p as any), ...(p as any),
} }
plugin.tags = p.tags ? p.tags.split(',') : [] 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 o[plugin.title] = plugin
return o return o
@ -400,8 +404,8 @@ watch(
if (!hook.eventOperation) return if (!hook.eventOperation) return
const [event, operation] = hook.eventOperation.split(' ') const [event, operation] = hook.eventOperation.split(' ')
hook.event = event hook.event = event as HookType['event']
hook.operation = operation hook.operation = operation as HookType['operation']
}, },
) )

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

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

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

@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HookTestReqType, HookType } from 'nocodb-sdk'
import { MetaInj, extractSdkResponseErrorMsg, inject, message, onMounted, ref, useI18n, useNuxtApp, watch } from '#imports' import { MetaInj, extractSdkResponseErrorMsg, inject, message, onMounted, ref, useI18n, useNuxtApp, watch } from '#imports'
interface Props { interface Props {
hook: Record<string, any> hook: HookType
} }
const { hook } = defineProps<Props>() const { hook } = defineProps<Props>()
@ -33,10 +34,13 @@ async function loadSampleData() {
async function testWebhook() { async function testWebhook() {
try { try {
await $api.dbTableWebhook.test(meta.value?.id as string, { await $api.dbTableWebhook.test(
meta.value?.id as string,
{
hook, hook,
payload: sampleData.value, payload: sampleData.value,
}) } as HookTestReqType,
)
// Webhook tested successfully // Webhook tested successfully
message.success(t('msg.success.webhookTested')) 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' import { message, useNuxtApp } from '#imports'
export function useGlobalActions(state: State): Actions { export function useGlobalActions(state: State): Actions {
@ -7,10 +7,8 @@ export function useGlobalActions(state: State): Actions {
state.token.value = null state.token.value = null
state.user.value = null state.user.value = null
try { try {
if (state.token.value) {
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
await nuxtApp.$api.auth.signout() await nuxtApp.$api.auth.signout()
}
} catch {} } catch {}
} }
@ -48,14 +46,14 @@ export function useGlobalActions(state: State): Actions {
message.error(err.message || t('msg.error.youHaveBeenSignedOut')) message.error(err.message || t('msg.error.youHaveBeenSignedOut'))
await signOut() await signOut()
}) })
.finally(resolve) .finally(() => resolve())
}) })
} }
const loadAppInfo = async () => { const loadAppInfo = async () => {
try { try {
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
state.appInfo.value = await nuxtApp.$api.utils.appInfo() state.appInfo.value = (await nuxtApp.$api.utils.appInfo()) as AppInfo
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }

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

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

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

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

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

@ -1,6 +1,6 @@
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import type { WatchStopHandle } from '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' import { extractSdkResponseErrorMsg, storeToRefs, useNuxtApp, useProject, useState, watch } from '#imports'
export function useMetas() { 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 // 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 if (!tableIdOrTitle) return null
/** wait until loading is finished if requesting same meta */ /** wait until loading is finished if requesting same meta */
if (!force && loadingState.value[tableIdOrTitle]) { 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 type { ColumnType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import type { AppInfo } from '~/composables/useGlobal' import type { AppInfo } from '~/composables/useGlobal'
import { parseProp } from '#imports'
export default function convertCellData( export default function convertCellData(
args: { from: UITypes; to: UITypes; value: any; column: ColumnType; appInfo: AppInfo }, args: { from: UITypes; to: UITypes; value: any; column: ColumnType; appInfo: AppInfo },
@ -73,7 +74,7 @@ export default function convertCellData(
case UITypes.Attachment: { case UITypes.Attachment: {
let parsedVal let parsedVal
try { try {
parsedVal = typeof value === 'string' ? JSON.parse(value) : value parsedVal = parseProp(value)
parsedVal = Array.isArray(parsedVal) ? parsedVal : [parsedVal] parsedVal = Array.isArray(parsedVal) ? parsedVal : [parsedVal]
} catch (e) { } catch (e) {
throw new Error('Invalid attachment data') throw new Error('Invalid attachment data')
@ -94,7 +95,7 @@ export default function convertCellData(
const attachmentMeta = { const attachmentMeta = {
...defaultAttachmentMeta, ...defaultAttachmentMeta,
...(typeof args.column?.meta === 'string' ? JSON.parse(args.column.meta) : args.column?.meta), ...parseProp(args.column?.meta),
} }
const attachments = [] const attachments = []

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

@ -10,7 +10,7 @@ import type {
ViewType, ViewType,
} from 'nocodb-sdk' } from 'nocodb-sdk'
import { UITypes, ViewTypes } 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() { export function useSharedView() {
const nestedFilters = ref<(FilterType & { status?: 'update' | 'delete' | 'create'; parentId?: string })[]>([]) const nestedFilters = ref<(FilterType & { status?: 'update' | 'delete' | 'create'; parentId?: string })[]>([])
@ -63,7 +63,7 @@ export function useSharedView() {
}, },
}) })
try { try {
allowCSVDownload.value = (typeof viewMeta.meta === 'string' ? JSON.parse(viewMeta.meta) : viewMeta.meta)?.allowCSVDownload allowCSVDownload.value = parseProp(viewMeta.meta)?.allowCSVDownload
} catch { } catch {
allowCSVDownload.value = false 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 { UITypes, isSystemColumn } from 'nocodb-sdk'
import { import {
Modal, Modal,
@ -40,14 +40,14 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void, baseId?
const createTable = async () => { const createTable = async () => {
if (!sqlUi?.value) return 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')) { if (col.column_name === 'id' && table.columns.includes('id_ag')) {
Object.assign(col, sqlUi?.value?.getDataTypeForUiType({ uidt: UITypes.ID }, 'AG')) Object.assign(col, sqlUi?.value?.getDataTypeForUiType({ uidt: UITypes.ID }, 'AG'))
col.dtxp = sqlUi?.value?.getDefaultLengthForDatatype(col.dt) col.dtxp = sqlUi?.value?.getDefaultLengthForDatatype(col.dt)
col.dtxs = sqlUi?.value?.getDefaultScaleForDatatype(col.dt) col.dtxs = sqlUi?.value?.getDefaultScaleForDatatype(col.dt)
return true return true
} }
return table.columns.includes(col.column_name) return table.columns.includes(col.column_name!)
}) })
try { try {

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

@ -162,10 +162,9 @@ export function useViewFilters(
const placeholderFilter = (): Filter => { const placeholderFilter = (): Filter => {
return { return {
// TODO: fix type
comparison_op: comparisonOpList(options.value?.[0].uidt as UITypes).filter((compOp) => comparison_op: comparisonOpList(options.value?.[0].uidt as UITypes).filter((compOp) =>
isComparisonOpAllowed({ fk_column_id: options.value?.[0].id }, compOp), isComparisonOpAllowed({ fk_column_id: options.value?.[0].id }, compOp),
)?.[0].value, )?.[0].value as FilterType['comparison_op'],
value: '', value: '',
status: 'create', status: 'create',
logical_op: 'and', logical_op: 'and',
@ -281,7 +280,6 @@ export function useViewFilters(
comparison: filter.comparison_op, comparison: filter.comparison_op,
}) })
} else { } else {
// todo: return type of dbTableFilter is void?
filters.value[i] = await $api.dbTableFilter.create(view.value.id!, { filters.value[i] = await $api.dbTableFilter.create(view.value.id!, {
...filter, ...filter,
fk_parent_id: parentId, 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 ActiveCellInj: InjectionKey<Ref<boolean>> = Symbol('active-cell')
export const IsPublicInj: InjectionKey<Ref<boolean>> = Symbol('is-public') export const IsPublicInj: InjectionKey<Ref<boolean>> = Symbol('is-public')
export const RowInj: InjectionKey<Ref<Row>> = Symbol('row') 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 MetaInj: InjectionKey<ComputedRef<TableType>> = Symbol('meta-injection')
export const TabMetaInj: InjectionKey<ComputedRef<TabItem>> = Symbol('tab-meta-injection') export const TabMetaInj: InjectionKey<ComputedRef<TabItem>> = Symbol('tab-meta-injection')
export const PaginationDataInj: InjectionKey<ReturnType<typeof useViewData>['paginationData']> = 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 { I18n } from 'vue-i18n'
import type { Theme as AntTheme } from 'ant-design-vue/es/config-provider' import type { Theme as AntTheme } from 'ant-design-vue/es/config-provider'
import type { UploadFile } from 'ant-design-vue' import type { UploadFile } from 'ant-design-vue'
@ -21,7 +21,7 @@ export interface ProjectMetaInfo {
Platform?: string Platform?: string
Docker?: boolean Docker?: boolean
Database?: string Database?: string
ProjectOnRootDB?: string ProjectOnRootDB?: boolean
RootDB?: string RootDB?: string
PackageVersion?: string PackageVersion?: string
} }
@ -79,7 +79,7 @@ export interface TabItem {
viewId?: string viewId?: string
sortsState?: Map<string, any> sortsState?: Map<string, any>
filterState?: Map<string, any> filterState?: Map<string, any>
meta?: Record<string, any> meta?: MetaType
} }
export interface SharedViewMeta extends Record<string, any> { 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) signIn(token)
} catch (e) { } catch (e: any) {
message.error({ content: await extractSdkResponseErrorMsg(e) }) message.error(await extractSdkResponseErrorMsg(e))
} }
const newURL = window.location.href.split('?')[0] 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", "@intlify/vite-plugin-vue-i18n": "^6.0.1",
"@nuxt/image-edge": "^1.0.0-27657146.da85542", "@nuxt/image-edge": "^1.0.0-27657146.da85542",
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",
"@types/d3-scale": "^4.0.3",
"@types/dagre": "^0.7.48", "@types/dagre": "^0.7.48",
"@types/file-saver": "^2.0.5", "@types/file-saver": "^2.0.5",
"@types/leaflet": "^1.9.0", "@types/leaflet": "^1.9.0",
@ -3307,11 +3308,26 @@
"@types/d3-color": "*" "@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": { "node_modules/@types/d3-selection": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz",
"integrity": "sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA==" "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": { "node_modules/@types/d3-zoom": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz",
@ -20323,11 +20339,26 @@
"@types/d3-color": "*" "@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": { "@types/d3-selection": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz",
"integrity": "sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA==" "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": { "@types/d3-zoom": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz", "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", "@intlify/vite-plugin-vue-i18n": "^6.0.1",
"@nuxt/image-edge": "^1.0.0-27657146.da85542", "@nuxt/image-edge": "^1.0.0-27657146.da85542",
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",
"@types/d3-scale": "^4.0.3",
"@types/dagre": "^0.7.48", "@types/dagre": "^0.7.48",
"@types/file-saver": "^2.0.5", "@types/file-saver": "^2.0.5",
"@types/leaflet": "^1.9.0", "@types/leaflet": "^1.9.0",

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

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

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

@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ProjectType } from 'nocodb-sdk'
import type { SelectHandler } from 'ant-design-vue/es/vc-select/Select' import type { SelectHandler } from 'ant-design-vue/es/vc-select/Select'
import type { DefaultConnection, ProjectCreateForm } from '#imports' import type { DefaultConnection, ProjectCreateForm } from '#imports'
import { import {
@ -233,7 +234,7 @@ const createProject = async () => {
const config = { ...formState.dataSource, connection } const config = { ...formState.dataSource, connection }
const result = await api.project.create({ const result = (await api.project.create({
title: formState.title, title: formState.title,
bases: [ bases: [
{ {
@ -244,7 +245,7 @@ const createProject = async () => {
}, },
], ],
external: true, external: true,
}) })) as Partial<ProjectType>
$e('a:project:create:extdb') $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 { Form, Input } from 'ant-design-vue'
import type { RuleObject } from 'ant-design-vue/es/form' import type { RuleObject } from 'ant-design-vue/es/form'
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import type { ProjectType } from 'nocodb-sdk'
import { import {
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
generateUniqueName, generateUniqueName,
@ -44,9 +45,9 @@ const createProject = async () => {
try { try {
creating.value = true creating.value = true
const result = await api.project.create({ const result = (await api.project.create({
title: formState.title, title: formState.title,
}) })) as Partial<ProjectType>
await navigateTo(`/nc/${result.id}`) await navigateTo(`/nc/${result.id}`)
} catch (e: any) { } catch (e: any) {

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

@ -11,6 +11,7 @@ import {
message, message,
navigateTo, navigateTo,
onBeforeMount, onBeforeMount,
parseProp,
projectThemeColors, projectThemeColors,
ref, ref,
themeV2Colors, themeV2Colors,
@ -80,7 +81,7 @@ const handleProjectColor = async (projectId: string, color: string) => {
const project: ProjectType = await $api.project.read(projectId) 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, { await $api.project.update(projectId, {
color, color,
@ -113,7 +114,7 @@ const handleProjectColor = async (projectId: string, color: string) => {
const getProjectPrimary = (project: ProjectType) => { const getProjectPrimary = (project: ProjectType) => {
if (!project) return 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 return meta.theme?.primaryColor || themeV2Colors['royal-blue'].DEFAULT
} }
@ -136,7 +137,7 @@ const copyProjectMeta = async () => {
const aggregatedMetaInfo = await $api.utils.aggregatedMetaInfo() const aggregatedMetaInfo = await $api.utils.aggregatedMetaInfo()
await copy(JSON.stringify(aggregatedMetaInfo)) await copy(JSON.stringify(aggregatedMetaInfo))
message.info('Copied aggregated project meta to clipboard') message.info('Copied aggregated project meta to clipboard')
} catch (e) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) 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) { async function loadProjectMetaInfo(force?: boolean) {
if (!projectMetaInfo.value || force) { 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') { if (data.meta && typeof data.meta === 'string') {
await api.project.update(projectId.value, data) await api.project.update(projectId.value, data)
} else { } 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 { 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[]) => { export const extractPkFromRow = (row: Record<string, any>, columns: ColumnType[]) => {
return ( return (
@ -21,7 +21,7 @@ export async function populateInsertObject({
}: { }: {
meta: TableType meta: TableType
ltarState: Record<string, any> 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> row: Record<string, any>
throwError?: boolean throwError?: boolean
}) { }) {

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

@ -22,3 +22,4 @@ export * from './memStorage'
export * from './browserUtils' export * from './browserUtils'
export * from './geoDataUtils' export * from './geoDataUtils'
export * from './mimeTypeUtils' 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 uidt === UITypes.LinkToAnotherRecord
export const isHm = (column: ColumnType) => 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) => 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) => 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 isLookup = (column: ColumnType) => column.uidt === UITypes.Lookup
export const isRollup = (column: ColumnType) => column.uidt === UITypes.Rollup 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 */ /** Plugin Title */
title: string; title: string;
/** Plugin Input as JSON string */ /** Plugin Input as JSON string */
input: string; input: string | object;
/** @example Email */
category: string;
} }
/** /**
@ -1775,6 +1777,24 @@ export interface ProjectReqType {
title: string; 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 * Model for Project User Request
*/ */
@ -2927,10 +2947,21 @@ export class Api<
* @request POST:/api/v1/db/meta/projects/{projectId}/users * @request POST:/api/v1/db/meta/projects/{projectId}/users
* @response `200` `{ * @response `200` `{
\** \**
* Success Message * Success Message for inviting single email
* @example The user has been invited successfully * @example The user has been invited successfully
*\ *\
msg?: string, 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 }` OK
* @response `400` `{ * @response `400` `{
@ -2947,10 +2978,20 @@ export class Api<
this.request< this.request<
{ {
/** /**
* Success Message * Success Message for inviting single email
* @example The user has been invited successfully * @example The user has been invited successfully
*/ */
msg?: string; 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> */ /** @example BadRequest [Error]: <ERROR MESSAGE> */
@ -3904,16 +3945,20 @@ export class Api<
* @name Update * @name Update
* @summary Update Project * @summary Update Project
* @request PATCH:/api/v1/db/meta/projects/{projectId} * @request PATCH:/api/v1/db/meta/projects/{projectId}
* @response `200` `void` OK * @response `200` `number` OK
* @response `400` `{ * @response `400` `{
\** @example BadRequest [Error]: <ERROR MESSAGE> *\ \** @example BadRequest [Error]: <ERROR MESSAGE> *\
msg: string, msg: string,
}` }`
*/ */
update: (projectId: IdType, data: number, params: RequestParams = {}) => update: (
projectId: IdType,
data: ProjectUpdateReqType,
params: RequestParams = {}
) =>
this.request< this.request<
void, number,
{ {
/** @example BadRequest [Error]: <ERROR MESSAGE> */ /** @example BadRequest [Error]: <ERROR MESSAGE> */
msg: string; msg: string;
@ -3923,6 +3968,7 @@ export class Api<
method: 'PATCH', method: 'PATCH',
body: data, body: data,
type: ContentType.Json, type: ContentType.Json,
format: 'json',
...params, ...params,
}), }),
@ -8319,6 +8365,7 @@ export class Api<
ee?: boolean, ee?: boolean,
ncAttachmentFieldSize?: number, ncAttachmentFieldSize?: number,
ncMaxAttachmentsAllowed?: number, ncMaxAttachmentsAllowed?: number,
isCloud?: boolean,
}` OK }` OK
* @response `400` `{ * @response `400` `{
@ -8347,6 +8394,7 @@ export class Api<
ee?: boolean; ee?: boolean;
ncAttachmentFieldSize?: number; ncAttachmentFieldSize?: number;
ncMaxAttachmentsAllowed?: number; ncMaxAttachmentsAllowed?: number;
isCloud?: boolean;
}, },
{ {
/** @example BadRequest [Error]: <ERROR MESSAGE> */ /** @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 syncMigration from '../meta/helpers/syncMigration';
import { populateMeta, validatePayload } from '../meta/api/helpers'; import { populateMeta, validatePayload } from '../meta/api/helpers';
import { extractPropsAndSanitize } from '../meta/helpers/extractProps'; import { extractPropsAndSanitize } from '../meta/helpers/extractProps';
import type { ProjectReqType } from 'nocodb-sdk'; import type { ProjectReqType, ProjectUpdateReqType } from 'nocodb-sdk';
export async function projectCreate(param: { export async function projectCreate(param: {
project: ProjectReqType; project: ProjectReqType;
@ -136,8 +136,13 @@ export function sanitizeProject(project: any) {
export async function projectUpdate(param: { export async function projectUpdate(param: {
projectId: string; projectId: string;
project: ProjectReqType; project: ProjectUpdateReqType;
}) { }) {
validatePayload(
'swagger.json#/components/schemas/ProjectUpdateReq',
param.project
);
const project = await Project.getWithInfo(param.projectId); const project = await Project.getWithInfo(param.projectId);
const data: Partial<Project> = extractPropsAndSanitize( const data: Partial<Project> = extractPropsAndSanitize(

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

@ -1461,16 +1461,75 @@
"x-stoplight": { "x-stoplight": {
"id": "waau9tvy75zsd" "id": "waau9tvy75zsd"
}, },
"description": "Success Message", "description": "Success Message for inviting single email",
"example": "The user has been invited successfully" "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": { "examples": {
"Example 1": { "Inviting a user without any errors": {
"value": { "value": {
"msg": "The user has been invited successfully" "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", "operationId": "project-update",
"responses": { "responses": {
"200": { "200": {
"description": "OK" "description": "OK",
"content": {
"application/json": {
"schema": {
"type": "number",
"example": 1
},
"examples": {
"Example 1": {
"value": 1
}
}
}
}
}, },
"400": { "400": {
"$ref": "#/components/responses/BadRequest" "$ref": "#/components/responses/BadRequest"
@ -2160,11 +2232,15 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "number" "$ref": "#/components/schemas/ProjectUpdateReq"
}, },
"examples": { "examples": {
"Example 1": { "Example 1": {
"value": 1 "value": {
"color": "#24716E",
"meta": null,
"title": "My Project"
}
} }
} }
} }
@ -12211,6 +12287,15 @@
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/PluginTestReq" "$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": { "ncMaxAttachmentsAllowed": {
"type": "number" "type": "number"
},
"isCloud": {
"type": "boolean",
"x-stoplight": {
"id": "bstdkpky2131f"
}
} }
} }
}, },
@ -12650,7 +12741,8 @@
"ncSiteUrl": "http://localhost:8080", "ncSiteUrl": "http://localhost:8080",
"ee": false, "ee": false,
"ncAttachmentFieldSize": 20971520, "ncAttachmentFieldSize": 20971520,
"ncMaxAttachmentsAllowed": 10 "ncMaxAttachmentsAllowed": 10,
"isCloud": false
} }
} }
} }
@ -17211,7 +17303,8 @@
"examples": [ "examples": [
{ {
"title": "Plugin Foo", "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", "title": "Plugin Test Request Model",
@ -17229,12 +17322,20 @@
"description": "Plugin Input as JSON string" "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": { "Project": {
"description": "Model for Project", "description": "Model for Project",
@ -17466,6 +17567,43 @@
"title": "Project Request Model", "title": "Project Request Model",
"type": "object" "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": { "ProjectUserReq": {
"description": "Model for Project User Request", "description": "Model for Project User Request",
"examples": [ "examples": [

Loading…
Cancel
Save