Browse Source

Nc fix/integration minor changes (#9184)

* fix(nc-gui): reduce font weight of connection name col cell

* fix(nc-gui): show spinner in edit source modal while loading integration

* fix(nc-gui): show loading spinner in create source, create/edit connection modal

* fix(nc-gui): monor changes

* chore(nc-gui): lint

* fix(nc-gui): remove extra integration pagemode check condition

* fix(nc-gui): update ds test case

* feat(nc-gui): add AI integration category

* fix: move syncDataType and IntegrationCategoryType enum to noco-sdk

* fix(nc-gui): cleanup unused code

* fix(nc-gui): integration list modal open issue from create source modal

* chore(nc-gui): lint

* fix(nc-gui): prevent unnecessarily load integration api calls

* fix(nc-gui): handle reset integration data on base change

* fix(nc-gui): add missing sync pr changes
pull/9194/head
Ramesh Mane 4 months ago committed by GitHub
parent
commit
5fbab165e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 11
      packages/nc-gui/assets/nc-icons/claude.svg
  2. 11
      packages/nc-gui/assets/nc-icons/groq.svg
  3. 7
      packages/nc-gui/assets/nc-icons/ollama.svg
  4. 10
      packages/nc-gui/assets/nc-icons/openai.svg
  5. 2
      packages/nc-gui/components/dashboard/Sidebar.vue
  6. 4
      packages/nc-gui/components/dashboard/settings/DataSources.vue
  7. 29
      packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
  8. 21
      packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue
  9. 2
      packages/nc-gui/components/general/Gift.vue
  10. 8
      packages/nc-gui/components/general/Overlay.vue
  11. 153
      packages/nc-gui/components/project/SyncDataModal.vue
  12. 14
      packages/nc-gui/components/project/View.vue
  13. 4
      packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue
  14. 25
      packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue
  15. 16
      packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
  16. 4
      packages/nc-gui/composables/useIntegrationsStore.ts
  17. 9
      packages/nc-gui/lang/en.json
  18. 72
      packages/nc-gui/lib/enums.ts
  19. 8
      packages/nc-gui/utils/iconUtils.ts
  20. 47
      packages/nc-gui/utils/syncDataUtils.ts
  21. 74
      packages/nocodb-sdk/src/lib/enums.ts
  22. 2
      tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts
  23. 2
      tests/playwright/pages/Dashboard/ProjectView/SourcePage.ts

11
packages/nc-gui/assets/nc-icons/claude.svg

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_926_22148)">
<path d="M25.4883 0H6.51169C2.91538 0 0 2.93931 0 6.56512V25.4349C0 29.0607 2.91538 32 6.51169 32H25.4883C29.0846 32 32 29.0607 32 25.4349V6.56512C32 2.93931 29.0846 0 25.4883 0Z" fill="#CC9B7A"/>
<path d="M19.9158 9.36133H17.2053L22.1398 22.6378L24.8503 22.638L19.9158 9.36133ZM12.0829 9.36133L7.14844 22.638H9.91437L10.9101 19.85L16.088 19.8498L17.0947 22.638H19.8606L14.9153 9.36133H12.0829ZM11.8172 17.3826L13.4991 12.7138L15.1918 17.3826H11.8172Z" fill="#1F1F1E"/>
</g>
<defs>
<clipPath id="clip0_926_22148">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 748 B

11
packages/nc-gui/assets/nc-icons/groq.svg

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_926_22160)">
<path d="M0 16C0 7.16344 7.16344 0 16 0C24.8366 0 32 7.16344 32 16C32 24.8366 24.8366 32 16 32C7.16344 32 0 24.8366 0 16Z" fill="#F54F35"/>
<path d="M20.3783 7.80069L20.68 8.04268C21.7054 8.96097 22.3846 10.2668 22.6071 11.6216C22.6226 11.9118 22.6307 12.2024 22.6328 12.4931L22.6375 13.01L22.6395 13.5644L22.6422 14.1396C22.6438 14.5411 22.6447 14.9426 22.6453 15.3439C22.6469 15.9553 22.6519 16.5666 22.6568 17.1781C22.6579 17.569 22.6587 17.9598 22.6594 18.3505L22.6654 18.9009C22.6602 20.7663 22.1284 22.3763 20.8385 23.7641C20.1138 24.4375 19.3821 24.9377 18.4678 25.3131L18.0878 25.4811C16.6112 26.0152 14.856 25.8627 13.4106 25.3012C12.612 24.9223 11.9529 24.4781 11.3037 23.8803C11.8892 23.1597 12.4834 22.5439 13.2141 21.9698L13.7017 22.348C14.5888 22.9781 15.4745 23.1776 16.5574 23.0843C17.6654 22.8639 18.5403 22.3675 19.2639 21.4922C19.9284 20.4122 19.9946 19.5464 19.9852 18.2932L19.9871 17.7403C19.9876 17.356 19.9863 16.9718 19.9836 16.5875C19.9804 16.0016 19.9836 15.4157 19.9878 14.8299C19.9873 14.4553 19.9865 14.0808 19.9852 13.7062L19.9892 13.1789C19.9736 11.9059 19.7134 11.0758 18.9255 10.0792C17.8452 9.21029 16.8392 8.772 15.4368 8.8387C14.2703 9.02784 13.3228 9.58696 12.6066 10.5264C12.0156 11.4643 11.759 12.4288 11.9405 13.532C12.2893 14.7335 12.7395 15.7526 13.8509 16.3977C14.7819 16.885 15.585 16.9586 16.6271 16.9849L17.0719 17.0013C17.4311 17.0143 17.7903 17.0247 18.1494 17.0345V19.5818C15.4936 19.6889 13.4346 19.6859 11.3272 17.8547C10.009 16.534 9.20376 14.7423 9.14453 12.8753C9.20726 11.304 9.7702 10.0265 10.6668 8.75592L10.9356 8.34788C13.4844 5.66147 17.5206 5.48205 20.3783 7.80069Z" fill="#FEFBFB"/>
</g>
<defs>
<clipPath id="clip0_926_22160">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

