feat(editor): Address data tables UI feedback (no-changelog) (#18566)

Co-authored-by: Milorad FIlipović <milorad@n8n.io>
This commit is contained in:
Svetoslav Dekov
2025-08-25 11:25:28 +02:00
committed by GitHub
parent 01ff2bacc5
commit fb97ec876c
10 changed files with 151 additions and 97 deletions

View File

@@ -44,10 +44,27 @@ const projectTabsSpy = vi.fn().mockReturnValue({
render: vi.fn(),
});
const ProjectCreateResourceStub = {
props: {
actions: Array,
},
template: `
<div>
<div data-test-id="add-resource"><button role="button"></button></div>
<button data-test-id="add-resource-workflow" @click="$emit('action', 'workflow')">Workflow</button>
<button data-test-id="action-credential" @click="$emit('action', 'credential')">Credentials</button>
<div data-test-id="add-resource-actions" >
<button v-for="action in $props.actions" :key="action.value"></button>
</div>
</div>
`,
};
const renderComponent = createComponentRenderer(ProjectHeader, {
global: {
stubs: {
ProjectTabs: projectTabsSpy,
ProjectCreateResource: ProjectCreateResourceStub,
},
},
});
@@ -69,6 +86,7 @@ describe('ProjectHeader', () => {
projectsStore.teamProjectsLimit = -1;
settingsStore.settings.folders = { enabled: false };
settingsStore.isDataStoreFeatureEnabled = true;
// Setup default moduleTabs structure
uiStore.moduleTabs = {
@@ -436,4 +454,21 @@ describe('ProjectHeader', () => {
);
});
});
describe('ProjectCreateResource', () => {
it('should render menu items', () => {
const { getByTestId } = renderComponent();
const actionsContainer = getByTestId('add-resource-actions');
expect(actionsContainer).toBeInTheDocument();
expect(actionsContainer.children).toHaveLength(2);
});
it('should not render datastore menu item if data store feature is disabled', () => {
settingsStore.isDataStoreFeatureEnabled = false;
const { getByTestId } = renderComponent();
const actionsContainer = getByTestId('add-resource-actions');
expect(actionsContainer).toBeInTheDocument();
expect(actionsContainer.children).toHaveLength(1);
});
});
});

View File

@@ -20,12 +20,7 @@ import { type IconName } from '@n8n/design-system/components/N8nIcon/icons';
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;
};
import { PROJECT_DATA_STORES } from '@/features/dataStore/constants';
const route = useRoute();
const router = useRouter();
@@ -37,17 +32,8 @@ 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 => {
@@ -122,6 +108,7 @@ const ACTION_TYPES = {
WORKFLOW: 'workflow',
CREDENTIAL: 'credential',
FOLDER: 'folder',
DATA_STORE: 'dataStore',
} as const;
type ActionTypes = (typeof ACTION_TYPES)[keyof typeof ACTION_TYPES];
@@ -155,16 +142,17 @@ const menu = computed(() => {
});
}
// Append custom actions
if (props.customActions?.length) {
props.customActions.forEach((customAction) => {
items.push({
value: customAction.id,
label: customAction.label,
disabled: customAction.disabled ?? false,
});
if (settingsStore.isDataStoreFeatureEnabled) {
// TODO: this should probably be moved to the module descriptor as a setting
items.push({
value: ACTION_TYPES.DATA_STORE,
label: i18n.baseText('dataStore.add.button.label'),
disabled:
sourceControlStore.preferences.branchReadOnly ||
!getResourcePermissions(homeProject.value?.scopes)?.dataStore?.create,
});
}
return items;
});
@@ -196,6 +184,12 @@ const actions: Record<ActionTypes, (projectId: string) => void> = {
[ACTION_TYPES.FOLDER]: () => {
emit('createFolder');
},
[ACTION_TYPES.DATA_STORE]: (projectId: string) => {
void router.push({
name: PROJECT_DATA_STORES,
params: { projectId, new: 'new' },
});
},
} as const;
const pageType = computed(() => {
@@ -267,12 +261,6 @@ const onSelect = (action: string) => {
return;
}
// Check if this is a custom action
if (!executableAction) {
emit('customActionSelected', action, homeProject.value.id);
return;
}
executableAction(homeProject.value.id);
};
</script>