Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(editor): Add node execution status indicator to output panel #8124

Merged
merged 9 commits into from
Dec 22, 2023
22 changes: 22 additions & 0 deletions cypress/e2e/5-ndv.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,28 @@ describe('NDV', () => {
ndv.getters.nodeVersion().should('have.text', 'Function node version 1 (Deprecated)');
ndv.actions.close();
});
it('should properly show node execution indicator', () => {
workflowPage.actions.addInitialNodeToCanvas('Code');
workflowPage.actions.openNode('Code');
// Should not show run info before execution
ndv.getters.nodeRunSuccessIndicator().should('not.exist');
ndv.getters.nodeRunErrorIndicator().should('not.exist');
ndv.getters.nodeExecuteButton().click();
ndv.getters.nodeRunSuccessIndicator().should('exist');
});
it('should properly show node execution indicator for multiple nodes', () => {
workflowPage.actions.addInitialNodeToCanvas('Code');
workflowPage.actions.openNode('Code');
ndv.actions.clearParameterInput('jsCode');
ndv.getters.backToCanvas().click();
workflowPage.actions.executeWorkflow();
// Manual tigger node should show success indicator
workflowPage.actions.openNode('When clicking "Execute Workflow"');
ndv.getters.nodeRunSuccessIndicator().should('exist');
// Code node should show error
ndv.getters.backToCanvas().click();
workflowPage.actions.openNode('Code');
ndv.getters.nodeRunErrorIndicator().should('exist');
MiloradFilipovic marked this conversation as resolved.
Show resolved Hide resolved
it('Should handle mismatched option attributes', () => {
workflowPage.actions.addInitialNodeToCanvas('LDAP', { keepNdvOpen: true, action: 'Create a new entry' });
// Add some attributes in Create operation
Expand Down
2 changes: 2 additions & 0 deletions cypress/pages/ndv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ export class NDV extends BasePage {
pagination: () => cy.getByTestId('ndv-data-pagination'),
nodeVersion: () => cy.getByTestId('node-version'),
nodeSettingsTab: () => cy.getByTestId('tab-settings'),
nodeRunSuccessIndicator: () => cy.getByTestId('node-run-info-success'),
nodeRunErrorIndicator: () => cy.getByTestId('node-run-info-danger'),
};

actions = {
Expand Down
69 changes: 47 additions & 22 deletions packages/design-system/src/components/N8nInfoTip/InfoTip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<div
:class="{
'n8n-info-tip': true,
[$style.infoTip]: true,
[$style[theme]]: true,
[$style[type]]: true,
[$style.bold]: bold,
Expand All @@ -10,11 +11,11 @@
<n8n-tooltip
v-if="type === 'tooltip'"
:placement="tooltipPlacement"
:popper-class="$style.tooltipPopper"
:popperClass="$style.tooltipPopper"
:disabled="type !== 'tooltip'"
>
<span :class="$style.iconText">
<n8n-icon :icon="theme.startsWith('info') ? 'info-circle' : 'exclamation-triangle'" />
<span :class="$style.iconText" :style="{ color: iconData.color }">
<n8n-icon :icon="iconData.icon" />
</span>
<template #content>
<span>
Expand All @@ -23,7 +24,7 @@
</template>
</n8n-tooltip>
<span :class="$style.iconText" v-else>
<n8n-icon :icon="theme.startsWith('info') ? 'info-circle' : 'exclamation-triangle'" />
<n8n-icon :icon="iconData.icon" />
<span>
<slot />
</span>
Expand All @@ -48,7 +49,7 @@ export default defineComponent({
type: String,
default: 'info',
validator: (value: string): boolean =>
['info', 'info-light', 'warning', 'danger'].includes(value),
['info', 'info-light', 'warning', 'danger', 'success'].includes(value),
},
type: {
type: String,
Expand All @@ -64,10 +65,50 @@ export default defineComponent({
default: 'top',
},
},
computed: {
iconData(): { icon: string; color: string } {
switch (this.theme) {
case 'info':
return {
icon: 'info-circle',
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>

<style lang="scss" module>
.infoTip {
display: flex;
}

.base {
font-size: var(--font-size-2xs);
line-height: var(--font-size-s);
Expand All @@ -92,7 +133,7 @@ export default defineComponent({
}
}

.tooltip {
.tooltipPopper {
composes: base;
display: inline-flex;
}
Expand All @@ -101,20 +142,4 @@ export default defineComponent({
display: inline-flex;
align-items: flex-start;
}

.info-light {
color: var(--color-foreground-dark);
}

.info {
color: var(--color-text-light);
}

.warning {
color: var(--color-warning);
}

.danger {
color: var(--color-danger);
}
</style>
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`N8nInfoTip > should render correctly as note 1`] = `"<div class="n8n-info-tip 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"><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 tooltip 1`] = `
"<div class="n8n-info-tip info tooltip bold">
"<div class="n8n-info-tip infoTip info tooltip bold">
<n8n-tooltip-stub popperclass="tooltipPopper" role="tooltip" showafter="0" hideafter="200" autoclose="0" boundariespadding="0" gpuacceleration="true" offset="12" placement="top" popperoptions="[object Object]" strategy="absolute" effect="dark" enterable="true" pure="false" focusonshow="false" trapping="false" stoppoppermouseevent="true" virtualtriggering="false" content="" rawcontent="false" persistent="false" teleported="true" disabled="false" open="false" trigger="hover" triggerkeys="Enter,Space" arrowoffset="5" showarrow="true" justifybuttons="flex-end" buttons="" class=""></n8n-tooltip-stub>
</div>"
`;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<el-tooltip v-bind="{ ...$props, ...$attrs }" popper-class="n8n-tooltip">
<el-tooltip v-bind="{ ...$props, ...$attrs }" :popperClass="$props.popperClass ?? 'n8n-tooltip'">
<slot />
<template #content>
<slot name="content">
Expand Down
5 changes: 3 additions & 2 deletions packages/editor-ui/src/components/OutputPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@
{{ $locale.baseText(outputPanelEditMode.enabled ? 'ndv.output.edit' : 'ndv.output') }}
</span>
<RunInfo
v-if="!hasPinData && runsCount === 1"
v-if="hasNodeRun && !hasPinData && runsCount === 1"
v-show="!outputPanelEditMode.enabled"
:taskData="runTaskData"
:hasStaleData="staleData"
/>

<n8n-info-tip
theme="warning"
type="tooltip"
Expand Down Expand Up @@ -352,6 +352,7 @@ export default defineComponent({
}
.titleSection {
display: flex;
align-items: center;

> * {
margin-right: var(--spacing-2xs);
Expand Down
23 changes: 21 additions & 2 deletions packages/editor-ui/src/components/RunInfo.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
<template>
<n8n-info-tip type="tooltip" theme="info-light" tooltipPlacement="right" v-if="runMetadata">
<n8n-info-tip
v-if="runMetadata"
type="tooltip"
:theme="theme"
:tooltipPlacement="hasStaleData ? 'left' : 'right'"
:data-test-id="`node-run-info-${theme}`"
>
<div>
<n8n-text :bold="true" size="small"
>{{
runTaskData.error
? $locale.baseText('runData.executionStatus.failed')
: $locale.baseText('runData.executionStatus.success')
}} </n8n-text
><br />
<n8n-text :bold="true" size="small">{{
$locale.baseText('runData.startTime') + ':'
}}</n8n-text>
Expand All @@ -16,23 +29,29 @@
<script lang="ts">
import { defineComponent } from 'vue';
import type { ITaskData } from 'n8n-workflow';
import { convertToDisplayDateComponents } from '@/utils/formatters/dateFormatter';

export default defineComponent({
props: {
taskData: {}, // ITaskData
hasStaleData: Boolean,
},

computed: {
theme(): string {
return this.runTaskData?.error ? 'danger' : 'success';
},
runTaskData(): ITaskData {
return this.taskData as ITaskData;
},
runMetadata(): { executionTime: number; startTime: string } | null {
if (!this.runTaskData) {
return null;
}
const { date, time } = convertToDisplayDateComponents(this.runTaskData.startTime);
return {
executionTime: this.runTaskData.executionTime,
startTime: new Date(this.runTaskData.startTime).toLocaleString(),
startTime: `${date} at ${time}`,
};
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ exports[`RunDataJsonSchema.vue > renders schema for empty data 1`] = `
class="schemaWrapper"
>
<div
class="n8n-info-tip info note bold"
class="n8n-info-tip infoTip info note bold"
>
<span
class="iconText"
Expand Down
3 changes: 2 additions & 1 deletion packages/editor-ui/src/mixins/executionsHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { i18n as locale } from '@/plugins/i18n';
import { genericHelpers } from './genericHelpers';
import type { IExecutionsSummary } from 'n8n-workflow';
import { convertToDisplayDateComponents } from '@/utils/formatters/dateFormatter';

export interface IExecutionUIData {
name: string;
Expand Down Expand Up @@ -72,7 +73,7 @@ export const executionHelpers = defineComponent({
return status;
},
formatDate(fullDate: Date | string | number) {
const { date, time } = this.convertToDisplayDate(fullDate);
const { date, time } = convertToDisplayDateComponents(fullDate);
return locale.baseText('executionsList.started', { interpolate: { time, date } });
},
},
Expand Down
9 changes: 0 additions & 9 deletions packages/editor-ui/src/mixins/genericHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import dateformat from 'dateformat';
import { VIEWS } from '@/constants';
import { useToast } from '@/composables/useToast';
import { useSourceControlStore } from '@/stores/sourceControl.store';
Expand Down Expand Up @@ -48,14 +47,6 @@ export const genericHelpers = defineComponent({

return `${minutesPassed}:${secondsLeft}${this.$locale.baseText('genericHelpers.minShort')}`;
},
convertToDisplayDate(fullDate: Date | string | number): { date: string; time: string } {
const mask = `d mmm${
new Date(fullDate).getFullYear() === new Date().getFullYear() ? '' : ', yyyy'
}#HH:MM:ss`;
const formattedDate = dateformat(fullDate, mask);
const [date, time] = formattedDate.split('#');
return { date, time };
},

/**
* @note Loading helpers extracted as composable in useLoadingService
Expand Down
2 changes: 2 additions & 0 deletions packages/editor-ui/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,8 @@
"runData.editOutputInvalid.onLine": "On line {line}:",
"runData.editOutputInvalid.atPosition": "(at position {position})",
"runData.editValue": "Edit Value",
"runData.executionStatus.success": "Executed successfully",
"runData.executionStatus.failed": "Execution failed",
"runData.downloadBinaryData": "Download",
"runData.executeNode": "Execute Node",
"runData.executionTime": "Execution Time",
Expand Down
12 changes: 12 additions & 0 deletions packages/editor-ui/src/utils/formatters/dateFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import dateformat from 'dateformat';

export const convertToDisplayDateComponents = (
fullDate: Date | string | number,
): { date: string; time: string } => {
const mask = `d mmm${
new Date(fullDate).getFullYear() === new Date().getFullYear() ? '' : ', yyyy'
}#HH:MM:ss`;
const formattedDate = dateformat(fullDate, mask);
const [date, time] = formattedDate.split('#');
return { date, time };
};
Loading