diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json index 2804bb0be3..bec3227f1a 100644 --- a/packages/frontend/@n8n/i18n/src/locales/en.json +++ b/packages/frontend/@n8n/i18n/src/locales/en.json @@ -2834,10 +2834,10 @@ "contextual.users.settings.unavailable.button.cloud": "Upgrade now", "contextual.feature.unavailable.title": "Available on the Enterprise Plan", "contextual.feature.unavailable.title.cloud": "Available on the Pro Plan", - "dataStore.dataStores": "Data Stores", - "dataStore.empty.label": "You don't have any data stores yet", - "dataStore.empty.description": "Once you create data stores for your projects, they will appear here", - "dataStore.empty.button.label": "Create data store in \"{projectName}\"", + "dataStore.dataStores": "Data Tables", + "dataStore.empty.label": "You don't have any data tables yet", + "dataStore.empty.description": "Once you create data tables for your projects, they will appear here", + "dataStore.empty.button.label": "Create data table in \"{projectName}\"", "dataStore.card.size": "{size}MB", "dataStore.card.column.count": "{count} column | {count} columns", "dataStore.card.row.count": "{count} record | {count} records", @@ -2846,21 +2846,21 @@ "dataStore.sort.nameAsc": "Sort by name (A-Z)", "dataStore.sort.nameDesc": "Sort by name (Z-A)", "dataStore.search.placeholder": "Search", - "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.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", - "dataStore.getDetails.error": "Error fetching data store details", - "dataStore.notFound": "Data store not found", + "dataStore.error.fetching": "Error loading data tables", + "dataStore.add.title": "Create data table", + "dataStore.add.description": "Set up a new data table to organize and manage your data.", + "dataStore.add.button.label": "Create data table", + "dataStore.add.input.name.label": "Data Table Name", + "dataStore.add.input.name.placeholder": "Enter data table name", + "dataStore.add.error": "Error creating data table", + "dataStore.delete.confirm.title": "Delete data table", + "dataStore.delete.confirm.message": "Are you sure you want to delete the data table \"{name}\"? This action cannot be undone.", + "dataStore.delete.error": "Error deleting data table", + "dataStore.rename.error": "Error renaming data table", + "dataStore.getDetails.error": "Error fetching data table details", + "dataStore.notFound": "Data table not found", "dataStore.noColumns.heading": "No columns yet", - "dataStore.noColumns.description": "Add columns to start storing data in this data store.", + "dataStore.noColumns.description": "Add columns to start storing data in this data table.", "dataStore.noColumns.button.label": "Add first column", "dataStore.addColumn.label": "Add Column", "dataStore.addColumn.nameInput.label": "@:_reusableBaseText.name", diff --git a/packages/frontend/editor-ui/src/features/dataStore/DataStoreDetailsView.vue b/packages/frontend/editor-ui/src/features/dataStore/DataStoreDetailsView.vue index d5e6526dae..530e8ce7df 100644 --- a/packages/frontend/editor-ui/src/features/dataStore/DataStoreDetailsView.vue +++ b/packages/frontend/editor-ui/src/features/dataStore/DataStoreDetailsView.vue @@ -44,6 +44,7 @@ const initialize = async () => { const response = await dataStoreStore.fetchOrFindDataStore(props.id, props.projectId); if (response) { dataStore.value = response; + documentTitle.set(`${i18n.baseText('dataStore.dataStores')} > ${response.name}`); } else { await showErrorAndGoBackToList(new Error(i18n.baseText('dataStore.notFound'))); } diff --git a/packages/frontend/editor-ui/src/features/dataStore/DataStoreView.vue b/packages/frontend/editor-ui/src/features/dataStore/DataStoreView.vue index ef9dbb8856..6ddf34be85 100644 --- a/packages/frontend/editor-ui/src/features/dataStore/DataStoreView.vue +++ b/packages/frontend/editor-ui/src/features/dataStore/DataStoreView.vue @@ -120,24 +120,6 @@ const onProjectHeaderAction = (action: string) => { } }; -const onCardRename = async (payload: { dataStore: DataStoreResource }) => { - 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')); - } -}; - onMounted(() => { documentTitle.set(i18n.baseText('dataStore.dataStores')); }); @@ -190,7 +172,6 @@ onMounted(() => { :data-store="data as DataStoreResource" :show-ownership-badge="projectPages.isOverviewSubPage" :read-only="readOnlyEnv" - @rename="onCardRename" /> diff --git a/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreActions.test.ts b/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreActions.test.ts index b05d59ee75..25367619e3 100644 --- a/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreActions.test.ts +++ b/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreActions.test.ts @@ -64,6 +64,7 @@ const renderComponent = createComponentRenderer(DataStoreActions, { props: { dataStore: mockDataStore, isReadOnly: false, + location: 'breadcrumbs', }, }); @@ -236,4 +237,50 @@ describe('DataStoreActions', () => { 'Something went wrong while deleting the data store.', ); }); + + describe('rename action visibility', () => { + it('should show rename action when location is breadcrumbs', async () => { + const { getByTestId, queryByTestId } = renderComponent({ + props: { + dataStore: mockDataStore, + isReadOnly: false, + location: 'breadcrumbs', + }, + pinia: createTestingPinia({ + initialState: {}, + stubActions: false, + }), + }); + + // Click on the action toggle to open dropdown + await userEvent.click(getByTestId('data-store-card-actions')); + expect(getByTestId('action-toggle-dropdown')).toBeInTheDocument(); + + // Check that rename action is present + expect(queryByTestId(`action-${DATA_STORE_CARD_ACTIONS.RENAME}`)).toBeInTheDocument(); + }); + + it('should not show rename action when location is card', async () => { + const { getByTestId, queryByTestId } = renderComponent({ + props: { + dataStore: mockDataStore, + isReadOnly: false, + location: 'card', + }, + pinia: createTestingPinia({ + initialState: {}, + stubActions: false, + }), + }); + + // Click on the action toggle to open dropdown + await userEvent.click(getByTestId('data-store-card-actions')); + expect(getByTestId('action-toggle-dropdown')).toBeInTheDocument(); + + // Check that rename action is NOT present + expect(queryByTestId(`action-${DATA_STORE_CARD_ACTIONS.RENAME}`)).not.toBeInTheDocument(); + // But delete action should still be present + expect(queryByTestId(`action-${DATA_STORE_CARD_ACTIONS.DELETE}`)).toBeInTheDocument(); + }); + }); }); diff --git a/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreActions.vue b/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreActions.vue index ca8927b1c4..876a372ebc 100644 --- a/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreActions.vue +++ b/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreActions.vue @@ -12,6 +12,7 @@ import { useToast } from '@/composables/useToast'; type Props = { dataStore: DataStore; isReadOnly?: boolean; + location: 'card' | 'breadcrumbs'; }; const props = withDefaults(defineProps(), { @@ -34,18 +35,23 @@ const i18n = useI18n(); const message = useMessage(); const toast = useToast(); -const actions = computed>>(() => [ - { - label: i18n.baseText('generic.rename'), - value: DATA_STORE_CARD_ACTIONS.RENAME, - disabled: props.isReadOnly, - }, - { - label: i18n.baseText('generic.delete'), - value: DATA_STORE_CARD_ACTIONS.DELETE, - disabled: props.isReadOnly, - }, -]); +const actions = computed>>(() => { + const availableActions = [ + { + label: i18n.baseText('generic.delete'), + value: DATA_STORE_CARD_ACTIONS.DELETE, + disabled: props.isReadOnly, + }, + ]; + if (props.location === 'breadcrumbs') { + availableActions.unshift({ + label: i18n.baseText('generic.rename'), + value: DATA_STORE_CARD_ACTIONS.RENAME, + disabled: props.isReadOnly, + }); + } + return availableActions; +}); const onAction = async (action: string) => { switch (action) { diff --git a/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreBreadcrumbs.test.ts b/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreBreadcrumbs.test.ts index 15b6da0542..01bdbf1042 100644 --- a/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreBreadcrumbs.test.ts +++ b/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreBreadcrumbs.test.ts @@ -153,7 +153,7 @@ describe('DataStoreBreadcrumbs', () => { const datastoresLink = getByText('Data Stores'); await userEvent.click(datastoresLink); - expect(mockRouter.push).toHaveBeenCalledWith('/projects/project-1/datastores'); + expect(mockRouter.push).toHaveBeenCalledWith('/projects/project-1/datatables'); }); it('should render DataStoreActions component that can trigger navigation', () => { diff --git a/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreBreadcrumbs.vue b/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreBreadcrumbs.vue index 1b08942faf..5c665b7d02 100644 --- a/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreBreadcrumbs.vue +++ b/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreBreadcrumbs.vue @@ -39,7 +39,7 @@ const breadcrumbs = computed(() => { { id: 'datastores', label: i18n.baseText('dataStore.dataStores'), - href: `/projects/${project.value.id}/datastores`, + href: `/projects/${project.value.id}/datatables`, }, ]; }); @@ -118,7 +118,12 @@ watch(
- +
diff --git a/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreCard.test.ts b/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreCard.test.ts index 3e21a3cfb7..c67a384c46 100644 --- a/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreCard.test.ts +++ b/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreCard.test.ts @@ -7,7 +7,7 @@ import type { IUser } from '@n8n/rest-api-client/api/users'; vi.mock('vue-router', () => { const push = vi.fn(); - const resolve = vi.fn().mockReturnValue({ href: '/projects/1/datastores/1' }); + const resolve = vi.fn().mockReturnValue({ href: '/projects/1/datatables/1' }); return { useRouter: vi.fn().mockReturnValue({ push, @@ -55,7 +55,7 @@ const renderComponent = createComponentRenderer(DataStoreCard, { href() { // Generate href from the route object if (this.to && typeof this.to === 'object') { - return `/projects/${this.to.params.projectId}/datastores/${this.to.params.id}`; + return `/projects/${this.to.params.projectId}/datatables/${this.to.params.id}`; } return '#'; }, @@ -83,7 +83,7 @@ describe('DataStoreCard', () => { it('should render data store info correctly', () => { const { getByTestId } = renderComponent(); expect(getByTestId('data-store-card-icon')).toBeInTheDocument(); - expect(getByTestId('datastore-name-input')).toHaveTextContent(DEFAULT_DATA_STORE.name); + expect(getByTestId('data-store-card-name')).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'); @@ -110,7 +110,7 @@ describe('DataStoreCard', () => { expect(link).toBeInTheDocument(); expect(link).toHaveAttribute( 'href', - `/projects/${DEFAULT_DATA_STORE.projectId}/datastores/${DEFAULT_DATA_STORE.id}`, + `/projects/${DEFAULT_DATA_STORE.projectId}/datatables/${DEFAULT_DATA_STORE.id}`, ); }); diff --git a/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreCard.vue b/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreCard.vue index 6d676e85c0..7304670caa 100644 --- a/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreCard.vue +++ b/packages/frontend/editor-ui/src/features/dataStore/components/DataStoreCard.vue @@ -2,7 +2,7 @@ import type { DataStoreResource } from '@/features/dataStore/types'; import { DATA_STORE_DETAILS } from '@/features/dataStore/constants'; import { useI18n } from '@n8n/i18n'; -import { computed, useTemplateRef } from 'vue'; +import { computed } from 'vue'; import DataStoreActions from '@/features/dataStore/components/DataStoreActions.vue'; type Props = { @@ -19,16 +19,6 @@ const props = withDefaults(defineProps(), { showOwnershipBadge: false, }); -const emit = defineEmits<{ - rename: [ - value: { - dataStore: DataStoreResource; - }, - ]; -}>(); - -const renameInput = useTemplateRef<{ forceFocus?: () => void }>('renameInput'); - const dataStoreRoute = computed(() => { return { name: DATA_STORE_DETAILS, @@ -38,24 +28,6 @@ const dataStoreRoute = computed(() => { }, }; }); - -const onRename = () => { - // Focus rename input if the action is rename - // We need this timeout to ensure action toggle is closed before focusing - if (renameInput.value && typeof renameInput.value.forceFocus === 'function') { - setTimeout(() => { - renameInput.value?.forceFocus?.(); - }, 100); - } -}; - -const onNameSubmit = (name: string) => { - if (props.dataStore.name === name) return; - - emit('rename', { - dataStore: { ...props.dataStore, name }, - }); -}; @@ -158,12 +122,6 @@ const onNameSubmit = (name: string) => { } } -.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); diff --git a/packages/frontend/editor-ui/src/features/dataStore/components/dataGrid/AddColumnPopover.vue b/packages/frontend/editor-ui/src/features/dataStore/components/dataGrid/AddColumnPopover.vue index ab5c68b4ac..b1b4ea6360 100644 --- a/packages/frontend/editor-ui/src/features/dataStore/components/dataGrid/AddColumnPopover.vue +++ b/packages/frontend/editor-ui/src/features/dataStore/components/dataGrid/AddColumnPopover.vue @@ -73,7 +73,7 @@ const validateName = () => { } }; -const onInput = debounce(validateName, { debounceTime: 300 }); +const onInput = debounce(validateName, { debounceTime: 100 });