7
packages/nc-gui/assets/nc-icons/ollama.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

10
packages/nc-gui/assets/nc-icons/openai.svg

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_926_22151)">
<path d="M29.688 13.0711C30.4105 10.8953 30.1604 8.51365 29.0017 6.53537C27.2594 3.50262 23.7577 1.9426 20.3378 2.67563C18.4105 0.531872 15.4863 -0.421414 12.6659 0.174614C9.84546 0.770642 7.55695 2.82551 6.66181 5.56571C4.41537 6.0264 2.47648 7.43295 1.34129 9.42545C-0.419968 12.4533 -0.0202085 16.2726 2.32984 18.8702C1.6046 21.045 1.85243 23.427 3.00986 25.4059C4.75431 28.4397 8.25837 29.9996 11.6801 29.2656C13.2021 30.9795 15.3886 31.9546 17.6806 31.9418C21.186 31.9449 24.2916 29.6819 25.3623 26.3441C27.6084 25.8826 29.547 24.4763 30.6829 22.4843C32.4229 19.4618 32.0214 15.6631 29.688 13.0711ZM17.6806 29.8512C16.2815 29.8534 14.9262 29.363 13.8524 28.466L14.0413 28.359L20.4007 24.6881C20.7227 24.4993 20.9213 24.1547 20.9233 23.7814V14.8152L23.6119 16.3705C23.6388 16.3842 23.6575 16.4099 23.6623 16.4397V23.8696C23.6554 27.1703 20.9813 29.8443 17.6806 29.8512ZM4.82324 24.3607C4.12157 23.1491 3.86963 21.7289 4.11174 20.3498L4.30063 20.4632L10.6664 24.134C10.9871 24.3222 11.3846 24.3222 11.7053 24.134L19.4814 19.6509V22.7551C19.48 22.7877 19.4638 22.8178 19.4374 22.8369L12.9961 26.5519C10.1338 28.2008 6.47677 27.2204 4.82324 24.3607ZM3.14838 10.5084C3.85492 9.28902 4.97015 8.35892 6.29662 7.88282V15.4386C6.29174 15.8104 6.48957 16.1554 6.81293 16.339L14.5513 20.8032L11.8627 22.3584C11.8332 22.3741 11.7978 22.3741 11.7683 22.3584L5.33955 18.6498C2.48285 16.994 1.50314 13.3398 3.14838 10.477V10.5084ZM25.2364 15.6401L17.4729 11.1318L20.1552 9.58286C20.1847 9.56719 20.2201 9.56719 20.2496 9.58286L26.6783 13.2978C28.6847 14.4556 29.8423 16.6678 29.6496 18.9763C29.4569 21.2848 27.9485 23.2745 25.7779 24.0836V16.5279C25.7666 16.1572 25.5609 15.8198 25.2364 15.6401ZM27.9124 11.6166L27.7235 11.5033L21.3704 7.80096C21.0477 7.61162 20.6478 7.61162 20.3252 7.80096L12.5553 12.2841V9.17989C12.5519 9.14774 12.5664 9.11633 12.5931 9.09804L19.0218 5.38941C21.0331 4.23069 23.533 4.33882 25.4368 5.66689C27.3406 6.99496 28.3053 9.30368 27.9124 11.5914V11.6166ZM11.0882 17.1197L8.39964 15.5708C8.37247 15.5543 8.35401 15.5267 8.34927 15.4953V8.0843C8.35233 5.76351 9.69616 3.65349 11.7979 2.66933C13.8997 1.68517 16.3809 2.00413 18.1655 3.48788L17.9766 3.59492L11.6171 7.26576C11.2952 7.45462 11.0966 7.79919 11.0945 8.17245L11.0882 17.1197ZM12.549 13.9715L16.0121 11.9755L19.4814 13.9715V17.9635L16.0247 19.9595L12.5553 17.9635L12.549 13.9715Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_926_22151">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

