mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
fix(editor): Add tooltips to workflow history button (#10570)
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
parent
7522dde3d1
commit
4a125f511c
|
@ -1,30 +1,22 @@
|
|||
import WorkflowDetails from '@/components/MainHeader/WorkflowDetails.vue';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import { STORES, WORKFLOW_SHARE_MODAL_KEY } from '@/constants';
|
||||
import { EnterpriseEditionFeature, STORES, WORKFLOW_SHARE_MODAL_KEY } from '@/constants';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
|
||||
vi.mock('vue-router', async () => {
|
||||
const actual = await import('vue-router');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useRoute: () => ({
|
||||
value: {
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
vi.mock('vue-router', () => ({
|
||||
useRoute: () => vi.fn(),
|
||||
useRouter: () => vi.fn(),
|
||||
RouterLink: vi.fn(),
|
||||
}));
|
||||
|
||||
const initialState = {
|
||||
[STORES.SETTINGS]: {
|
||||
settings: {
|
||||
enterprise: {
|
||||
sharing: true,
|
||||
[EnterpriseEditionFeature.Sharing]: true,
|
||||
[EnterpriseEditionFeature.WorkflowHistory]: true,
|
||||
},
|
||||
},
|
||||
areTagsEnabled: true,
|
||||
|
@ -45,21 +37,26 @@ const initialState = {
|
|||
|
||||
const renderComponent = createComponentRenderer(WorkflowDetails, {
|
||||
pinia: createTestingPinia({ initialState }),
|
||||
global: {
|
||||
stubs: {
|
||||
RouterLink: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let uiStore: ReturnType<typeof useUIStore>;
|
||||
const workflow = {
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
tags: ['1', '2'],
|
||||
active: false,
|
||||
};
|
||||
|
||||
describe('WorkflowDetails', () => {
|
||||
beforeEach(() => {
|
||||
uiStore = useUIStore();
|
||||
});
|
||||
it('renders workflow name and tags', async () => {
|
||||
const workflow = {
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
tags: ['1', '2'],
|
||||
};
|
||||
|
||||
const { getByTestId, getByText } = renderComponent({
|
||||
props: {
|
||||
workflow,
|
||||
|
@ -79,11 +76,7 @@ describe('WorkflowDetails', () => {
|
|||
const onSaveButtonClick = vi.fn();
|
||||
const { getByTestId } = renderComponent({
|
||||
props: {
|
||||
workflow: {
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
tags: [],
|
||||
},
|
||||
workflow,
|
||||
readOnly: false,
|
||||
},
|
||||
global: {
|
||||
|
@ -102,11 +95,7 @@ describe('WorkflowDetails', () => {
|
|||
|
||||
const { getByTestId } = renderComponent({
|
||||
props: {
|
||||
workflow: {
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
tags: [],
|
||||
},
|
||||
workflow,
|
||||
readOnly: false,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -21,6 +21,7 @@ import SaveButton from '@/components/SaveButton.vue';
|
|||
import TagsDropdown from '@/components/TagsDropdown.vue';
|
||||
import InlineTextEdit from '@/components/InlineTextEdit.vue';
|
||||
import BreakpointsObserver from '@/components/BreakpointsObserver.vue';
|
||||
import WorkflowHistoryButton from '@/components/MainHeader/WorkflowHistoryButton.vue';
|
||||
|
||||
import { useRootStore } from '@/stores/root.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
|
@ -216,19 +217,6 @@ const isWorkflowHistoryFeatureEnabled = computed(() => {
|
|||
return settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.WorkflowHistory];
|
||||
});
|
||||
|
||||
const workflowHistoryRoute = computed<{ name: string; params: { workflowId: string } }>(() => {
|
||||
return {
|
||||
name: VIEWS.WORKFLOW_HISTORY,
|
||||
params: {
|
||||
workflowId: props.workflow.id,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const isWorkflowHistoryButtonDisabled = computed(() => {
|
||||
return isNewWorkflow.value;
|
||||
});
|
||||
|
||||
const workflowTagIds = computed(() => {
|
||||
return (props.workflow.tags ?? []).map((tag) => (typeof tag === 'string' ? tag : tag.id));
|
||||
});
|
||||
|
@ -588,6 +576,10 @@ function goToUpgrade() {
|
|||
void uiStore.goToUpgrade('workflow_sharing', 'upgrade-workflow-sharing');
|
||||
}
|
||||
|
||||
function goToWorkflowHistoryUpgrade() {
|
||||
void uiStore.goToUpgrade('workflow-history', 'upgrade-workflow-history');
|
||||
}
|
||||
|
||||
function showCreateWorkflowSuccessToast(id?: string) {
|
||||
if (!id || ['new', PLACEHOLDER_EMPTY_WORKFLOW_ID].includes(id)) {
|
||||
let toastTitle = locale.baseText('workflows.create.personal.toast.title');
|
||||
|
@ -732,20 +724,12 @@ function showCreateWorkflowSuccessToast(id?: string) {
|
|||
data-test-id="workflow-save-button"
|
||||
@click="onSaveButtonClick"
|
||||
/>
|
||||
<RouterLink
|
||||
v-if="isWorkflowHistoryFeatureEnabled"
|
||||
:to="workflowHistoryRoute"
|
||||
:class="$style.workflowHistoryButton"
|
||||
>
|
||||
<N8nIconButton
|
||||
:disabled="isWorkflowHistoryButtonDisabled"
|
||||
data-test-id="workflow-history-button"
|
||||
type="tertiary"
|
||||
icon="history"
|
||||
size="medium"
|
||||
text
|
||||
/>
|
||||
</RouterLink>
|
||||
<WorkflowHistoryButton
|
||||
:workflow-id="props.workflow.id"
|
||||
:is-feature-enabled="isWorkflowHistoryFeatureEnabled"
|
||||
:is-new-workflow="isNewWorkflow"
|
||||
@upgrade="goToWorkflowHistoryUpgrade"
|
||||
/>
|
||||
</div>
|
||||
<div :class="[$style.workflowMenuContainer, $style.group]">
|
||||
<input
|
||||
|
@ -848,21 +832,4 @@ $--header-spacing: 20px;
|
|||
.disabledShareButton {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.workflowHistoryButton {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
color: var(--color-text-dark);
|
||||
border-radius: var(--border-radius-base);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-background-base);
|
||||
}
|
||||
|
||||
:disabled {
|
||||
background: transparent;
|
||||
border: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import { within } from '@testing-library/vue';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import WorkflowHistoryButton from '@/components/MainHeader/WorkflowHistoryButton.vue';
|
||||
|
||||
vi.mock('vue-router', () => ({
|
||||
useRoute: () => vi.fn(),
|
||||
useRouter: () => vi.fn(),
|
||||
RouterLink: vi.fn(),
|
||||
}));
|
||||
|
||||
const renderComponent = createComponentRenderer(WorkflowHistoryButton, {
|
||||
global: {
|
||||
stubs: {
|
||||
RouterLink: true,
|
||||
'router-link': {
|
||||
template: '<div><slot /></div>',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('WorkflowHistoryButton', () => {
|
||||
it('should be disabled if the feature is disabled', async () => {
|
||||
const { getByRole, emitted } = renderComponent({
|
||||
props: {
|
||||
workflowId: '1',
|
||||
isNewWorkflow: false,
|
||||
isFeatureEnabled: false,
|
||||
},
|
||||
});
|
||||
expect(getByRole('button')).toBeDisabled();
|
||||
|
||||
await userEvent.hover(getByRole('button'));
|
||||
expect(getByRole('tooltip')).toBeVisible();
|
||||
|
||||
within(getByRole('tooltip')).getByText('View plans').click();
|
||||
|
||||
expect(emitted()).toHaveProperty('upgrade');
|
||||
});
|
||||
|
||||
it('should be disabled if the feature is enabled but the workflow is new', async () => {
|
||||
const { getByRole } = renderComponent({
|
||||
props: {
|
||||
workflowId: '1',
|
||||
isNewWorkflow: true,
|
||||
isFeatureEnabled: true,
|
||||
},
|
||||
});
|
||||
expect(getByRole('button')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should be enabled if the feature is enabled and the workflow is not new', async () => {
|
||||
const { getByRole } = renderComponent({
|
||||
props: {
|
||||
workflowId: '1',
|
||||
isNewWorkflow: false,
|
||||
isFeatureEnabled: true,
|
||||
},
|
||||
});
|
||||
expect(getByRole('button')).toBeEnabled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,73 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { VIEWS } from '@/constants';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
|
||||
const locale = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
workflowId: string;
|
||||
isNewWorkflow: boolean;
|
||||
isFeatureEnabled: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
upgrade: [];
|
||||
}>();
|
||||
|
||||
const workflowHistoryRoute = computed<{ name: string; params: { workflowId: string } }>(() => ({
|
||||
name: VIEWS.WORKFLOW_HISTORY,
|
||||
params: {
|
||||
workflowId: props.workflowId,
|
||||
},
|
||||
}));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<N8nTooltip placement="bottom">
|
||||
<RouterLink :to="workflowHistoryRoute" :class="$style.workflowHistoryButton">
|
||||
<N8nIconButton
|
||||
:disabled="isNewWorkflow || !isFeatureEnabled"
|
||||
data-test-id="workflow-history-button"
|
||||
type="tertiary"
|
||||
icon="history"
|
||||
size="medium"
|
||||
text
|
||||
/>
|
||||
</RouterLink>
|
||||
<template #content>
|
||||
<span v-if="isFeatureEnabled && isNewWorkflow">
|
||||
{{ locale.baseText('workflowHistory.button.tooltip.empty') }}
|
||||
</span>
|
||||
<span v-else-if="isFeatureEnabled">{{
|
||||
locale.baseText('workflowHistory.button.tooltip.enabled')
|
||||
}}</span>
|
||||
<i18n-t v-else keypath="workflowHistory.button.tooltip.disabled">
|
||||
<template #link>
|
||||
<N8nLink size="small" @click="emit('upgrade')">
|
||||
{{ locale.baseText('workflowHistory.button.tooltip.disabled.link') }}
|
||||
</N8nLink>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</template>
|
||||
</N8nTooltip>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.workflowHistoryButton {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
color: var(--color-text-dark);
|
||||
border-radius: var(--border-radius-base);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-background-base);
|
||||
}
|
||||
|
||||
:disabled {
|
||||
background: transparent;
|
||||
border: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -2166,6 +2166,10 @@
|
|||
"workflowHistory.action.restore.success.title": "Successfully restored workflow version",
|
||||
"workflowHistory.action.clone.success.title": "Successfully cloned workflow version",
|
||||
"workflowHistory.action.clone.success.message": "Open cloned workflow in a new tab",
|
||||
"workflowHistory.button.tooltip.empty": "This workflow currently has no history to view. Once you've made your first save, you'll be able to view previous versions",
|
||||
"workflowHistory.button.tooltip.enabled": "Workflow history to view and restore previous versions of your workflows",
|
||||
"workflowHistory.button.tooltip.disabled": "Upgrade to unlock workflow history to view and restore previous versions of your workflows. {link}",
|
||||
"workflowHistory.button.tooltip.disabled.link": "View plans",
|
||||
"workflows.heading": "Workflows",
|
||||
"workflows.add": "Add workflow",
|
||||
"workflows.project.add": "Add workflow to project",
|
||||
|
|
Loading…
Reference in a new issue