refactor(editor): Migrate header WorkflowDetails to composition api (no-changelog) (#9186)

This commit is contained in:
Alex Grozav 2024-04-29 07:53:42 +03:00 committed by GitHub
parent 442aaba116
commit 1c261f85a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 756 additions and 635 deletions

View file

@ -61,23 +61,13 @@ import { ref, useCssModule, useAttrs, computed } from 'vue';
import { ElDropdown, ElDropdownMenu, ElDropdownItem, type Placement } from 'element-plus';
import N8nIcon from '../N8nIcon';
import { N8nKeyboardShortcut } from '../N8nKeyboardShortcut';
import type { KeyboardShortcut } from '../../types';
import type { ActionDropdownItem } from '../../types';
import type { IconSize } from '@/types/icon';
interface IActionDropdownItem {
id: string;
label: string;
icon?: string;
divided?: boolean;
disabled?: boolean;
shortcut?: KeyboardShortcut;
customClass?: string;
}
const TRIGGER = ['click', 'hover'] as const;
interface ActionDropdownProps {
items: IActionDropdownItem[];
items: ActionDropdownItem[];
placement?: Placement;
activatorIcon?: string;
activatorSize?: IconSize;
@ -99,7 +89,7 @@ const $attrs = useAttrs();
const testIdPrefix = $attrs['data-test-id'];
const $style = useCssModule();
const getItemClasses = (item: IActionDropdownItem): Record<string, boolean> => {
const getItemClasses = (item: ActionDropdownItem): Record<string, boolean> => {
return {
[$style.itemContainer]: true,
[$style.disabled]: !!item.disabled,

View file

@ -127,6 +127,7 @@
border-radius: var.$tag-border-radius;
box-sizing: border-box;
white-space: nowrap;
line-height: 1;
.el-icon.el-tag__close {
border-radius: 50%;
@ -137,9 +138,8 @@
height: 16px;
width: 16px;
line-height: 16px;
vertical-align: middle;
top: -1px;
right: -5px;
margin-top: 0;
margin-right: 0;
&::before {
display: block;

View file

@ -0,0 +1,11 @@
import type { KeyboardShortcut } from '@/types/keyboardshortcut';
export interface ActionDropdownItem {
id: string;
label: string;
icon?: string;
divided?: boolean;
disabled?: boolean;
shortcut?: KeyboardShortcut;
customClass?: string;
}

View file

@ -1,3 +1,4 @@
export * from './action-dropdown';
export * from './button';
export * from './datatable';
export * from './form';

View file

@ -19,3 +19,28 @@ Range.prototype.getClientRects = vi.fn(() => ({
length: 0,
[Symbol.iterator]: vi.fn(),
}));
export class IntersectionObserver {
root = null;
rootMargin = '';
thresholds = [];
disconnect() {
return null;
}
observe() {
return null;
}
takeRecords() {
return [];
}
unobserve() {
return null;
}
}
window.IntersectionObserver = IntersectionObserver;
global.IntersectionObserver = IntersectionObserver;

View file

@ -2,7 +2,7 @@
<div>
<div :class="{ 'main-header': true, expanded: !uiStore.sidebarMenuCollapsed }">
<div v-show="!hideMenuBar" class="top-menu">
<WorkflowDetails :read-only="readOnly" />
<WorkflowDetails v-if="workflow?.name" :workflow="workflow" :read-only="readOnly" />
<TabBar
v-if="onWorkflowPage"
:items="tabBarItems"
@ -27,7 +27,7 @@ import {
STICKY_NODE_TYPE,
VIEWS,
} from '@/constants';
import type { INodeUi, ITabBarItem } from '@/Interface';
import type { INodeUi, ITabBarItem, IWorkflowDb } from '@/Interface';
import { useNDVStore } from '@/stores/ndv.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useUIStore } from '@/stores/ui.store';
@ -75,6 +75,9 @@ export default defineComponent({
hideMenuBar(): boolean {
return Boolean(this.activeNode && this.activeNode.type !== STICKY_NODE_TYPE);
},
workflow(): IWorkflowDb {
return this.workflowsStore.workflow;
},
workflowName(): string {
return this.workflowsStore.workflowName;
},

View file

@ -0,0 +1,115 @@
import WorkflowDetails from '@/components/MainHeader/WorkflowDetails.vue';
import { createComponentRenderer } from '@/__tests__/render';
import { STORES } from '@/constants';
import { createTestingPinia } from '@pinia/testing';
import { fireEvent } from '@testing-library/vue';
vi.mock('vue-router', async () => {
const actual = await import('vue-router');
return {
...actual,
useRoute: () => ({
value: {
params: {
id: '1',
},
},
}),
};
});
const initialState = {
[STORES.SETTINGS]: {
settings: {
enterprise: {
sharing: true,
},
},
areTagsEnabled: true,
},
[STORES.TAGS]: {
tags: {
1: {
id: '1',
name: 'tag1',
},
2: {
id: '2',
name: 'tag2',
},
},
},
};
const renderComponent = createComponentRenderer(WorkflowDetails, {
pinia: createTestingPinia({ initialState }),
});
describe('WorkflowDetails', () => {
it('renders workflow name and tags', async () => {
const workflow = {
id: '1',
name: 'Test Workflow',
tags: ['1', '2'],
};
const { getByTestId, getByText } = renderComponent({
props: {
workflow,
readOnly: false,
},
});
const workflowName = getByTestId('workflow-name-input');
const workflowNameInput = workflowName.querySelector('input');
expect(workflowNameInput).toHaveValue('Test Workflow');
expect(getByText('tag1')).toBeInTheDocument();
expect(getByText('tag2')).toBeInTheDocument();
});
it('calls save function on save button click', async () => {
const onSaveButtonClick = vi.fn();
const { getByTestId } = renderComponent({
props: {
workflow: {
id: '1',
name: 'Test Workflow',
tags: [],
},
readOnly: false,
},
global: {
mocks: {
onSaveButtonClick,
},
},
});
await fireEvent.click(getByTestId('workflow-save-button'));
expect(onSaveButtonClick).toHaveBeenCalled();
});
it('opens share modal on share button click', async () => {
const onShareButtonClick = vi.fn();
const { getByTestId } = renderComponent({
props: {
workflow: {
id: '1',
name: 'Test Workflow',
tags: [],
},
readOnly: false,
},
global: {
mocks: {
onShareButtonClick,
},
},
});
await fireEvent.click(getByTestId('workflow-share-button'));
expect(onShareButtonClick).toHaveBeenCalled();
});
});

View file

@ -318,7 +318,7 @@ export default defineComponent({
}
.el-tag {
padding: 1px var(--spacing-4xs);
padding: var(--spacing-5xs) var(--spacing-4xs);
color: var(--color-text-dark);
background-color: var(--color-background-base);
border-radius: var(--border-radius-base);

View file

@ -1,10 +1,9 @@
import type { XYPosition } from '@/Interface';
import type { ActionDropdownItem, XYPosition } from '@/Interface';
import { NOT_DUPLICATABE_NODE_TYPES, STICKY_NODE_TYPE } from '@/constants';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import type { IActionDropdownItem } from 'n8n-design-system/src/components/N8nActionDropdown/ActionDropdown.vue';
import type { INode, INodeTypeDescription } from 'n8n-workflow';
import { computed, ref, watch } from 'vue';
import { getMousePosition } from '../utils/nodeViewUtils';
@ -34,7 +33,7 @@ export type ContextMenuAction =
const position = ref<XYPosition>([0, 0]);
const isOpen = ref(false);
const target = ref<ContextMenuTarget>({ source: 'canvas' });
const actions = ref<IActionDropdownItem[]>([]);
const actions = ref<ActionDropdownItem[]>([]);
const actionCallback = ref<ContextMenuActionCallback>(() => {});
export const useContextMenu = (onAction: ContextMenuActionCallback = () => {}) => {
@ -147,7 +146,7 @@ export const useContextMenu = (onAction: ContextMenuActionCallback = () => {}) =
...selectionActions,
];
} else {
const menuActions: IActionDropdownItem[] = [
const menuActions: ActionDropdownItem[] = [
!onlyStickies && {
id: 'toggle_activation',
label: nodes.every((node) => node.disabled)
@ -183,7 +182,7 @@ export const useContextMenu = (onAction: ContextMenuActionCallback = () => {}) =
shortcut: { keys: ['Del'] },
disabled: isReadOnly.value,
},
].filter(Boolean) as IActionDropdownItem[];
].filter(Boolean) as ActionDropdownItem[];
if (nodes.length === 1) {
const singleNodeActions = onlyStickies

View file

@ -354,7 +354,7 @@
font-size: 12px;
font-weight: 400;
display: flex;
align-items: flex-end;
align-items: center;
&.is-closable {
overflow-y: hidden;
@ -363,7 +363,7 @@
.el-tag__close {
max-height: 15px;
max-width: 15px;
margin-right: 6px;
margin-left: var(--spacing-4xs);
&:hover {
background-color: $tag-close-background-hover-color !important;