mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Implement row management for data stores (no-changelog) (#18483)
This commit is contained in:
committed by
GitHub
parent
82876b711a
commit
fd94d71b6d
@@ -80,6 +80,7 @@
|
||||
"generic.tryNow": "Try now",
|
||||
"generic.startNow": "Start now",
|
||||
"generic.dismiss": "Dismiss",
|
||||
"generic.saving": "Saving",
|
||||
"generic.unsavedWork.confirmMessage.headline": "Save changes before leaving?",
|
||||
"generic.unsavedWork.confirmMessage.message": "If you don't save, you will lose your changes.",
|
||||
"generic.unsavedWork.confirmMessage.confirmButtonText": "Save",
|
||||
@@ -2870,6 +2871,10 @@
|
||||
"dataStore.addColumn.error": "Error adding column",
|
||||
"dataStore.addColumn.invalidName.error": "Invalid column name",
|
||||
"dataStore.addColumn.invalidName.description": "Only alphanumeric characters and non-leading dashes are allowed for column names",
|
||||
"dataStore.fetchContent.error": "Error fetching data store content",
|
||||
"dataStore.addRow.label": "Add Row",
|
||||
"dataStore.addRow.error": "Error adding row",
|
||||
"dataStore.updateRow.error": "Error updating row",
|
||||
"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>",
|
||||
|
||||
@@ -5,10 +5,11 @@ import { useDataStoreStore } from '@/features/dataStore/dataStore.store';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { DATA_STORE_VIEW } from '@/features/dataStore/constants';
|
||||
import { DATA_STORE_VIEW, MIN_LOADING_TIME } from '@/features/dataStore/constants';
|
||||
import DataStoreBreadcrumbs from '@/features/dataStore/components/DataStoreBreadcrumbs.vue';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import DataStoreTable from './components/dataGrid/DataStoreTable.vue';
|
||||
import { useDebounce } from '@/composables/useDebounce';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
@@ -25,7 +26,9 @@ const documentTitle = useDocumentTitle();
|
||||
const dataStoreStore = useDataStoreStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const dataStore = ref<DataStore | null>(null);
|
||||
const { debounce } = useDebounce();
|
||||
|
||||
const showErrorAndGoBackToList = async (error: unknown) => {
|
||||
if (!(error instanceof Error)) {
|
||||
@@ -51,6 +54,30 @@ const initialize = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Debounce creating new timer slightly if saving is initiated fast in succession
|
||||
const debouncedSetSaving = debounce(
|
||||
(value: boolean) => {
|
||||
saving.value = value;
|
||||
},
|
||||
{ debounceTime: 50, trailing: true },
|
||||
);
|
||||
|
||||
// Debounce hiding the saving indicator so users can see saving state
|
||||
const debouncedHideSaving = debounce(
|
||||
() => {
|
||||
saving.value = false;
|
||||
},
|
||||
{ debounceTime: MIN_LOADING_TIME, trailing: true },
|
||||
);
|
||||
|
||||
const onToggleSave = (value: boolean) => {
|
||||
if (value) {
|
||||
debouncedSetSaving(true);
|
||||
} else {
|
||||
debouncedHideSaving();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
documentTitle.set(i18n.baseText('dataStore.dataStores'));
|
||||
await initialize();
|
||||
@@ -72,9 +99,13 @@ onMounted(async () => {
|
||||
<div v-else-if="dataStore">
|
||||
<div :class="$style.header">
|
||||
<DataStoreBreadcrumbs :data-store="dataStore" />
|
||||
<div v-if="saving" :class="$style.saving">
|
||||
<n8n-spinner />
|
||||
<n8n-text>{{ i18n.baseText('generic.saving') }}...</n8n-text>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.content">
|
||||
<DataStoreTable :data-store="dataStore" />
|
||||
<DataStoreTable :data-store="dataStore" @toggle-save="onToggleSave" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -102,8 +133,15 @@ onMounted(async () => {
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
gap: var(--spacing-l);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.saving {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-3xs);
|
||||
margin-top: var(--spacing-5xs);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,7 +19,8 @@ vi.mock('ag-grid-vue3', () => ({
|
||||
mounted(this: MockComponentInstance) {
|
||||
this.$emit('gridReady', {
|
||||
api: {
|
||||
// Mock API methods
|
||||
refreshHeader: vi.fn(),
|
||||
applyTransaction: vi.fn(),
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -37,6 +38,12 @@ vi.mock('ag-grid-community', () => ({
|
||||
ColumnAutoSizeModule: {},
|
||||
CheckboxEditorModule: {},
|
||||
NumberEditorModule: {},
|
||||
RowSelectionModule: {},
|
||||
RenderApiModule: {},
|
||||
DateEditorModule: {},
|
||||
ClientSideRowModelApiModule: {},
|
||||
ValidationModule: {},
|
||||
UndoRedoEditModule: {},
|
||||
}));
|
||||
|
||||
// Mock the n8n theme
|
||||
|
||||
@@ -15,24 +15,43 @@ import {
|
||||
ColumnAutoSizeModule,
|
||||
CheckboxEditorModule,
|
||||
NumberEditorModule,
|
||||
RowSelectionModule,
|
||||
RenderApiModule,
|
||||
DateEditorModule,
|
||||
ClientSideRowModelApiModule,
|
||||
ValidationModule,
|
||||
UndoRedoEditModule,
|
||||
} from 'ag-grid-community';
|
||||
import type {
|
||||
GridApi,
|
||||
GridReadyEvent,
|
||||
ColDef,
|
||||
RowSelectionOptions,
|
||||
CellValueChangedEvent,
|
||||
ValueGetterParams,
|
||||
} from 'ag-grid-community';
|
||||
import type { GridApi, GridReadyEvent, ColDef } from 'ag-grid-community';
|
||||
import { n8nTheme } from '@/features/dataStore/components/dataGrid/n8nTheme';
|
||||
import AddColumnPopover from '@/features/dataStore/components/dataGrid/AddColumnPopover.vue';
|
||||
import { useDataStoreStore } from '@/features/dataStore/dataStore.store';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { DEFAULT_ID_COLUMN_NAME } from '@/features/dataStore/constants';
|
||||
import { DEFAULT_ID_COLUMN_NAME, NO_TABLE_YET_MESSAGE } from '@/features/dataStore/constants';
|
||||
import { useDataStoreTypes } from '@/features/dataStore/composables/useDataStoreTypes';
|
||||
|
||||
// Register only the modules we actually use
|
||||
ModuleRegistry.registerModules([
|
||||
ValidationModule, // This module allows us to see AG Grid errors in browser console
|
||||
ClientSideRowModelModule,
|
||||
TextEditorModule,
|
||||
LargeTextEditorModule,
|
||||
ColumnAutoSizeModule,
|
||||
CheckboxEditorModule,
|
||||
NumberEditorModule,
|
||||
RowSelectionModule,
|
||||
RenderApiModule,
|
||||
DateEditorModule,
|
||||
ClientSideRowModelApiModule,
|
||||
UndoRedoEditModule,
|
||||
]);
|
||||
|
||||
type Props = {
|
||||
@@ -41,6 +60,10 @@ type Props = {
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggleSave: [value: boolean];
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
const toast = useToast();
|
||||
const dataStoreTypes = useDataStoreTypes();
|
||||
@@ -51,6 +74,13 @@ const dataStoreStore = useDataStoreStore();
|
||||
const gridApi = ref<GridApi | null>(null);
|
||||
const colDefs = ref<ColDef[]>([]);
|
||||
const rowData = ref<DataStoreRow[]>([]);
|
||||
const rowSelection: RowSelectionOptions | 'single' | 'multiple' = {
|
||||
mode: 'singleRow',
|
||||
enableClickSelection: true,
|
||||
checkboxes: false,
|
||||
};
|
||||
|
||||
const contentLoading = ref(false);
|
||||
|
||||
// Shared config for all columns
|
||||
const defaultColumnDef = {
|
||||
@@ -60,22 +90,26 @@ const defaultColumnDef = {
|
||||
};
|
||||
|
||||
// Pagination
|
||||
const pageSizeOptions = [10, 20, 50];
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(20);
|
||||
const pageSizeOptions = ref([10, 20, 50]);
|
||||
const totalItems = ref(0);
|
||||
|
||||
// Data store content
|
||||
const rows = ref<DataStoreRow[]>([]);
|
||||
|
||||
const onGridReady = (params: GridReadyEvent) => {
|
||||
gridApi.value = params.api;
|
||||
};
|
||||
|
||||
const setCurrentPage = (page: number) => {
|
||||
const setCurrentPage = async (page: number) => {
|
||||
currentPage.value = page;
|
||||
await fetchDataStoreContent();
|
||||
};
|
||||
|
||||
const setPageSize = (size: number) => {
|
||||
const setPageSize = async (size: number) => {
|
||||
pageSize.value = size;
|
||||
currentPage.value = 1; // Reset to first page on page size change
|
||||
await fetchDataStoreContent();
|
||||
};
|
||||
|
||||
const onAddColumn = async ({ column }: { column: DataStoreColumnCreatePayload }) => {
|
||||
@@ -101,15 +135,52 @@ const createColumnDef = (col: DataStoreColumn) => {
|
||||
headerName: col.name,
|
||||
editable: col.name !== DEFAULT_ID_COLUMN_NAME,
|
||||
cellDataType: dataStoreTypes.mapToAGCellType(col.type),
|
||||
valueGetter: (params: ValueGetterParams<DataStoreRow>) => {
|
||||
// If the value is null, return null to show empty cell
|
||||
if (params.data?.[col.name] === null || params.data?.[col.name] === undefined) {
|
||||
return null;
|
||||
}
|
||||
// Parse dates
|
||||
if (col.type === 'date') {
|
||||
const value = params.data?.[col.name];
|
||||
if (typeof value === 'string') {
|
||||
return new Date(value);
|
||||
}
|
||||
}
|
||||
return params.data?.[col.name];
|
||||
},
|
||||
};
|
||||
// Enable large text editor for text columns
|
||||
if (col.type === 'string') {
|
||||
columnDef.cellEditor = 'agLargeTextCellEditor';
|
||||
columnDef.cellEditorPopup = true;
|
||||
}
|
||||
// Setup date editor
|
||||
if (col.type === 'date') {
|
||||
columnDef.cellEditor = 'agDateCellEditor';
|
||||
}
|
||||
return columnDef;
|
||||
};
|
||||
|
||||
const onAddRowClick = async () => {
|
||||
try {
|
||||
// Go to last page if we are not there already
|
||||
if (currentPage.value * pageSize.value < totalItems.value) {
|
||||
await setCurrentPage(Math.ceil(totalItems.value / pageSize.value));
|
||||
}
|
||||
const inserted = await dataStoreStore.insertEmptyRow(props.dataStore);
|
||||
if (!inserted) {
|
||||
throw new Error(i18n.baseText('generic.unknownError'));
|
||||
}
|
||||
emit('toggleSave', true);
|
||||
await fetchDataStoreContent();
|
||||
} catch (error) {
|
||||
toast.showError(error, i18n.baseText('dataStore.addRow.error'));
|
||||
} finally {
|
||||
emit('toggleSave', false);
|
||||
}
|
||||
};
|
||||
|
||||
const initColumnDefinitions = () => {
|
||||
colDefs.value = [
|
||||
// Always add the ID column, it's not returned by the back-end but all data stores have it
|
||||
@@ -125,12 +196,54 @@ const initColumnDefinitions = () => {
|
||||
];
|
||||
};
|
||||
|
||||
const initialize = () => {
|
||||
initColumnDefinitions();
|
||||
const onCellValueChanged = async (params: CellValueChangedEvent) => {
|
||||
const { data, api } = params;
|
||||
|
||||
try {
|
||||
emit('toggleSave', true);
|
||||
await dataStoreStore.upsertRow(props.dataStore.id, props.dataStore.projectId, data);
|
||||
} catch (error) {
|
||||
// Revert cell to original value if the update fails
|
||||
api.undoCellEditing();
|
||||
toast.showError(error, i18n.baseText('dataStore.updateRow.error'));
|
||||
} finally {
|
||||
emit('toggleSave', false);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initialize();
|
||||
const fetchDataStoreContent = async () => {
|
||||
try {
|
||||
contentLoading.value = true;
|
||||
const fetchedRows = await dataStoreStore.fetchDataStoreContent(
|
||||
props.dataStore.id,
|
||||
props.dataStore.projectId,
|
||||
currentPage.value,
|
||||
pageSize.value,
|
||||
);
|
||||
rows.value = fetchedRows.data;
|
||||
totalItems.value = fetchedRows.count;
|
||||
rowData.value = rows.value;
|
||||
} catch (error) {
|
||||
// TODO: We currently don't create user tables until user columns or rows are added
|
||||
// so we need to ignore NO_TABLE_YET_MESSAGE error here
|
||||
if ('message' in error && !error.message.includes(NO_TABLE_YET_MESSAGE)) {
|
||||
toast.showError(error, i18n.baseText('dataStore.fetchContent.error'));
|
||||
}
|
||||
} finally {
|
||||
contentLoading.value = false;
|
||||
if (gridApi.value) {
|
||||
gridApi.value.refreshHeader();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const initialize = async () => {
|
||||
initColumnDefinitions();
|
||||
await fetchDataStoreContent();
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await initialize();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -145,7 +258,14 @@ onMounted(() => {
|
||||
:dom-layout="'autoHeight'"
|
||||
:animate-rows="false"
|
||||
:theme="n8nTheme"
|
||||
:loading="contentLoading"
|
||||
:row-selection="rowSelection"
|
||||
:get-row-id="(params) => String(params.data.id)"
|
||||
:single-click-edit="true"
|
||||
:stop-editing-when-cells-lose-focus="true"
|
||||
:undo-redo-cell-editing="true"
|
||||
@grid-ready="onGridReady"
|
||||
@cell-value-changed="onCellValueChanged"
|
||||
/>
|
||||
<AddColumnPopover
|
||||
:data-store="props.dataStore"
|
||||
@@ -154,20 +274,23 @@ onMounted(() => {
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.footer">
|
||||
<n8n-icon-button
|
||||
icon="plus"
|
||||
class="mb-xl"
|
||||
type="secondary"
|
||||
data-test-id="data-store-add-row-button"
|
||||
/>
|
||||
<n8n-tooltip :content="i18n.baseText('dataStore.addRow.label')">
|
||||
<n8n-icon-button
|
||||
data-test-id="data-store-add-row-button"
|
||||
icon="plus"
|
||||
class="mb-xl"
|
||||
type="secondary"
|
||||
@click="onAddRowClick"
|
||||
/>
|
||||
</n8n-tooltip>
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
data-test-id="data-store-content-pagination"
|
||||
background
|
||||
:total="totalItems"
|
||||
:page-sizes="pageSizeOptions"
|
||||
layout="total, prev, pager, next, sizes"
|
||||
data-test-id="data-store-content-pagination"
|
||||
@update:current-page="setCurrentPage"
|
||||
@size-change="setPageSize"
|
||||
/>
|
||||
@@ -199,7 +322,6 @@ onMounted(() => {
|
||||
--ag-font-family: var(--font-family);
|
||||
--ag-font-size: var(--font-size-xs);
|
||||
--ag-row-height: calc(var(--ag-grid-size) * 0.8 + 32px);
|
||||
|
||||
--ag-header-background-color: var(--color-background-base);
|
||||
--ag-header-font-size: var(--font-size-xs);
|
||||
--ag-header-font-weight: var(--font-weight-bold);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { IconName } from '@n8n/design-system/components/N8nIcon/icons';
|
||||
import type { AGGridCellType, DataStoreColumnType } from '@/features/dataStore/datastore.types';
|
||||
import type {
|
||||
AGGridCellType,
|
||||
DataStoreColumnType,
|
||||
DataStoreValue,
|
||||
} from '@/features/dataStore/datastore.types';
|
||||
|
||||
/* eslint-disable id-denylist */
|
||||
const COLUMN_TYPE_ICONS: Record<DataStoreColumnType, IconName> = {
|
||||
@@ -27,8 +31,24 @@ export const useDataStoreTypes = () => {
|
||||
return colType;
|
||||
};
|
||||
|
||||
const getDefaultValueForType = (colType: DataStoreColumnType): DataStoreValue => {
|
||||
switch (colType) {
|
||||
case 'string':
|
||||
return '';
|
||||
case 'number':
|
||||
return 0;
|
||||
case 'boolean':
|
||||
return false;
|
||||
case 'date':
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getIconForType,
|
||||
mapToAGCellType,
|
||||
getDefaultValueForType,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -19,3 +19,7 @@ export const DEFAULT_ID_COLUMN_NAME = 'id';
|
||||
export const MAX_COLUMN_NAME_LENGTH = 128;
|
||||
|
||||
export const COLUMN_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9-]*$/;
|
||||
|
||||
export const NO_TABLE_YET_MESSAGE = 'SQLITE_ERROR: no such table:';
|
||||
|
||||
export const MIN_LOADING_TIME = 500; // ms
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
DataStoreColumnCreatePayload,
|
||||
DataStore,
|
||||
DataStoreColumn,
|
||||
DataStoreRow,
|
||||
} from '@/features/dataStore/datastore.types';
|
||||
|
||||
export const fetchDataStoresApi = async (
|
||||
@@ -17,7 +18,7 @@ export const fetchDataStoresApi = async (
|
||||
filter?: {
|
||||
id?: string | string[];
|
||||
name?: string | string[];
|
||||
projectId?: string | string[];
|
||||
projectId: string | string[];
|
||||
},
|
||||
) => {
|
||||
const apiEndpoint = projectId ? `/projects/${projectId}/data-stores` : '/data-stores-global';
|
||||
@@ -26,8 +27,8 @@ export const fetchDataStoresApi = async (
|
||||
'GET',
|
||||
apiEndpoint,
|
||||
{
|
||||
...options,
|
||||
...(filter ?? {}),
|
||||
options: options ?? undefined,
|
||||
filter: filter ?? undefined,
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -35,7 +36,7 @@ export const fetchDataStoresApi = async (
|
||||
export const createDataStoreApi = async (
|
||||
context: IRestApiContext,
|
||||
name: string,
|
||||
projectId?: string,
|
||||
projectId: string,
|
||||
columns?: DataStoreColumnCreatePayload[],
|
||||
) => {
|
||||
return await makeRestApiRequest<DataStore>(
|
||||
@@ -52,7 +53,7 @@ export const createDataStoreApi = async (
|
||||
export const deleteDataStoreApi = async (
|
||||
context: IRestApiContext,
|
||||
dataStoreId: string,
|
||||
projectId?: string,
|
||||
projectId: string,
|
||||
) => {
|
||||
return await makeRestApiRequest<boolean>(
|
||||
context,
|
||||
@@ -69,7 +70,7 @@ export const updateDataStoreApi = async (
|
||||
context: IRestApiContext,
|
||||
dataStoreId: string,
|
||||
name: string,
|
||||
projectId?: string,
|
||||
projectId: string,
|
||||
) => {
|
||||
return await makeRestApiRequest<DataStore>(
|
||||
context,
|
||||
@@ -96,3 +97,54 @@ export const addDataStoreColumnApi = async (
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const getDataStoreRowsApi = async (
|
||||
context: IRestApiContext,
|
||||
dataStoreId: string,
|
||||
projectId: string,
|
||||
options?: {
|
||||
skip?: number;
|
||||
take?: number;
|
||||
},
|
||||
) => {
|
||||
return await makeRestApiRequest<{
|
||||
count: number;
|
||||
data: DataStoreRow[];
|
||||
}>(context, 'GET', `/projects/${projectId}/data-stores/${dataStoreId}/rows`, {
|
||||
...(options ?? {}),
|
||||
});
|
||||
};
|
||||
|
||||
export const insertDataStoreRowApi = async (
|
||||
context: IRestApiContext,
|
||||
dataStoreId: string,
|
||||
row: DataStoreRow,
|
||||
projectId: string,
|
||||
) => {
|
||||
return await makeRestApiRequest<boolean>(
|
||||
context,
|
||||
'POST',
|
||||
`/projects/${projectId}/data-stores/${dataStoreId}/insert`,
|
||||
{
|
||||
data: [row],
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const upsertDataStoreRowsApi = async (
|
||||
context: IRestApiContext,
|
||||
dataStoreId: string,
|
||||
rows: DataStoreRow[],
|
||||
projectId: string,
|
||||
matchFields: string[] = ['id'],
|
||||
) => {
|
||||
return await makeRestApiRequest<boolean>(
|
||||
context,
|
||||
'POST',
|
||||
`/projects/${projectId}/data-stores/${dataStoreId}/upsert`,
|
||||
{
|
||||
rows,
|
||||
matchFields,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,14 +8,24 @@ import {
|
||||
deleteDataStoreApi,
|
||||
updateDataStoreApi,
|
||||
addDataStoreColumnApi,
|
||||
getDataStoreRowsApi,
|
||||
insertDataStoreRowApi,
|
||||
upsertDataStoreRowsApi,
|
||||
} from '@/features/dataStore/dataStore.api';
|
||||
import type { DataStore, DataStoreColumnCreatePayload } from '@/features/dataStore/datastore.types';
|
||||
import type {
|
||||
DataStore,
|
||||
DataStoreColumnCreatePayload,
|
||||
DataStoreRow,
|
||||
} from '@/features/dataStore/datastore.types';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { useDataStoreTypes } from '@/features/dataStore/composables/useDataStoreTypes';
|
||||
|
||||
export const useDataStoreStore = defineStore(DATA_STORE_STORE, () => {
|
||||
const rootStore = useRootStore();
|
||||
const projectStore = useProjectsStore();
|
||||
|
||||
const dataStoreTypes = useDataStoreTypes();
|
||||
|
||||
const dataStores = ref<DataStore[]>([]);
|
||||
const totalCount = ref(0);
|
||||
|
||||
@@ -28,7 +38,7 @@ export const useDataStoreStore = defineStore(DATA_STORE_STORE, () => {
|
||||
totalCount.value = response.count;
|
||||
};
|
||||
|
||||
const createDataStore = async (name: string, projectId?: string) => {
|
||||
const createDataStore = async (name: string, projectId: string) => {
|
||||
const newStore = await createDataStoreApi(rootStore.restApiContext, name, projectId);
|
||||
if (!newStore.project && projectId) {
|
||||
const project = await projectStore.fetchProject(projectId);
|
||||
@@ -41,7 +51,7 @@ export const useDataStoreStore = defineStore(DATA_STORE_STORE, () => {
|
||||
return newStore;
|
||||
};
|
||||
|
||||
const deleteDataStore = async (datastoreId: string, projectId?: string) => {
|
||||
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);
|
||||
@@ -50,7 +60,7 @@ export const useDataStoreStore = defineStore(DATA_STORE_STORE, () => {
|
||||
return deleted;
|
||||
};
|
||||
|
||||
const updateDataStore = async (datastoreId: string, name: string, projectId?: string) => {
|
||||
const updateDataStore = async (datastoreId: string, name: string, projectId: string) => {
|
||||
const updated = await updateDataStoreApi(
|
||||
rootStore.restApiContext,
|
||||
datastoreId,
|
||||
@@ -68,6 +78,7 @@ export const useDataStoreStore = defineStore(DATA_STORE_STORE, () => {
|
||||
|
||||
const fetchDataStoreDetails = async (datastoreId: string, projectId: string) => {
|
||||
const response = await fetchDataStoresApi(rootStore.restApiContext, projectId, undefined, {
|
||||
projectId,
|
||||
id: datastoreId,
|
||||
});
|
||||
if (response.data.length > 0) {
|
||||
@@ -104,6 +115,36 @@ export const useDataStoreStore = defineStore(DATA_STORE_STORE, () => {
|
||||
return newColumn;
|
||||
};
|
||||
|
||||
const fetchDataStoreContent = async (
|
||||
datastoreId: string,
|
||||
projectId: string,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
) => {
|
||||
return await getDataStoreRowsApi(rootStore.restApiContext, datastoreId, projectId, {
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
});
|
||||
};
|
||||
|
||||
const insertEmptyRow = async (dataStore: DataStore) => {
|
||||
const emptyRow: DataStoreRow = {};
|
||||
dataStore.columns.forEach((column) => {
|
||||
// Set default values based on column type
|
||||
emptyRow[column.name] = dataStoreTypes.getDefaultValueForType(column.type);
|
||||
});
|
||||
return await insertDataStoreRowApi(
|
||||
rootStore.restApiContext,
|
||||
dataStore.id,
|
||||
emptyRow,
|
||||
dataStore.projectId,
|
||||
);
|
||||
};
|
||||
|
||||
const upsertRow = async (dataStoreId: string, projectId: string, row: DataStoreRow) => {
|
||||
return await upsertDataStoreRowsApi(rootStore.restApiContext, dataStoreId, [row], projectId);
|
||||
};
|
||||
|
||||
return {
|
||||
dataStores,
|
||||
totalCount,
|
||||
@@ -114,5 +155,8 @@ export const useDataStoreStore = defineStore(DATA_STORE_STORE, () => {
|
||||
fetchDataStoreDetails,
|
||||
fetchOrFindDataStore,
|
||||
addDataStoreColumn,
|
||||
fetchDataStoreContent,
|
||||
insertEmptyRow,
|
||||
upsertRow,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { DataStoreValue } from '@/features/dataStore/datastore.types';
|
||||
|
||||
export const isDataStoreValue = (value: unknown): value is DataStoreValue => {
|
||||
return (
|
||||
value === null ||
|
||||
typeof value === 'string' ||
|
||||
typeof value === 'number' ||
|
||||
typeof value === 'boolean' ||
|
||||
value instanceof Date
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user