feat(editor): Separate node output execution tooltip from status icon (#11196)

This commit is contained in:
Charlie Kolb 2024-10-21 09:35:23 +02:00 committed by GitHub
parent 321d6deef1
commit cd15e959c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 75 additions and 65 deletions

View file

@ -23,6 +23,7 @@ describe('Manual partial execution', () => {
canvas.actions.openNode('Webhook1'); canvas.actions.openNode('Webhook1');
ndv.getters.nodeRunSuccessIndicator().should('exist'); ndv.getters.nodeRunSuccessIndicator().should('exist');
ndv.getters.nodeRunTooltipIndicator().should('exist');
ndv.getters.outputRunSelector().should('not.exist'); // single run ndv.getters.outputRunSelector().should('not.exist'); // single run
}); });
}); });

View file

@ -133,9 +133,10 @@ describe('NDV', () => {
"An expression here won't work because it uses .item and n8n can't figure out the matching item.", "An expression here won't work because it uses .item and n8n can't figure out the matching item.",
); );
ndv.getters.nodeRunErrorIndicator().should('be.visible'); ndv.getters.nodeRunErrorIndicator().should('be.visible');
ndv.getters.nodeRunTooltipIndicator().should('be.visible');
// The error details should be hidden behind a tooltip // The error details should be hidden behind a tooltip
ndv.getters.nodeRunErrorIndicator().should('not.contain', 'Start Time'); ndv.getters.nodeRunTooltipIndicator().should('not.contain', 'Start Time');
ndv.getters.nodeRunErrorIndicator().should('not.contain', 'Execution Time'); ndv.getters.nodeRunTooltipIndicator().should('not.contain', 'Execution Time');
}); });
it('should save workflow using keyboard shortcut from NDV', () => { it('should save workflow using keyboard shortcut from NDV', () => {
@ -617,8 +618,10 @@ describe('NDV', () => {
// Should not show run info before execution // Should not show run info before execution
ndv.getters.nodeRunSuccessIndicator().should('not.exist'); ndv.getters.nodeRunSuccessIndicator().should('not.exist');
ndv.getters.nodeRunErrorIndicator().should('not.exist'); ndv.getters.nodeRunErrorIndicator().should('not.exist');
ndv.getters.nodeRunTooltipIndicator().should('not.exist');
ndv.getters.nodeExecuteButton().click(); ndv.getters.nodeExecuteButton().click();
ndv.getters.nodeRunSuccessIndicator().should('exist'); ndv.getters.nodeRunSuccessIndicator().should('exist');
ndv.getters.nodeRunTooltipIndicator().should('exist');
}); });
it('should properly show node execution indicator for multiple nodes', () => { it('should properly show node execution indicator for multiple nodes', () => {
@ -630,6 +633,7 @@ describe('NDV', () => {
// Manual tigger node should show success indicator // Manual tigger node should show success indicator
workflowPage.actions.openNode('When clicking Test workflow'); workflowPage.actions.openNode('When clicking Test workflow');
ndv.getters.nodeRunSuccessIndicator().should('exist'); ndv.getters.nodeRunSuccessIndicator().should('exist');
ndv.getters.nodeRunTooltipIndicator().should('exist');
// Code node should show error // Code node should show error
ndv.getters.backToCanvas().click(); ndv.getters.backToCanvas().click();
workflowPage.actions.openNode('Code'); workflowPage.actions.openNode('Code');

View file

@ -130,8 +130,9 @@ export class NDV extends BasePage {
codeEditorFullscreenButton: () => cy.getByTestId('code-editor-fullscreen-button'), codeEditorFullscreenButton: () => cy.getByTestId('code-editor-fullscreen-button'),
codeEditorDialog: () => cy.getByTestId('code-editor-fullscreen'), codeEditorDialog: () => cy.getByTestId('code-editor-fullscreen'),
codeEditorFullscreen: () => this.getters.codeEditorDialog().find('.cm-content'), codeEditorFullscreen: () => this.getters.codeEditorDialog().find('.cm-content'),
nodeRunSuccessIndicator: () => cy.getByTestId('node-run-info-success'), nodeRunTooltipIndicator: () => cy.getByTestId('node-run-info'),
nodeRunErrorIndicator: () => cy.getByTestId('node-run-info-danger'), nodeRunSuccessIndicator: () => cy.getByTestId('node-run-status-success'),
nodeRunErrorIndicator: () => cy.getByTestId('node-run-status-danger'),
nodeRunErrorMessage: () => cy.getByTestId('node-error-message'), nodeRunErrorMessage: () => cy.getByTestId('node-error-message'),
nodeRunErrorDescription: () => cy.getByTestId('node-error-description'), nodeRunErrorDescription: () => cy.getByTestId('node-error-description'),
fixedCollectionParameter: (paramName: string) => fixedCollectionParameter: (paramName: string) =>

View file

@ -2,12 +2,24 @@
import type { Placement } from 'element-plus'; import type { Placement } from 'element-plus';
import { computed } from 'vue'; import { computed } from 'vue';
import type { IconColor } from 'n8n-design-system/types/icon';
import N8nIcon from '../N8nIcon'; import N8nIcon from '../N8nIcon';
import N8nTooltip from '../N8nTooltip'; import N8nTooltip from '../N8nTooltip';
const THEME = ['info', 'info-light', 'warning', 'danger', 'success'] as const; const THEME = ['info', 'info-light', 'warning', 'danger', 'success'] as const;
const TYPE = ['note', 'tooltip'] as const; const TYPE = ['note', 'tooltip'] as const;
const ICON_MAP = {
info: 'info-circle',
// eslint-disable-next-line @typescript-eslint/naming-convention
'info-light': 'info-circle',
warning: 'exclamation-triangle',
danger: 'exclamation-triangle',
success: 'check-circle',
} as const;
type IconMap = typeof ICON_MAP;
interface InfoTipProps { interface InfoTipProps {
theme?: (typeof THEME)[number]; theme?: (typeof THEME)[number];
type?: (typeof TYPE)[number]; type?: (typeof TYPE)[number];
@ -23,39 +35,11 @@ const props = withDefaults(defineProps<InfoTipProps>(), {
tooltipPlacement: 'top', tooltipPlacement: 'top',
}); });
const iconData = computed((): { icon: string; color: string } => { const iconData = computed<{ icon: IconMap[keyof IconMap]; color: IconColor }>(() => {
switch (props.theme) { return {
case 'info': icon: ICON_MAP[props.theme],
return { color: props.theme === 'info' || props.theme === 'info-light' ? 'text-base' : props.theme,
icon: 'info-circle', } as const;
color: '--color-text-light)',
};
case 'info-light':
return {
icon: 'info-circle',
color: 'var(--color-foreground-dark)',
};
case 'warning':
return {
icon: 'exclamation-triangle',
color: 'var(--color-warning)',
};
case 'danger':
return {
icon: 'exclamation-triangle',
color: 'var(--color-danger)',
};
case 'success':
return {
icon: 'check-circle',
color: 'var(--color-success)',
};
default:
return {
icon: 'info-circle',
color: '--color-text-light)',
};
}
}); });
</script> </script>
@ -69,14 +53,16 @@ const iconData = computed((): { icon: string; color: string } => {
[$style.bold]: bold, [$style.bold]: bold,
}" }"
> >
<!-- Note that the branching is required to support displaying
the slot either in the tooltip of the icon or following it -->
<N8nTooltip <N8nTooltip
v-if="type === 'tooltip'" v-if="type === 'tooltip'"
:placement="tooltipPlacement" :placement="tooltipPlacement"
:popper-class="$style.tooltipPopper" :popper-class="$style.tooltipPopper"
:disabled="type !== 'tooltip'" :disabled="type !== 'tooltip'"
> >
<span :class="$style.iconText" :style="{ color: iconData.color }"> <span :class="$style.iconText">
<N8nIcon :icon="iconData.icon" /> <N8nIcon :icon="iconData.icon" :color="iconData.color" />
</span> </span>
<template #content> <template #content>
<span> <span>
@ -85,7 +71,7 @@ const iconData = computed((): { icon: string; color: string } => {
</template> </template>
</N8nTooltip> </N8nTooltip>
<span v-else :class="$style.iconText"> <span v-else :class="$style.iconText">
<N8nIcon :icon="iconData.icon" /> <N8nIcon :icon="iconData.icon" :color="iconData.color" />
<span> <span>
<slot /> <slot />
</span> </span>

View file

@ -1,9 +1,16 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`N8nInfoTip > should render correctly as note 1`] = `"<div class="n8n-info-tip infoTip info note bold"><span class="iconText"><span class="n8n-text compact size-medium regular n8n-icon n8n-icon"><!----></span><span>Need help doing something?<a href="/docs" target="_blank">Open docs</a></span></span></div>"`; exports[`N8nInfoTip > should render correctly as note 1`] = `
"<div class="n8n-info-tip infoTip info note bold">
<!-- Note that the branching is required to support displaying
the slot either in the tooltip of the icon or following it --><span class="iconText"><span class="n8n-text text-base compact size-medium regular n8n-icon n8n-icon"><!----></span><span>Need help doing something?<a href="/docs" target="_blank">Open docs</a></span></span>
</div>"
`;
exports[`N8nInfoTip > should render correctly as tooltip 1`] = ` exports[`N8nInfoTip > should render correctly as tooltip 1`] = `
"<div class="n8n-info-tip infoTip info tooltip bold"><span class="iconText el-tooltip__trigger el-tooltip__trigger"><span class="n8n-text compact size-medium regular n8n-icon n8n-icon"><!----></span></span> "<div class="n8n-info-tip infoTip info tooltip bold">
<!-- Note that the branching is required to support displaying
the slot either in the tooltip of the icon or following it --><span class="iconText el-tooltip__trigger el-tooltip__trigger"><span class="n8n-text text-base compact size-medium regular n8n-icon n8n-icon"><!----></span></span>
<!--teleport start--> <!--teleport start-->
<!--teleport end--> <!--teleport end-->
</div>" </div>"

View file

@ -50,27 +50,38 @@ const runMetadata = computed(() => {
" "
></span> ></span>
</n8n-info-tip> </n8n-info-tip>
<n8n-info-tip <div v-else-if="runMetadata" :class="$style.tooltipRow">
v-else-if="runMetadata" <n8n-info-tip type="note" :theme="theme" :data-test-id="`node-run-status-${theme}`" />
type="tooltip" <n8n-info-tip
:theme="theme" type="tooltip"
:data-test-id="`node-run-info-${theme}`" theme="info"
tooltip-placement="right" :data-test-id="`node-run-info`"
> tooltip-placement="right"
<div> >
<n8n-text :bold="true" size="small" <div>
>{{ <n8n-text :bold="true" size="small"
runTaskData?.error >{{
? i18n.baseText('runData.executionStatus.failed') runTaskData?.error
: i18n.baseText('runData.executionStatus.success') ? i18n.baseText('runData.executionStatus.failed')
}} </n8n-text : i18n.baseText('runData.executionStatus.success')
><br /> }} </n8n-text
<n8n-text :bold="true" size="small">{{ i18n.baseText('runData.startTime') + ':' }}</n8n-text> ><br />
{{ runMetadata.startTime }}<br /> <n8n-text :bold="true" size="small">{{
<n8n-text :bold="true" size="small">{{ i18n.baseText('runData.startTime') + ':'
i18n.baseText('runData.executionTime') + ':' }}</n8n-text>
}}</n8n-text> {{ runMetadata.startTime }}<br />
{{ runMetadata.executionTime }} {{ i18n.baseText('runData.ms') }} <n8n-text :bold="true" size="small">{{
</div> i18n.baseText('runData.executionTime') + ':'
</n8n-info-tip> }}</n8n-text>
{{ runMetadata.executionTime }} {{ i18n.baseText('runData.ms') }}
</div>
</n8n-info-tip>
</div>
</template> </template>
<style lang="scss" module>
.tooltipRow {
display: flex;
column-gap: var(--spacing-4xs);
}
</style>