mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Update data store front-end (no-changelog) (#18000)
This commit is contained in:
committed by
GitHub
parent
acfb79bd97
commit
d6ee6067cf
@@ -105,6 +105,7 @@
|
||||
"generic.rename": "Rename",
|
||||
"generic.missing.permissions": "Missing permissions to perform this action",
|
||||
"generic.shortcutHint": "Or press",
|
||||
"generic.unknownError": "An unknown error occurred",
|
||||
"generic.upgradeToEnterprise": "Upgrade to Enterprise",
|
||||
"generic.never": "Never",
|
||||
"about.aboutN8n": "About n8n",
|
||||
@@ -2817,10 +2818,14 @@
|
||||
"dataStore.error.fetching": "Error loading data stores",
|
||||
"dataStore.add.title": "Create data store",
|
||||
"dataStore.add.description": "Set up a new data store to organize and manage your data.",
|
||||
"dataStore.add.button.label": "Create data store",
|
||||
"dataStore.add.button.label": "Create Data Store",
|
||||
"dataStore.add.input.name.label": "Data Store Name",
|
||||
"dataStore.add.input.name.placeholder": "Enter data store name",
|
||||
"dataStore.add.error": "Error creating data store",
|
||||
"dataStore.delete.confirm.title": "Delete data store",
|
||||
"dataStore.delete.confirm.message": "Are you sure you want to delete the data store \"{name}\"? This action cannot be undone.",
|
||||
"dataStore.delete.error": "Error deleting data store",
|
||||
"dataStore.rename.error": "Error renaming data store",
|
||||
"settings.ldap": "LDAP",
|
||||
"settings.ldap.note": "LDAP allows users to authenticate with their centralized account. It's compatible with services that provide an LDAP interface like Active Directory, Okta and Jumpcloud.",
|
||||
"settings.ldap.infoTip": "Learn more about <a href='https://docs.n8n.io/user-management/ldap/' target='_blank'>LDAP in the Docs</a>",
|
||||
|
||||
@@ -21,6 +21,12 @@ import type { IUser } from 'n8n-workflow';
|
||||
import { type IconOrEmoji, isIconOrEmoji } from '@n8n/design-system/components/N8nIconPicker/types';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
|
||||
export type CustomAction = {
|
||||
id: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
@@ -31,8 +37,17 @@ const uiStore = useUIStore();
|
||||
|
||||
const projectPages = useProjectPages();
|
||||
|
||||
type Props = {
|
||||
customActions?: CustomAction[];
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
customActions: () => [],
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
createFolder: [];
|
||||
customActionSelected: [actionId: string, projectId: string];
|
||||
}>();
|
||||
|
||||
const headerIcon = computed((): IconOrEmoji => {
|
||||
@@ -139,6 +154,17 @@ const menu = computed(() => {
|
||||
!getResourcePermissions(homeProject.value?.scopes).folder.create,
|
||||
});
|
||||
}
|
||||
|
||||
// Append custom actions
|
||||
if (props.customActions?.length) {
|
||||
props.customActions.forEach((customAction) => {
|
||||
items.push({
|
||||
value: customAction.id,
|
||||
label: customAction.label,
|
||||
disabled: customAction.disabled ?? false,
|
||||
});
|
||||
});
|
||||
}
|
||||
return items;
|
||||
});
|
||||
|
||||
@@ -240,6 +266,13 @@ const onSelect = (action: string) => {
|
||||
if (!homeProject.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a custom action
|
||||
if (!executableAction) {
|
||||
emit('customActionSelected', action, homeProject.value.id);
|
||||
return;
|
||||
}
|
||||
|
||||
executableAction(homeProject.value.id);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -100,9 +100,9 @@ const initialState = {
|
||||
const TEST_DATA_STORE: DataStoreResource = {
|
||||
id: '1',
|
||||
name: 'Test Data Store',
|
||||
size: 1024,
|
||||
sizeBytes: 1024,
|
||||
recordCount: 100,
|
||||
columnCount: 5,
|
||||
columns: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
resourceType: 'datastore',
|
||||
@@ -123,7 +123,7 @@ describe('DataStoreView', () => {
|
||||
// Mock dataStore store state
|
||||
dataStoreStore.dataStores = [TEST_DATA_STORE];
|
||||
dataStoreStore.totalCount = 1;
|
||||
dataStoreStore.loadDataStores = vi.fn().mockResolvedValue(undefined);
|
||||
dataStoreStore.fetchDataStores = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
projectsStore.getCurrentProjectId = vi.fn(() => 'test-project');
|
||||
sourceControlStore.isProjectShared = vi.fn(() => false);
|
||||
@@ -134,7 +134,7 @@ describe('DataStoreView', () => {
|
||||
const { getByTestId } = renderComponent({ pinia });
|
||||
await waitAllPromises();
|
||||
|
||||
expect(dataStoreStore.loadDataStores).toHaveBeenCalledWith('test-project', 1, 25);
|
||||
expect(dataStoreStore.fetchDataStores).toHaveBeenCalledWith('test-project', 1, 25);
|
||||
expect(getByTestId('resources-list-wrapper')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -147,7 +147,7 @@ describe('DataStoreView', () => {
|
||||
|
||||
it('should handle initialization error', async () => {
|
||||
const error = new Error('Store Error');
|
||||
dataStoreStore.loadDataStores = vi.fn().mockRejectedValue(error);
|
||||
dataStoreStore.fetchDataStores = vi.fn().mockRejectedValue(error);
|
||||
|
||||
renderComponent({ pinia });
|
||||
await waitAllPromises();
|
||||
@@ -201,7 +201,7 @@ describe('DataStoreView', () => {
|
||||
await waitAllPromises();
|
||||
|
||||
// Clear the initial call
|
||||
dataStoreStore.loadDataStores = vi.fn().mockClear();
|
||||
dataStoreStore.fetchDataStores = vi.fn().mockClear();
|
||||
mockDebounce.callDebounced.mockClear();
|
||||
|
||||
// The component should be rendered and ready to handle pagination
|
||||
@@ -223,7 +223,7 @@ describe('DataStoreView', () => {
|
||||
await waitAllPromises();
|
||||
|
||||
// Initial call should use default page size of 25
|
||||
expect(dataStoreStore.loadDataStores).toHaveBeenCalledWith('test-project', 1, 25);
|
||||
expect(dataStoreStore.fetchDataStores).toHaveBeenCalledWith('test-project', 1, 25);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import ProjectHeader from '@/components/Projects/ProjectHeader.vue';
|
||||
import ProjectHeader, { type CustomAction } from '@/components/Projects/ProjectHeader.vue';
|
||||
import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue';
|
||||
import InsightsSummary from '@/features/insights/components/InsightsSummary.vue';
|
||||
import { useProjectPages } from '@/composables/useProjectPages';
|
||||
@@ -24,6 +24,8 @@ import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useDataStoreStore } from '@/features/dataStore/dataStore.store';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { MODAL_CONFIRM } from '@/constants';
|
||||
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
@@ -31,6 +33,7 @@ const projectPages = useProjectPages();
|
||||
const { callDebounced } = useDebounce();
|
||||
const documentTitle = useDocumentTitle();
|
||||
const toast = useToast();
|
||||
const message = useMessage();
|
||||
|
||||
const dataStoreStore = useDataStoreStore();
|
||||
const insightsStore = useInsightsStore();
|
||||
@@ -42,6 +45,14 @@ const loading = ref(true);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(DEFAULT_DATA_STORE_PAGE_SIZE);
|
||||
|
||||
const customProjectActions = computed<CustomAction[]>(() => [
|
||||
{
|
||||
id: 'add-data-store',
|
||||
label: i18n.baseText('dataStore.add.button.label'),
|
||||
disabled: loading.value || projectPages.isOverviewSubPage,
|
||||
},
|
||||
]);
|
||||
|
||||
const dataStoreResources = computed<DataStoreResource[]>(() =>
|
||||
dataStoreStore.dataStores.map((ds) => {
|
||||
return {
|
||||
@@ -88,11 +99,6 @@ const cardActions = computed<Array<UserAction<IUser>>>(() => [
|
||||
value: DATA_STORE_CARD_ACTIONS.DELETE,
|
||||
disabled: readOnlyEnv.value,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('generic.clear'),
|
||||
value: DATA_STORE_CARD_ACTIONS.CLEAR,
|
||||
disabled: readOnlyEnv.value,
|
||||
},
|
||||
]);
|
||||
|
||||
const initialize = async () => {
|
||||
@@ -101,7 +107,7 @@ const initialize = async () => {
|
||||
? route.params.projectId[0]
|
||||
: route.params.projectId;
|
||||
try {
|
||||
await dataStoreStore.loadDataStores(projectId, currentPage.value, pageSize.value);
|
||||
await dataStoreStore.fetchDataStores(projectId, currentPage.value, pageSize.value);
|
||||
} catch (error) {
|
||||
toast.showError(error, 'Error loading data stores');
|
||||
} finally {
|
||||
@@ -125,6 +131,64 @@ const onAddModalClick = () => {
|
||||
useUIStore().openModal(ADD_DATA_STORE_MODAL_KEY);
|
||||
};
|
||||
|
||||
const onCardAction = async (payload: { action: string; dataStore: DataStoreResource }) => {
|
||||
switch (payload.action) {
|
||||
case DATA_STORE_CARD_ACTIONS.DELETE: {
|
||||
const promptResponse = await message.confirm(
|
||||
i18n.baseText('dataStore.delete.confirm.message', {
|
||||
interpolate: { name: payload.dataStore.name },
|
||||
}),
|
||||
i18n.baseText('dataStore.delete.confirm.title'),
|
||||
{
|
||||
confirmButtonText: i18n.baseText('generic.delete'),
|
||||
cancelButtonText: i18n.baseText('generic.cancel'),
|
||||
},
|
||||
);
|
||||
if (promptResponse === MODAL_CONFIRM) {
|
||||
try {
|
||||
const deleted = await dataStoreStore.deleteDataStore(
|
||||
payload.dataStore.id,
|
||||
payload.dataStore.projectId,
|
||||
);
|
||||
if (!deleted) {
|
||||
toast.showError(
|
||||
new Error(i18n.baseText('generic.unknownError')),
|
||||
i18n.baseText('dataStore.delete.error'),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.showError(error, i18n.baseText('dataStore.delete.error'));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DATA_STORE_CARD_ACTIONS.RENAME: {
|
||||
try {
|
||||
const updated = await dataStoreStore.updateDataStore(
|
||||
payload.dataStore.id,
|
||||
payload.dataStore.name,
|
||||
payload.dataStore.projectId,
|
||||
);
|
||||
if (!updated) {
|
||||
toast.showError(
|
||||
new Error(i18n.baseText('generic.unknownError')),
|
||||
i18n.baseText('dataStore.rename.error'),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.showError(error, i18n.baseText('dataStore.rename.error'));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onProjectHeaderAction = (action: string) => {
|
||||
if (action === 'add-data-store') {
|
||||
useUIStore().openModal(ADD_DATA_STORE_MODAL_KEY);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
documentTitle.set(i18n.baseText('dataStore.tab.label'));
|
||||
});
|
||||
@@ -149,7 +213,10 @@ onMounted(() => {
|
||||
@update:pagination-and-sort="onPaginationUpdate"
|
||||
>
|
||||
<template #header>
|
||||
<ProjectHeader>
|
||||
<ProjectHeader
|
||||
:custom-actions="customProjectActions"
|
||||
@custom-action-selected="onProjectHeaderAction"
|
||||
>
|
||||
<InsightsSummary
|
||||
v-if="projectPages.isOverviewSubPage && insightsStore.isSummaryEnabled"
|
||||
:loading="insightsStore.weeklySummary.isLoading"
|
||||
@@ -165,7 +232,7 @@ onMounted(() => {
|
||||
:description="emptyCalloutDescription"
|
||||
:button-text="emptyCalloutButtonText"
|
||||
button-type="secondary"
|
||||
@click="onAddModalClick"
|
||||
@click:button="onAddModalClick"
|
||||
/>
|
||||
</template>
|
||||
<template #item="{ item: data }">
|
||||
@@ -175,6 +242,7 @@ onMounted(() => {
|
||||
:show-ownership-badge="projectPages.isOverviewSubPage"
|
||||
:actions="cardActions"
|
||||
:read-only="readOnlyEnv"
|
||||
@action="onCardAction"
|
||||
/>
|
||||
</template>
|
||||
</ResourcesListLayout>
|
||||
|
||||
@@ -31,7 +31,7 @@ onMounted(() => {
|
||||
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
await dataStoreStore.createNewDataStore(dataStoreName.value, route.params.projectId as string);
|
||||
await dataStoreStore.createDataStore(dataStoreName.value, route.params.projectId as string);
|
||||
} catch (error) {
|
||||
toast.showError(error, i18n.baseText('dataStore.add.error'));
|
||||
} finally {
|
||||
|
||||
@@ -26,9 +26,9 @@ vi.mock('vue-router', () => {
|
||||
const DEFAULT_DATA_STORE: DataStoreResource = {
|
||||
id: '1',
|
||||
name: 'Test Data Store',
|
||||
size: 1024,
|
||||
sizeBytes: 1024,
|
||||
recordCount: 100,
|
||||
columnCount: 5,
|
||||
columns: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
resourceType: 'datastore',
|
||||
@@ -72,7 +72,7 @@ describe('DataStoreCard', () => {
|
||||
it('should render data store info correctly', () => {
|
||||
const { getByTestId } = renderComponent();
|
||||
expect(getByTestId('data-store-card-icon')).toBeInTheDocument();
|
||||
expect(getByTestId('folder-card-name')).toHaveTextContent(DEFAULT_DATA_STORE.name);
|
||||
expect(getByTestId('datastore-name-input')).toHaveTextContent(DEFAULT_DATA_STORE.name);
|
||||
expect(getByTestId('data-store-card-record-count')).toBeInTheDocument();
|
||||
expect(getByTestId('data-store-card-column-count')).toBeInTheDocument();
|
||||
expect(getByTestId('data-store-card-last-updated')).toHaveTextContent('Last updated');
|
||||
@@ -99,12 +99,12 @@ describe('DataStoreCard', () => {
|
||||
actions: [],
|
||||
},
|
||||
});
|
||||
expect(queryByTestId('folder-card-actions')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('data-store-card-actions')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render action dropdown if actions are provided', () => {
|
||||
const { getByTestId } = renderComponent();
|
||||
expect(getByTestId('folder-card-actions')).toBeInTheDocument();
|
||||
expect(getByTestId('data-store-card-actions')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render correct route to data store details', () => {
|
||||
@@ -113,12 +113,6 @@ describe('DataStoreCard', () => {
|
||||
expect(link).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display size information', () => {
|
||||
const { getByTestId } = renderComponent();
|
||||
const sizeElement = getByTestId('folder-card-folder-count');
|
||||
expect(sizeElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display record count information', () => {
|
||||
const { getByTestId } = renderComponent();
|
||||
const recordCountElement = getByTestId('data-store-card-record-count');
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { IUser, UserAction } from '@/Interface';
|
||||
import type { DataStoreResource } from '@/features/dataStore/types';
|
||||
import { DATA_STORE_DETAILS } from '../constants';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { computed } from 'vue';
|
||||
import { computed, useTemplateRef } from 'vue';
|
||||
|
||||
type Props = {
|
||||
dataStore: DataStoreResource;
|
||||
@@ -20,6 +20,17 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
showOwnershipBadge: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
action: [
|
||||
value: {
|
||||
dataStore: DataStoreResource;
|
||||
action: string;
|
||||
},
|
||||
];
|
||||
}>();
|
||||
|
||||
const renameInput = useTemplateRef('renameInput');
|
||||
|
||||
const dataStoreRoute = computed(() => {
|
||||
return {
|
||||
name: DATA_STORE_DETAILS,
|
||||
@@ -29,6 +40,33 @@ const dataStoreRoute = computed(() => {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const onCardAction = (action: string) => {
|
||||
// Focus rename input if the action is rename
|
||||
// We need this timeout to ensure action toggle is closed before focusing
|
||||
if (action === 'rename') {
|
||||
if (renameInput.value?.forceFocus) {
|
||||
setTimeout(() => {
|
||||
renameInput.value?.forceFocus();
|
||||
}, 100);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Otherwise, emit the action directly
|
||||
emit('action', {
|
||||
dataStore: props.dataStore,
|
||||
action,
|
||||
});
|
||||
};
|
||||
|
||||
const onNameSubmit = (name: string) => {
|
||||
if (props.dataStore.name === name) return;
|
||||
|
||||
emit('action', {
|
||||
dataStore: { ...props.dataStore, name },
|
||||
action: 'rename',
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div data-test-id="data-store-card">
|
||||
@@ -44,10 +82,18 @@ const dataStoreRoute = computed(() => {
|
||||
/>
|
||||
</template>
|
||||
<template #header>
|
||||
<div :class="$style['card-header']">
|
||||
<N8nHeading tag="h2" bold size="small" data-test-id="folder-card-name">
|
||||
{{ props.dataStore.name }}
|
||||
</N8nHeading>
|
||||
<div :class="$style['card-header']" @click.prevent>
|
||||
<N8nInlineTextEdit
|
||||
ref="renameInput"
|
||||
data-test-id="datastore-name-input"
|
||||
:placeholder="i18n.baseText('dataStore.add.input.name.label')"
|
||||
:class="$style['card-name']"
|
||||
:model-value="props.dataStore.name"
|
||||
:max-length="50"
|
||||
:read-only="props.readOnly"
|
||||
:disabled="props.readOnly"
|
||||
@update:model-value="onNameSubmit"
|
||||
/>
|
||||
<N8nBadge v-if="props.readOnly" class="ml-3xs" theme="tertiary" bold>
|
||||
{{ i18n.baseText('workflows.item.readonly') }}
|
||||
</N8nBadge>
|
||||
@@ -55,14 +101,6 @@ const dataStoreRoute = computed(() => {
|
||||
</template>
|
||||
<template #footer>
|
||||
<div :class="$style['card-footer']">
|
||||
<N8nText
|
||||
size="small"
|
||||
color="text-light"
|
||||
:class="[$style['info-cell'], $style['info-cell--size']]"
|
||||
data-test-id="folder-card-folder-count"
|
||||
>
|
||||
{{ i18n.baseText('dataStore.card.size', { interpolate: { size: dataStore.size } }) }}
|
||||
</N8nText>
|
||||
<N8nText
|
||||
size="small"
|
||||
color="text-light"
|
||||
@@ -71,7 +109,7 @@ const dataStoreRoute = computed(() => {
|
||||
>
|
||||
{{
|
||||
i18n.baseText('dataStore.card.row.count', {
|
||||
interpolate: { count: props.dataStore.recordCount },
|
||||
interpolate: { count: props.dataStore.recordCount ?? 0 },
|
||||
})
|
||||
}}
|
||||
</N8nText>
|
||||
@@ -83,7 +121,7 @@ const dataStoreRoute = computed(() => {
|
||||
>
|
||||
{{
|
||||
i18n.baseText('dataStore.card.column.count', {
|
||||
interpolate: { count: props.dataStore.columnCount },
|
||||
interpolate: { count: props.dataStore.columns.length },
|
||||
})
|
||||
}}
|
||||
</N8nText>
|
||||
@@ -113,7 +151,8 @@ const dataStoreRoute = computed(() => {
|
||||
v-if="props.actions.length"
|
||||
:actions="props.actions"
|
||||
theme="dark"
|
||||
data-test-id="folder-card-actions"
|
||||
data-test-id="data-store-card-actions"
|
||||
@action="onCardAction"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -132,6 +171,12 @@ const dataStoreRoute = computed(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.card-name {
|
||||
color: $custom-font-dark;
|
||||
font-size: var(--font-size-m);
|
||||
margin-bottom: var(--spacing-5xs);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-base);
|
||||
|
||||
@@ -2,7 +2,12 @@ import { defineStore } from 'pinia';
|
||||
import { DATA_STORE_STORE } from '@/features/dataStore/constants';
|
||||
import { ref } from 'vue';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { fetchDataStores, createDataStore } from '@/features/dataStore/datastore.api';
|
||||
import {
|
||||
fetchDataStoresApi,
|
||||
createDataStoreApi,
|
||||
deleteDataStoreApi,
|
||||
updateDataStoreApi,
|
||||
} from '@/features/dataStore/datastore.api';
|
||||
import type { DataStoreEntity } from '@/features/dataStore/datastore.types';
|
||||
|
||||
export const useDataStoreStore = defineStore(DATA_STORE_STORE, () => {
|
||||
@@ -11,26 +16,55 @@ export const useDataStoreStore = defineStore(DATA_STORE_STORE, () => {
|
||||
const dataStores = ref<DataStoreEntity[]>([]);
|
||||
const totalCount = ref(0);
|
||||
|
||||
const loadDataStores = async (projectId: string, page: number, pageSize: number) => {
|
||||
const response = await fetchDataStores(rootStore.restApiContext, projectId, {
|
||||
page,
|
||||
pageSize,
|
||||
const fetchDataStores = async (projectId: string, page: number, pageSize: number) => {
|
||||
const response = await fetchDataStoresApi(rootStore.restApiContext, projectId, {
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
});
|
||||
console.log('Data stores fetched:', response);
|
||||
|
||||
dataStores.value = response.data;
|
||||
totalCount.value = response.count;
|
||||
};
|
||||
|
||||
const createNewDataStore = async (name: string, projectId?: string) => {
|
||||
const newStore = await createDataStore(rootStore.restApiContext, name, projectId);
|
||||
dataStores.value.push(newStore.data);
|
||||
const createDataStore = async (name: string, projectId?: string) => {
|
||||
const newStore = await createDataStoreApi(rootStore.restApiContext, name, projectId);
|
||||
dataStores.value.push(newStore);
|
||||
totalCount.value += 1;
|
||||
return newStore;
|
||||
};
|
||||
|
||||
const deleteDataStore = async (datastoreId: string, projectId?: string) => {
|
||||
const deleted = await deleteDataStoreApi(rootStore.restApiContext, datastoreId, projectId);
|
||||
if (deleted) {
|
||||
dataStores.value = dataStores.value.filter((store) => store.id !== datastoreId);
|
||||
totalCount.value -= 1;
|
||||
}
|
||||
return deleted;
|
||||
};
|
||||
|
||||
const updateDataStore = async (datastoreId: string, name: string, projectId?: string) => {
|
||||
const updated = await updateDataStoreApi(
|
||||
rootStore.restApiContext,
|
||||
datastoreId,
|
||||
name,
|
||||
projectId,
|
||||
);
|
||||
if (updated) {
|
||||
const index = dataStores.value.findIndex((store) => store.id === datastoreId);
|
||||
if (index !== -1) {
|
||||
dataStores.value[index] = { ...dataStores.value[index], name };
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
};
|
||||
|
||||
return {
|
||||
dataStores,
|
||||
totalCount,
|
||||
loadDataStores,
|
||||
createNewDataStore,
|
||||
fetchDataStores,
|
||||
createDataStore,
|
||||
deleteDataStore,
|
||||
updateDataStore,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,29 +1,71 @@
|
||||
import { getFullApiResponse } from '@n8n/rest-api-client';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
|
||||
import { type DataStoreEntity } from '@/features/dataStore/datastore.types';
|
||||
|
||||
export const fetchDataStores = async (
|
||||
export const fetchDataStoresApi = async (
|
||||
context: IRestApiContext,
|
||||
projectId?: string,
|
||||
options?: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
skip?: number;
|
||||
take?: number;
|
||||
},
|
||||
) => {
|
||||
return await getFullApiResponse<DataStoreEntity[]>(context, 'GET', '/data-stores', {
|
||||
projectId,
|
||||
options,
|
||||
});
|
||||
const apiEndpoint = projectId ? `/projects/${projectId}/data-stores` : '/data-stores-global';
|
||||
return await makeRestApiRequest<{ count: number; data: DataStoreEntity[] }>(
|
||||
context,
|
||||
'GET',
|
||||
apiEndpoint,
|
||||
{
|
||||
...options,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const createDataStore = async (
|
||||
export const createDataStoreApi = async (
|
||||
context: IRestApiContext,
|
||||
name: string,
|
||||
projectId?: string,
|
||||
) => {
|
||||
return await getFullApiResponse<DataStoreEntity>(context, 'POST', '/data-stores', {
|
||||
name,
|
||||
projectId,
|
||||
});
|
||||
return await makeRestApiRequest<DataStoreEntity>(
|
||||
context,
|
||||
'POST',
|
||||
`/projects/${projectId}/data-stores`,
|
||||
{
|
||||
name,
|
||||
columns: [],
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const deleteDataStoreApi = async (
|
||||
context: IRestApiContext,
|
||||
dataStoreId: string,
|
||||
projectId?: string,
|
||||
) => {
|
||||
return await makeRestApiRequest<boolean>(
|
||||
context,
|
||||
'DELETE',
|
||||
`/projects/${projectId}/data-stores/${dataStoreId}`,
|
||||
{
|
||||
dataStoreId,
|
||||
projectId,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const updateDataStoreApi = async (
|
||||
context: IRestApiContext,
|
||||
dataStoreId: string,
|
||||
name: string,
|
||||
projectId?: string,
|
||||
) => {
|
||||
return await makeRestApiRequest<DataStoreEntity>(
|
||||
context,
|
||||
'PATCH',
|
||||
`/projects/${projectId}/data-stores/${dataStoreId}`,
|
||||
{
|
||||
name,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import type { ProjectSharingData } from 'n8n-workflow';
|
||||
|
||||
export type DataStoreEntity = {
|
||||
id: string;
|
||||
name: string;
|
||||
size: number;
|
||||
sizeBytes: number;
|
||||
recordCount: number;
|
||||
columnCount: number;
|
||||
columns: DataStoreColumnEntity[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
projectId?: string;
|
||||
project?: ProjectSharingData;
|
||||
};
|
||||
|
||||
export type DataStoreColumnEntity = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
index: number;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user