Skip to content

Commit

Permalink
feat(editor): Debug executions in the editor (#6834)
Browse files Browse the repository at this point in the history
  • Loading branch information
cstuncsik committed Aug 25, 2023
1 parent 72f65dc commit c833078
Show file tree
Hide file tree
Showing 20 changed files with 665 additions and 76 deletions.
129 changes: 129 additions & 0 deletions cypress/e2e/28-debug.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {
HTTP_REQUEST_NODE_NAME, IF_NODE_NAME,
INSTANCE_OWNER,
MANUAL_TRIGGER_NODE_NAME,
SET_NODE_NAME,
} from '../constants';
import { WorkflowPage, NDV, WorkflowExecutionsTab } from '../pages';

const workflowPage = new WorkflowPage();
const ndv = new NDV();
const executionsTab = new WorkflowExecutionsTab();

describe('Debug', () => {
it('should be able to debug executions', () => {
cy.intercept('GET', '/rest/settings', (req) => {
req.on('response', (res) => {
res.send({
data: { ...res.body.data, enterprise: { debugInEditor: true } },
});
});
}).as('loadSettings');
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
cy.intercept('GET', '/rest/executions/*').as('getExecution');
cy.intercept('GET', '/rest/executions-current?filter=*').as('getCurrentExecutions');
cy.intercept('POST', '/rest/workflows/run').as('postWorkflowRun');

cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });

workflowPage.actions.visit();

workflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
workflowPage.actions.addNodeToCanvas(HTTP_REQUEST_NODE_NAME);
workflowPage.actions.openNode(HTTP_REQUEST_NODE_NAME);
ndv.actions.typeIntoParameterInput('url', 'https://foo.bar');
ndv.actions.close();

workflowPage.actions.addNodeToCanvas(SET_NODE_NAME, true);

workflowPage.actions.saveWorkflowUsingKeyboardShortcut();
workflowPage.actions.executeWorkflow();

cy.wait(['@postWorkflowRun']);

executionsTab.actions.switchToExecutionsTab();

cy.wait(['@getExecutions', '@getCurrentExecutions']);

executionsTab.getters.executionDebugButton().should('have.text', 'Debug in editor').click();
cy.get('.el-notification').contains('Execution data imported').should('be.visible');
cy.get('.matching-pinned-nodes-confirmation').should('not.exist');


workflowPage.actions.openNode(HTTP_REQUEST_NODE_NAME);
ndv.actions.clearParameterInput('url');
ndv.actions.typeIntoParameterInput('url', 'https://postman-echo.com/get?foo1=bar1&foo2=bar2');
ndv.actions.close();

workflowPage.actions.saveWorkflowUsingKeyboardShortcut();
workflowPage.actions.executeWorkflow();

cy.wait(['@postWorkflowRun']);

workflowPage.actions.openNode(HTTP_REQUEST_NODE_NAME);
ndv.actions.pinData();
ndv.actions.close();

executionsTab.actions.switchToExecutionsTab();

cy.wait(['@getExecutions', '@getCurrentExecutions']);

executionsTab.getters.executionListItems().should('have.length', 2).first().click();
cy.wait(['@getExecution']);

executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click();

let confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible');
confirmDialog.find('li').should('have.length', 2);
confirmDialog.get('.btn--cancel').click();

cy.wait(['@getExecutions', '@getCurrentExecutions']);

executionsTab.getters.executionListItems().should('have.length', 2).first().click();
cy.wait(['@getExecution']);

executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click();

confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible');
confirmDialog.find('li').should('have.length', 2);
confirmDialog.get('.btn--confirm').click();

workflowPage.getters.canvasNodes().first().should('have.descendants', '.node-pin-data-icon');
workflowPage.getters.canvasNodes().not(':first').should('not.have.descendants', '.node-pin-data-icon');

cy.reload(true);
cy.wait(['@getExecution']);

confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible');
confirmDialog.find('li').should('have.length', 1);
confirmDialog.get('.btn--confirm').click();

workflowPage.getters.canvasNodePlusEndpointByName(SET_NODE_NAME).click();
workflowPage.actions.addNodeToCanvas(IF_NODE_NAME, false);
workflowPage.actions.saveWorkflowUsingKeyboardShortcut();

executionsTab.actions.switchToExecutionsTab();
cy.wait(['@getExecutions', '@getCurrentExecutions']);
executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click();

confirmDialog = cy.get('.matching-pinned-nodes-confirmation').filter(':visible');
confirmDialog.find('li').should('have.length', 1);
confirmDialog.get('.btn--confirm').click();
workflowPage.getters.canvasNodes().last().find('.node-info-icon').should('be.empty');

workflowPage.getters.canvasNodes().first().dblclick();
ndv.getters.pinDataButton().click();
ndv.actions.close();

workflowPage.actions.saveWorkflowUsingKeyboardShortcut();
workflowPage.actions.executeWorkflow();
workflowPage.actions.deleteNode(IF_NODE_NAME);

executionsTab.actions.switchToExecutionsTab();
cy.wait(['@getExecutions', '@getCurrentExecutions']);
executionsTab.getters.executionListItems().should('have.length', 3).first().click();
cy.wait(['@getExecution']);
executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click();
cy.get('.el-notification').contains('Some execution data wasn\'t imported').should('be.visible');
});
});
1 change: 1 addition & 0 deletions cypress/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export * from './settings-log-streaming';
export * from './sidebar';
export * from './ndv';
export * from './bannerStack';
export * from './workflow-executions-tab';
export * from './signin';
1 change: 1 addition & 0 deletions cypress/pages/workflow-executions-tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class WorkflowExecutionsTab extends BasePage {
this.getters.executionPreviewDetails().find('[data-test-id="execution-preview-label"]'),
executionPreviewId: () =>
this.getters.executionPreviewDetails().find('[data-test-id="execution-preview-id"]'),
executionDebugButton: () => cy.getByTestId('execution-debug-button'),
};
actions = {
toggleNodeEnabled: (nodeName: string) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/editor-ui/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ export interface ITimeoutHMS {
seconds: number;
}

export type WorkflowTitleStatus = 'EXECUTING' | 'IDLE' | 'ERROR';
export type WorkflowTitleStatus = 'EXECUTING' | 'IDLE' | 'ERROR' | 'DEBUG';

export type ExtractActionKeys<T> = T extends SimplifiedNodeType ? T['name'] : never;

Expand Down Expand Up @@ -897,6 +897,7 @@ export interface WorkflowsState {
workflowExecutionData: IExecutionResponse | null;
workflowExecutionPairedItemMappings: { [itemId: string]: Set<string> };
workflowsById: IWorkflowsMap;
isInDebugMode?: boolean;
}

export interface RootState {
Expand Down
31 changes: 31 additions & 0 deletions packages/editor-ui/src/__tests__/router.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createPinia, setActivePinia } from 'pinia';
import { createComponentRenderer } from '@/__tests__/render';
import router from '@/router';
import { VIEWS } from '@/constants';

const App = {
template: '<div />',
};
const renderComponent = createComponentRenderer(App);

describe('router', () => {
beforeAll(() => {
const pinia = createPinia();
setActivePinia(pinia);
renderComponent({ pinia });
});

test.each([
['/', VIEWS.WORKFLOWS],
['/workflow', VIEWS.NEW_WORKFLOW],
['/workflow/new', VIEWS.NEW_WORKFLOW],
['/workflow/R9JFXwkUCL1jZBuw', VIEWS.WORKFLOW],
['/workflow/R9JFXwkUCL1jZBuw/executions/29021', VIEWS.EXECUTION_PREVIEW],
['/workflow/R9JFXwkUCL1jZBuw/debug/29021', VIEWS.EXECUTION_DEBUG],
['/workflows/templates/R9JFXwkUCL1jZBuw', VIEWS.TEMPLATE_IMPORT],
['/workflows/demo', VIEWS.DEMO],
])('should resolve %s to %s', async (path, name) => {
await router.push(path);
expect(router.currentRoute.value.name).toBe(name);
});
});
40 changes: 40 additions & 0 deletions packages/editor-ui/src/components/DebugPaywallModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script lang="ts" setup>
import { useI18n } from '@/composables';
import Modal from '@/components/Modal.vue';
const props = defineProps<{
modalName: string;
data: { title: string; footerButtonAction: () => void };
}>();
const i18n = useI18n();
</script>

<template>
<Modal width="500px" :title="props.data.title" :name="props.modalName">
<template #content>
<n8n-text>
{{ i18n.baseText('executionsList.debug.paywall.content') }}
<br />
<n8n-link :to="i18n.baseText('executionsList.debug.paywall.link.url')">
{{ i18n.baseText('executionsList.debug.paywall.link.text') }}
</n8n-link>
</n8n-text>
</template>
<template #footer>
<div :class="$style.footer">
<n8n-button @click="props.data.footerButtonAction">
{{ i18n.baseText('generic.seePlans') }}
</n8n-button>
</div>
</template>
</Modal>
</template>

<style module lang="scss">
.footer {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,29 @@
</n8n-text>
</div>
<div>
<n8n-button
size="large"
:type="debugButtonData.type"
:class="{
[$style.debugLink]: true,
[$style.secondary]: debugButtonData.type === 'secondary',
}"
>
<router-link
:to="{
name: VIEWS.EXECUTION_DEBUG,
params: {
name: activeExecution.workflowId,
executionId: activeExecution.id,
},
}"
>
<span @click="handleDebugLinkClick" data-test-id="execution-debug-button">{{
debugButtonData.text
}}</span>
</router-link>
</n8n-button>
<el-dropdown
v-if="executionUIDetails?.name === 'error'"
trigger="click"
Expand Down Expand Up @@ -128,13 +151,12 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { useMessage } from '@/composables';
import { ElDropdown } from 'element-plus';
import { useExecutionDebugging, useMessage } from '@/composables';
import WorkflowPreview from '@/components/WorkflowPreview.vue';
import type { IExecutionUIData } from '@/mixins/executionsHelpers';
import { executionHelpers } from '@/mixins/executionsHelpers';
import { MODAL_CONFIRM, VIEWS } from '@/constants';
import { ElDropdown } from 'element-plus';
type RetryDropdownRef = InstanceType<typeof ElDropdown> & { hide: () => void };
Expand All @@ -153,6 +175,7 @@ export default defineComponent({
setup() {
return {
...useMessage(),
...useExecutionDebugging(),
};
},
computed: {
Expand All @@ -162,6 +185,17 @@ export default defineComponent({
executionMode(): string {
return this.activeExecution?.mode || '';
},
debugButtonData(): Record<string, string> {
return this.activeExecution?.status === 'success'
? {
text: this.$locale.baseText('executionsList.debug.button.copyToEditor'),
type: 'secondary',
}
: {
text: this.$locale.baseText('executionsList.debug.button.debugInEditor'),
type: 'primary',
};
},
},
methods: {
async onDeleteExecution(): Promise<void> {
Expand Down Expand Up @@ -212,9 +246,15 @@ export default defineComponent({
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 150ms ease-in-out;
pointer-events: none;
> div:last-child {
display: flex;
align-items: center;
}
& * {
pointer-events: all;
}
Expand Down Expand Up @@ -254,4 +294,21 @@ export default defineComponent({
margin-top: var(--spacing-l);
text-align: center;
}
.debugLink {
padding: 0;
margin-right: var(--spacing-xs);
&.secondary {
a span {
color: var(--color-primary-shade-1);
}
}
a span {
display: block;
padding: var(--spacing-xs) var(--spacing-m);
color: var(--color-text-xlight);
}
}
</style>

0 comments on commit c833078

Please sign in to comment.