2
packages/nc-gui/components/dashboard/Sidebar.vue

@ -57,7 +57,7 @@ onUnmounted(() => {
<DashboardTreeView v-if="!isWorkspaceLoading" />
</div>
<div v-if="!isSharedBase" class="overflow-auto">
<GeneralGift v-if="!isEeUI"/>
<GeneralGift v-if="!isEeUI" />
<div class="border-t-1 w-full"></div>
<DashboardSidebarBeforeUserInfo />
<DashboardSidebarUserInfo />

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

@ -527,7 +527,7 @@ const handleClickRow = (source: SourceType, tab?: string) => {
{{ source.is_meta || source.is_local ? $t('general.base') : source.alias }}
</NcTooltip>
</div>
<div class="ds-table-col ds-table-integration-name font-medium w-full">
<div class="ds-table-col ds-table-integration-name w-full">
<NcTooltip class="truncate" show-on-truncate-only>
<template #title>
{{ source?.integration_title || '-' }}
@ -537,7 +537,7 @@ const handleClickRow = (source: SourceType, tab?: string) => {
</div>
<div class="ds-table-col ds-table-type">
<NcBadge rounded="lg" class="flex items-center gap-2 px-2 py-1 !h-7 truncate !border-transparent">
<NcBadge rounded="lg" class="flex items-center gap-2 px-0 py-1 !h-7 truncate !border-transparent">
<GeneralBaseLogo :source-type="source.type" class="flex-none !w-4 !h-4" />
<NcTooltip placement="bottom" show-on-truncate-only class="text-sm truncate">
<template #title> {{ clientTypesMap[source.type]?.text || source.type }}</template>

29
packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue

@ -50,6 +50,8 @@ const creatingSource = ref(false)
const advancedOptionsExpansionPanel = ref<string[]>([])
const isLoading = ref<boolean>(false)
const defaultFormState = (client = ClientType.MYSQL) => {
return {
title: '',
@ -314,7 +316,12 @@ watch(
// select and focus title field on load
onMounted(async () => {
await loadIntegrations(true, base.value?.id)
isLoading.value = true
if (!integrations.value.length) {
await loadIntegrations(true, base.value?.id)
}
formState.value.title = await generateUniqueName()
nextTick(() => {
@ -325,6 +332,8 @@ onMounted(async () => {
input?.focus()
}, 500)
})
isLoading.value = false
})
const allowMetaWrite = computed({
@ -371,7 +380,7 @@ const handleAddNewConnection = () => {
}
eventBus.on((event, payload) => {
if (event === IntegrationStoreEvents.INTEGRATION_ADD && pageMode.value === IntegrationsPageMode.ADD && payload?.id) {
if (event === IntegrationStoreEvents.INTEGRATION_ADD && payload?.id) {
formState.value.fk_integration_id = payload.id
until(() => selectedIntegration.value?.id === payload.id)
.toBeTruthy()
@ -442,7 +451,7 @@ const filterIntegrationCategory = (c: IntegrationCategoryItemType) => [Integrati
size="small"
class="nc-extdb-btn-test-connection"
:class="{ 'pointer-events-none': testSuccess }"
:disabled="!selectedIntegration"
:disabled="!selectedIntegration || isLoading"
:loading="testingConnection"
icon-position="right"
@click="testConnection"
@ -461,7 +470,7 @@ const filterIntegrationCategory = (c: IntegrationCategoryItemType) => [Integrati
<NcButton
size="small"
type="primary"
:disabled="!testSuccess || !selectedIntegration"
:disabled="!testSuccess || !selectedIntegration || isLoading"
:loading="creatingSource"
class="nc-extdb-btn-submit"
@click="createSource"
@ -474,7 +483,7 @@ const filterIntegrationCategory = (c: IntegrationCategoryItemType) => [Integrati
</div>
</div>
<div class="h-[calc(100%_-_58px)] flex">
<div class="nc-add-source-left-panel nc-scrollbar-thin">
<div class="nc-add-source-left-panel nc-scrollbar-thin relative">
<div class="create-source bg-white relative flex flex-col gap-2 w-full max-w-[768px]">
<a-form
ref="form"
@ -534,6 +543,7 @@ const filterIntegrationCategory = (c: IntegrationCategoryItemType) => [Integrati
<a-divider style="margin: 4px 0" />
<div
class="px-1.5 flex items-center text-brand-500 text-sm cursor-pointer"
@mousedown.prevent
@click="handleAddNewConnection"
>
<div class="w-full flex items-center gap-2 px-2 py-2 rounded-md hover:bg-gray-100">
@ -707,6 +717,11 @@ const filterIntegrationCategory = (c: IntegrationCategoryItemType) => [Integrati
<WorkspaceIntegrationsTab is-modal :filter-category="filterIntegrationCategory" />
<WorkspaceIntegrationsEditOrAdd load-datasource-info :base-id="baseId" />
</div>
<general-overlay :model-value="isLoading" inline transition class="!bg-opacity-15">
<div class="flex items-center justify-center h-full w-full !bg-white !bg-opacity-85 z-1000">
<a-spin size="large" />
</div>
</general-overlay>
</div>
<div class="nc-add-source-right-panel">
<DashboardSettingsDataSourcesSupportedDocs />
@ -873,4 +888,8 @@ const filterIntegrationCategory = (c: IntegrationCategoryItemType) => [Integrati
max-height: min(calc(100vh - 100px), 1024px) !important;
}
}
.nc-dropdown-ext-db-type {
@apply !z-1000;
}
</style>

21
packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue

@ -50,6 +50,8 @@ const easterEggCount = ref(0)
const advancedOptionsExpansionPanel = ref<string[]>([])
const isLoading = ref<boolean>(false)
const onEasterEgg = () => {
easterEggCount.value += 1
if (easterEggCount.value >= 2) {
@ -277,7 +279,12 @@ watch(
// load source config
onMounted(async () => {
await loadIntegrations(true, base.value?.id)
isLoading.value = true
if (!integrations.value.length) {
await loadIntegrations(true, base.value?.id)
}
if (base.value?.id) {
const definedParameters = ['host', 'port', 'user', 'password', 'database']
@ -302,6 +309,8 @@ onMounted(async () => {
}
updateSSLUse()
}
isLoading.value = false
})
// if searchPath is null/undefined reset it to empty array when necessary
@ -369,7 +378,7 @@ function handleAutoScroll(scroll: boolean, className: string) {
<template>
<div class="edit-source bg-white relative h-full flex flex-col w-full">
<div class="h-full max-h-[calc(100%_-_65px)] flex">
<div class="nc-edit-source-left-panel nc-scrollbar-thin">
<div class="nc-edit-source-left-panel nc-scrollbar-thin relative">
<div class="h-full max-w-[768px] mx-auto">
<a-form
ref="form"
@ -582,6 +591,11 @@ function handleAutoScroll(scroll: boolean, className: string) {
</div>
</a-form>
</div>
<general-overlay :model-value="isLoading" inline transition class="!bg-opacity-15">
<div class="flex items-center justify-center h-full w-full !bg-white !bg-opacity-85 z-1000">
<a-spin size="large" />
</div>
</general-overlay>
</div>
<div class="nc-edit-source-right-panel">
<DashboardSettingsDataSourcesSupportedDocs />
@ -608,6 +622,7 @@ function handleAutoScroll(scroll: boolean, className: string) {
class="nc-extdb-btn-test-connection"
:class="{ 'pointer-events-none': testSuccess }"
:loading="testingConnection"
:disabled="isLoading"
icon-position="right"
@click="testConnection"
>
@ -625,7 +640,7 @@ function handleAutoScroll(scroll: boolean, className: string) {
<NcButton
size="small"
type="primary"
:disabled="!testSuccess"
:disabled="!testSuccess || isLoading"
:loading="editingSource"
class="nc-extdb-btn-submit"
@click="editBase"

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

@ -61,7 +61,7 @@ const closeAndShowAgain = () => {
</div>
<div class="body">We are giving away $100 worth of amazon coupons to our pro open source users!</div>
</div>
<div class="img-wrapper" v-if="!hideImage && !giftBannerDismissedCount">
<div v-if="!hideImage && !giftBannerDismissedCount" class="img-wrapper">
<img src="~assets/img/giftCard.svg" />
</div>

8
packages/nc-gui/components/general/Overlay.vue

@ -46,7 +46,13 @@ export default {
<template>
<teleport :disabled="teleportDisabled || (inline && !target)" :to="target || 'body'">
<Transition :name="transition ? 'fade' : undefined" mode="out-in">
<div v-show="!!vModel" v-bind="$attrs" :style="{ zIndex }" :class="[inline ? 'absolute' : 'fixed']" class="inset-0">
<div
v-show="!!vModel"
v-bind="$attrs"
:style="{ zIndex }"
:class="[inline ? 'absolute' : 'fixed']"
class="inset-0 nc-general-overlay"
>
<slot :is-open="vModel" />
</div>
</Transition>

153
packages/nc-gui/components/project/SyncDataModal.vue

@ -1,153 +0,0 @@
<script lang="ts" setup>
import type { SyncDataType } from '../../lib/enums'
const props = defineProps<{ open: boolean }>()
const emit = defineEmits(['update:open'])
const vOpen = useVModel(props, 'open', emit)
const { syncDataUpvotes, updateSyncDataUpvotes } = useGlobal()
const { $e } = useNuxtApp()
const searchQuery = ref('')
const filteredSyncDataTypes = computed(() =>
syncDataTypes.filter((s) => s.title.toLowerCase().includes(searchQuery.value.toLowerCase())),
)
const upvotesData = computed(() => {
return new Set(syncDataUpvotes.value)
})
const handleUpvote = (syncDataType: SyncDataType) => {
if (upvotesData.value.has(syncDataType)) return
$e(`a:integration-request:${syncDataType}`)
updateSyncDataUpvotes([...syncDataUpvotes.value, syncDataType])
}
</script>
<template>
<NcModal
v-model:visible="vOpen"
centered
size="large"
wrap-class-name="nc-project-sync-data-modal-wrapper"
nc-modal-class-name="!p-0 h-80vh max-h-[864px]"
@keydown.esc="vOpen = false"
>
<div class="h-full flex flex-col overflow-hidden">
<div class="flex items-center justify-between gap-4 p-4 border-b-1 border-gray-200">
<GeneralIcon icon="refresh" class="flex-none h-5 w-5 !text-blue-700" />
<div class="flex-1">
<div class="flex-1 flex items-center gap-3">
<h3 class="my-0 capitalize text-base font-weight-700">
{{ $t('labels.syncData') }}
</h3>
<NcBadge :border="false" class="text-brand-500 !h-5.5 bg-brand-50 text-sm px-2">{{
$t('msg.toast.futureRelease')
}}</NcBadge>
</div>
<div class="text-xs text-gray-600">{{ $t('labels.syncDataModalSubtitle') }}</div>
</div>
<NcButton type="text" size="xs" class="!px-0" @click="vOpen = false">
<GeneralIcon icon="close" />
</NcButton>
</div>
<div class="p-6">
<div class="flex items-center justify-end gap-3 max-w-[918px] mx-auto">
<a-input
v-model:value="searchQuery"
type="text"
class="nc-search-sync-data-input !min-w-[300px] !max-w-[300px] nc-input-sm flex-none"
placeholder="Search service"
allow-clear
>
<template #prefix>
<GeneralIcon icon="search" class="mr-2 h-4 w-4 text-gray-500" />
</template>
</a-input>
</div>
</div>
<div class="flex-1 px-6 flex overflow-auto nc-scrollbar-thin">
<div
class="flex flex-col gap-6 w-full max-w-[918px] mx-auto"
:class="{
'flex-1': !filteredSyncDataTypes.length,
}"
>
<div v-if="filteredSyncDataTypes.length" class="flex items-start gap-3 flex-wrap pb-6">
<div v-for="syncData of filteredSyncDataTypes" :key="syncData.value" class="nc-sync-data-card">
<div class="card-icon-wrapper">
<component :is="syncData.icon" class="flex-none stroke-transparent" />
</div>
<div class="card-title flex-1">
{{ $t(syncData.title) }}
</div>
<div>
<NcButton
type="secondary"
size="xsmall"
class="nc-sync-data-upvote-btn !rounded-lg !px-2"
:class="{
selected: upvotesData.has(syncData.value),
}"
@click="handleUpvote(syncData.value)"
>
<div class="flex items-center gap-2">
<GeneralIcon icon="thumbsUpOutline" />
</div>
</NcButton>
</div>
</div>
</div>
<div v-else class="pt-8 flex-1 flex items-center justify-center">
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" class="!my-0" />
</div>
</div>
</div>
</div>
</NcModal>
</template>
<style lang="scss">
.nc-project-sync-data-modal-wrapper {
.nc-modal {
@apply !p-0;
height: min(90vh, 1024px);
max-height: min(90vh, 1024px) !important;
}
.nc-sync-data-title {
@apply text-xl font-semibold;
}
.ant-input-affix-wrapper.nc-search-sync-data-input {
&:not(:has(.ant-input-clear-icon-hidden)):has(.ant-input-clear-icon) {
@apply border-[var(--ant-primary-5)];
}
}
.nc-sync-data-card {
@apply p-3 flex items-center gap-4 rounded-xl border-1 border-gray-200 w-[298px] h-[76px];
.card-icon-wrapper {
@apply w-[52px] h-[52px] p-1 flex items-center justify-center bg-gray-100 rounded-lg;
.card-icon {
}
}
.card-title {
@apply text-base font-weight-700 text-gray-800;
}
.nc-sync-data-upvote-btn {
&.selected {
@apply shadow-selected !text-brand-500 !border-brand-500 !cursor-not-allowed pointer-events-none;
}
}
}
}
</style>

14
packages/nc-gui/components/project/View.vue

@ -6,7 +6,7 @@ const props = defineProps<{
baseId?: string
}>()
useProvideIntegrationViewStore()
const { integrations } = useProvideIntegrationViewStore()
const basesStore = useBases()
@ -94,6 +94,18 @@ watch(
immediate: true,
},
)
watch(
() => currentBase.value?.id,
() => {
/**
* When the current base ID changes, reset the integrations array.
* This ensures that the integration data is cleared, allowing it to be reloaded
* properly when opening the create/edit source modal with the updated base.
*/
integrations.value = []
},
)
</script>
<template>

4
packages/nc-gui/components/workspace/integrations/ConnectionsTab.vue

@ -399,7 +399,7 @@ onKeyStroke('ArrowDown', onDown)
</td>
<td class="cell-type">
<div>
<NcBadge rounded="lg" class="flex items-center gap-2 px-2 py-1 !h-7 truncate !border-transparent">
<NcBadge rounded="lg" class="flex items-center gap-2 px-0 py-1 !h-7 truncate !border-transparent">
<WorkspaceIntegrationsIcon
v-if="integration.sub_type"
:integration-type="integration.sub_type"
@ -687,7 +687,7 @@ onKeyStroke('ArrowDown', onDown)
</div>
<!-- Todo: add link -->
<a target="_blank" rel="noopener noreferrer"> Learn more </a>
<a target="_blank" href="https://docs.nocodb.com/data-sources/connect-to-data-source/" rel="noopener noreferrer"> Learn more </a>
</div>
</div>
</NcModal>

25
packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue

@ -91,25 +91,21 @@ const integrationsMapByCategory = computed(() => {
})
const isEmptyList = computed(() => {
const categories = Object.keys(integrationsMapByCategory.value);
const categories = Object.keys(integrationsMapByCategory.value)
if (!categories.length) {
return true;
return true
}
console.log('cate', categories, integrationsMapByCategory.value)
return !categories.some(category => integrationsMapByCategory.value[category].list.length > 0);
});
return !categories.some((category) => integrationsMapByCategory.value[category].list.length > 0)
})
const isAddNewIntegrationModalOpen = computed({
get: () => {
return pageMode.value === IntegrationsPageMode.LIST
},
set: (value: boolean) => {
if (value) {
pageMode.value = IntegrationsPageMode.LIST
} else {
if (!value) {
pageMode.value = null
}
},
@ -209,9 +205,12 @@ const handleAddIntegration = (category: IntegrationCategoryType, integration: In
ref="integrationListRef"
class="flex-1 px-6 pb-6 flex flex-col nc-workspace-settings-integrations-list overflow-y-auto nc-scrollbar-thin"
>
<div class="w-full flex justify-center" :class="{
'flex-1': isEmptyList
}">
<div
class="w-full flex justify-center"
:class="{
'flex-1': isEmptyList,
}"
>
<div
class="flex flex-col space-y-6 w-full"
:style="{

16
packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue

@ -81,6 +81,8 @@ const useSslExpansionPanel = ref<string[]>([])
const advancedOptionsExpansionPanel = ref<string[]>([])
const isLoading = ref<boolean>(false)
const isDisabledSubmitBtn = computed(() => {
if (isEditMode.value) {
return !testSuccess.value && !isEnabledSaveChangesBtn.value
@ -444,6 +446,8 @@ watch(
// select and focus title field on load
onMounted(async () => {
isLoading.value = true
if (pageMode.value === IntegrationsPageMode.ADD) {
formState.value.title = await generateUniqueName()
} else {
@ -474,6 +478,8 @@ onMounted(async () => {
input?.focus()
}, 500)
})
isLoading.value = false
})
watch(
@ -519,6 +525,7 @@ watch(
class="nc-extdb-btn-test-connection"
:class="{ 'pointer-events-none': testSuccess }"
:loading="testingConnection"
:disabled="isLoading"
icon-position="right"
@click="testConnection"
>
@ -536,7 +543,7 @@ watch(
<NcButton
size="small"
type="primary"
:disabled="isDisabledSubmitBtn"
:disabled="isDisabledSubmitBtn || isLoading"
:loading="creatingSource"
class="nc-extdb-btn-submit"
@click="createOrUpdateIntegration"
@ -550,7 +557,7 @@ watch(
</div>
<div class="h-[calc(100%_-_66px)] flex">
<div class="nc-edit-or-add-integration-left-panel nc-scrollbar-thin">
<div class="nc-edit-or-add-integration-left-panel nc-scrollbar-thin relative">
<div class="w-full gap-8 max-w-[768px]">
<div class="nc-edit-or-add-connection bg-white relative flex flex-col justify-center gap-2 w-full">
<a-form
@ -1100,6 +1107,11 @@ watch(
<div class="mt-10"></div>
</div>
</div>
<general-overlay :model-value="isLoading" inline transition class="!bg-opacity-15">
<div class="flex items-center justify-center h-full w-full !bg-white !bg-opacity-85 z-1000">
<a-spin size="large" />
</div>
</general-overlay>
</div>
<div class="nc-edit-or-add-integration-right-panel">
<template v-if="isEeUI">

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

@ -217,7 +217,9 @@ const [useProvideIntegrationViewStore, _useIntegrationStore] = useInjectionState
await loadIntegrations(loadDatasourceInfo, baseId)
eventBus.emit(IntegrationStoreEvents.INTEGRATION_ADD, response)
if (mode === 'create') {
eventBus.emit(IntegrationStoreEvents.INTEGRATION_ADD, response)
}
pageMode.value = null
activeIntegration.value = null

9
packages/nc-gui/lang/en.json

@ -387,7 +387,11 @@
"bitbucket": "BitBucket",
"quickbooks": "Quickbooks",
"intercom": "Intercom",
"dropbox": "Dropbox"
"dropbox": "Dropbox",
"openai": "OpenAI",
"claude": "Claude",
"ollama": "Ollama",
"groq": "Groq"
},
"integrationCategories": {
"allIntegrations": "All Integrations",
@ -411,7 +415,8 @@
"ticketingSubtitle": "Manage and track support tickets efficiently with NocoDB.",
"storageSubtitle": "Integrate and organize your storage solutions seamlessly with NocoDB.",
"others": "Others",
"othersSubtitle": "Discover additional versatile integrations to enhance your NocoDB experience."
"othersSubtitle": "Discover additional versatile integrations to enhance your NocoDB experience.",
"ai": "AI"
}
},
"datatype": {

72
packages/nc-gui/lib/enums.ts

@ -1,4 +1,4 @@
export { ClientType } from 'nocodb-sdk'
export { ClientType, IntegrationCategoryType, SyncDataType } from 'nocodb-sdk'
export enum Language {
ar = 'العربية',
@ -177,73 +177,3 @@ export enum ExtensionsEvents {
export enum IntegrationStoreEvents {
INTEGRATION_ADD = 'integration-add',
}
// Move this to nocodb-sdk
export enum SyncDataType {
// Database
SNOWFLAKE = 'snowflake',
MICROSOFT_ACCESS = 'microsoft-access',
TABLEAU = 'tableau',
ORACLE = 'oracle',
// Communication
SLACK = 'slack',
DISCORD = 'discord',
TWILLO = 'twillo',
MICROSOFT_OUTLOOK = 'microsoft-outlook',
MICROSOFT_TEAMS = 'microsoft-teams',
TELEGRAM = 'telegram',
GMAIL = 'gmail',
WHATSAPP = 'whatsapp',
// Project Management
ASANA = 'asana',
JIRA = 'jira',
MIRO = 'miro',
TRELLO = 'trello',
// CRM
SALESFORCE = 'salesforce',
PIPEDRIVE = 'pipedrive',
MICROSOFT_DYNAMICS_365 = 'microsoft-dynamics-365',
ZOHO_CRM = 'zoho-crm',
// Marketing
HUBSPOT = 'hubspot',
MAILCHIMP = 'mailchimp',
SURVEYMONKEY = 'surveymonkey',
TYPEFORM = 'typeform',
// ATS
WORKDAY = 'workday',
GREENHOUSE = 'greenhouse',
LEVER = 'lever',
// Development
GITHUB = 'github',
GITLAB = 'gitlab',
BITBUCKET = 'bitbucket',
// Finance
STRIPE = 'stripe',
QUICKBOOKS = 'quickbooks',
// Ticketing
INTERCOM = 'intercom',
ZENDESK = 'zendesk',
// Storage
BOX = 'box',
GOOGLE_DRIVE = 'google-drive',
DROPBOX = 'dropbox',
// Others
APPLE_NUMBERS = 'apple-numbers',
GOOGLE_CALENDAR = 'google-calendar',
MICROSOFT_EXCEL = 'microsoft-excel',
GOOGLE_SHEETS = 'google-sheets',
}
export enum IntegrationCategoryType {
DATABASE = 'database',
COMMUNICATION = 'communication',
PROJECT_MANAGEMENT = 'project-management',
CRM = 'crm',
MARKETING = 'marketing',
ATS = 'ats',
DEVELOPMENT = 'development',
FINANCE = 'finance',
TICKETING = 'ticketing',
STORAGE = 'storage',
OTHERS = 'others',
}

8
packages/nc-gui/utils/iconUtils.ts

@ -277,6 +277,10 @@ import NcBitBucket from '~icons/nc-icons/bit-bucket'
import NcQuickbooks from '~icons/nc-icons/quickbooks'
import NcIntercom from '~icons/nc-icons/intercom'
import NcDropbox from '~icons/nc-icons/dropbox'
import NcOpenai from '~icons/nc-icons/openai'
import NcClaude from '~icons/nc-icons/claude'
import NcOllama from '~icons/nc-icons/ollama'
import NcGroq from '~icons/nc-icons/groq'
// keep it for reference
// todo: remove it after all icons are migrated
@ -777,6 +781,10 @@ export const iconMap = {
intercom: NcIntercom,
dropbox: NcDropbox,
gift: NcIconsGift,
openai: NcOpenai,
claude: NcClaude,
ollama: NcOllama,
groq: NcGroq,
}
export const getMdiIcon = (type: string): any => {

47
packages/nc-gui/utils/syncDataUtils.ts

@ -32,6 +32,16 @@ export const integrationCategories: IntegrationCategoryItemType[] = [
},
isAvailable: true,
},
{
title: 'objects.integrationCategories.ai',
subtitle: 'objects.integrationCategories.ai',
value: IntegrationCategoryType.AI,
icon: iconMap.openai,
iconBgColor: '#FFF0F7',
iconStyle: {
color: '#801044',
},
},
{
title: 'objects.integrationCategories.communication',
subtitle: 'objects.integrationCategories.communicationSubtitle',
@ -179,6 +189,32 @@ export const allIntegrations: IntegrationItemType[] = [
categories: [IntegrationCategoryType.DATABASE],
},
// AI
{
title: 'objects.syncData.openai',
value: SyncDataType.OPENAI,
icon: iconMap.openai,
categories: [IntegrationCategoryType.AI],
},
{
title: 'objects.syncData.claude',
value: SyncDataType.CLAUDE,
icon: iconMap.claude,
categories: [IntegrationCategoryType.AI],
},
{
title: 'objects.syncData.ollama',
value: SyncDataType.OLLAMA,
icon: iconMap.ollama,
categories: [IntegrationCategoryType.AI],
},
{
title: 'objects.syncData.groq',
value: SyncDataType.GROQ,
icon: iconMap.groq,
categories: [IntegrationCategoryType.AI],
},
// Communication
{
title: 'general.slack',
@ -444,14 +480,3 @@ export const allIntegrations: IntegrationItemType[] = [
categories: [IntegrationCategoryType.OTHERS],
},
]
export const syncDataTypes = [] as {
title: string
icon: FunctionalComponent<SVGAttributes, {}, any, {}>
value: SyncDataType
}[]
export const syncDataTypesMap = allIntegrations.reduce((acc, curr) => {
acc[curr.value] = curr
return acc
}, {} as Record<string, IntegrationItemType>)

74
packages/nocodb-sdk/src/lib/enums.ts

@ -353,3 +353,77 @@ export enum SSLUsage {
RequiredWithCa = 'Required-CA',
RequiredWithIdentity = 'Required-Identity',
}
export enum SyncDataType {
// Database
MICROSOFT_ACCESS = 'microsoft-access',
TABLEAU = 'tableau',
ORACLE = 'oracle',
// AI
OPENAI = 'openai',
CLAUDE = 'claude',
OLLAMA = 'ollama',
GROQ = 'groq',
// Communication
SLACK = 'slack',
DISCORD = 'discord',
TWILLO = 'twillo',
MICROSOFT_OUTLOOK = 'microsoft-outlook',
MICROSOFT_TEAMS = 'microsoft-teams',
TELEGRAM = 'telegram',
GMAIL = 'gmail',
WHATSAPP = 'whatsapp',
// Project Management
ASANA = 'asana',
JIRA = 'jira',
MIRO = 'miro',
TRELLO = 'trello',
// CRM
SALESFORCE = 'salesforce',
PIPEDRIVE = 'pipedrive',
MICROSOFT_DYNAMICS_365 = 'microsoft-dynamics-365',
ZOHO_CRM = 'zoho-crm',
// Marketing
HUBSPOT = 'hubspot',
MAILCHIMP = 'mailchimp',
SURVEYMONKEY = 'surveymonkey',
TYPEFORM = 'typeform',
// ATS
WORKDAY = 'workday',
GREENHOUSE = 'greenhouse',
LEVER = 'lever',
// Development
GITHUB = 'github',
GITLAB = 'gitlab',
BITBUCKET = 'bitbucket',
// Finance
STRIPE = 'stripe',
QUICKBOOKS = 'quickbooks',
// Ticketing
INTERCOM = 'intercom',
ZENDESK = 'zendesk',
// Storage
BOX = 'box',
GOOGLE_DRIVE = 'google-drive',
DROPBOX = 'dropbox',
// Others
APPLE_NUMBERS = 'apple-numbers',
GOOGLE_CALENDAR = 'google-calendar',
MICROSOFT_EXCEL = 'microsoft-excel',
GOOGLE_SHEETS = 'google-sheets',
}
export enum IntegrationCategoryType {
DATABASE = 'database',
AI = 'ai',
COMMUNICATION = 'communication',
PROJECT_MANAGEMENT = 'project-management',
CRM = 'crm',
MARKETING = 'marketing',
ATS = 'ats',
DEVELOPMENT = 'development',
FINANCE = 'finance',
TICKETING = 'ticketing',
STORAGE = 'storage',
OTHERS = 'others',
}

2
tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts

@ -95,6 +95,8 @@ export class DataSourcePage extends BasePage {
await this.getDsDetailsModal().waitFor({ state: 'visible' });
await this.getDsDetailsModal().getByTestId('nc-connection-tab').click();
await this.getDsDetailsModal().locator('.nc-general-overlay').first().waitFor({ state: 'hidden' });
}
async openAcl({ dataSourceName = defaultBaseName }: { dataSourceName?: string } = {}) {

2
tests/playwright/pages/Dashboard/ProjectView/SourcePage.ts

@ -20,6 +20,8 @@ export class SourcePage extends BasePage {
async openEditWindow({ sourceName }: { sourceName: string }) {
await this.get().locator('.ds-table-row', { hasText: sourceName }).click();
await this.getDsDetailsModal().getByTestId('nc-connection-tab').click();
await this.getDsDetailsModal().locator('.nc-general-overlay').first().waitFor({ state: 'hidden' });
}
async updateSchemaReadOnly({ sourceName, readOnly }: { sourceName: string; readOnly: boolean }) {

Loading…
Cancel
Save