Add more frontend hooks (#1687)

*  add hook for nodecreatelist mount

*  add hook for nodeCreateList selectedTypeChanged

*  add hook for nodeCreateList nodeFilterChanged

*  add hook for nodeCreateList filteredNodeTypesComputed

*  add hook for nodeView.activeNodeChanged

*  add hook for credentialsEdit credentialTypeChanged

*  add hook for onDocumentationUrlClick

*  add hook for executionsList openDialog

*  add hook for execution open

*  add hook for credentialsList dialogVisibleChanged

*  add hook for workflowSettings

*  add hook for showMessage showError

*  add hook for nodeView createNodeActiveChanged

*  add hook for nodeView addNodeButton

*  cleanup

*  add hook for workflowRun runWorkflow

*  add hook for pushConnection executionFinished

*  add hook for runData.displayModeChanged

*  update nodeCreateList.nodeFilterChanged hook

*  update dataDisplay nodeTypeChanged hook

*  update dataDisplay nodeTypeChanged hook

*  update dataDisplay nodeTypeChanged hook

*  update error data in hooks

* update workflowRun runError hook

*  Minor improvements

Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
Ahsan Virani 2021-05-06 02:46:33 +02:00 committed by GitHub
parent f324fe1dff
commit 2052cadce9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 143 additions and 54 deletions

View file

@ -50,6 +50,7 @@
<script lang="ts">
import Vue from 'vue';
import { externalHooks } from '@/components/mixins/externalHooks';
import { restApi } from '@/components/mixins/restApi';
import { showMessage } from '@/components/mixins/showMessage';
import CredentialsInput from '@/components/CredentialsInput.vue';
@ -71,6 +72,7 @@ import { INodeUi } from '../Interface';
export default mixins(
restApi,
showMessage,
externalHooks,
).extend({
name: 'CredentialsEdit',
props: [
@ -195,7 +197,6 @@ export default mixins(
});
return;
}
this.credentialData = currentCredentials;
} else {
Vue.nextTick(() => {
@ -227,6 +228,9 @@ export default mixins(
this.credentialType = null;
}
},
async credentialType (newValue, oldValue) {
this.$externalHooks().run('credentialsEdit.credentialTypeChanged', { newValue, oldValue, editCredentials: !!this.editCredentials, credentialType: this.credentialType, setCredentialType: this.setCredentialType });
},
},
methods: {
getCredentialProperties (name: string): INodeProperties[] {

View file

@ -37,6 +37,7 @@
</template>
<script lang="ts">
import { externalHooks } from '@/components/mixins/externalHooks';
import { restApi } from '@/components/mixins/restApi';
import { ICredentialsResponse } from '@/Interface';
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
@ -47,6 +48,7 @@ import { genericHelpers } from '@/components/mixins/genericHelpers';
import mixins from 'vue-typed-mixins';
export default mixins(
externalHooks,
genericHelpers,
nodeHelpers,
restApi,
@ -75,6 +77,7 @@ export default mixins(
this.loadCredentials();
this.loadCredentialTypes();
}
this.$externalHooks().run('credentialsList.dialogVisibleChanged', { dialogVisible: newValue });
},
},
methods: {

View file

@ -26,7 +26,7 @@
</svg>
<div v-if="showDocumentHelp && nodeType" class="text">
Need help? <a id="doc-hyperlink" v-if="showDocumentHelp && nodeType" :href="documentationUrl" target="_blank">Open {{nodeType.displayName}} documentation</a>
Need help? <a id="doc-hyperlink" v-if="showDocumentHelp && nodeType" :href="documentationUrl" target="_blank" @click="onDocumentationUrlClick">Open {{nodeType.displayName}} documentation</a>
</div>
</div>
</transition>
@ -49,10 +49,16 @@ import {
IUpdateInformation,
} from '../Interface';
import { externalHooks } from '@/components/mixins/externalHooks';
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import NodeSettings from '@/components/NodeSettings.vue';
import RunData from '@/components/RunData.vue';
export default Vue.extend({
import mixins from 'vue-typed-mixins';
export default mixins(externalHooks, nodeHelpers, workflowHelpers).extend({
name: 'DataDisplay',
components: {
NodeSettings,
@ -88,6 +94,13 @@ export default Vue.extend({
return null;
},
},
watch: {
node (node, oldNode) {
if(node && !oldNode) {
this.$externalHooks().run('dataDisplay.nodeTypeChanged', { nodeSubtitle: this.getNodeSubtitle(node, this.nodeType, this.getWorkflow()) });
}
},
},
methods: {
valueChanged (parameterData: IUpdateInformation) {
this.$emit('valueChanged', parameterData);
@ -102,6 +115,9 @@ export default Vue.extend({
this.$store.commit('setActiveNode', null);
}
},
onDocumentationUrlClick () {
this.$externalHooks().run('dataDisplay.onDocumentationUrlClick', { nodeType: this.nodeType, documentationUrl: this.documentationUrl });
},
},
});

View file

@ -158,6 +158,7 @@ import Vue from 'vue';
import ExecutionTime from '@/components/ExecutionTime.vue';
import WorkflowActivator from '@/components/WorkflowActivator.vue';
import { externalHooks } from '@/components/mixins/externalHooks';
import { restApi } from '@/components/mixins/restApi';
import { genericHelpers } from '@/components/mixins/genericHelpers';
import { showMessage } from '@/components/mixins/showMessage';
@ -182,6 +183,7 @@ import {
import mixins from 'vue-typed-mixins';
export default mixins(
externalHooks,
genericHelpers,
restApi,
showMessage,
@ -558,6 +560,8 @@ export default mixins(
await this.loadWorkflows();
await this.refreshData();
this.handleAutoRefreshToggle();
this.$externalHooks().run('executionsList.openDialog');
},
async retryExecution (execution: IExecutionShortResponse, loadWorkflow?: boolean) {
this.isDataLoading = true;

View file

@ -47,14 +47,11 @@
import Vue from 'vue';
import { nodeBase } from '@/components/mixins/nodeBase';
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import {
INode,
INodeIssueObjectProperty,
INodePropertyOptions,
INodeTypeDescription,
ITaskData,
NodeHelpers,
} from 'n8n-workflow';
@ -62,7 +59,7 @@ import NodeIcon from '@/components/NodeIcon.vue';
import mixins from 'vue-typed-mixins';
export default mixins(nodeBase, workflowHelpers).extend({
export default mixins(nodeBase, nodeHelpers, workflowHelpers).extend({
name: 'Node',
components: {
NodeIcon,
@ -133,41 +130,7 @@ export default mixins(nodeBase, workflowHelpers).extend({
}
},
nodeSubtitle (): string | undefined {
if (this.data.notesInFlow) {
return this.data.notes;
}
if (this.nodeType !== null && this.nodeType.subtitle !== undefined) {
return this.workflow.expression.getSimpleParameterValue(this.data as INode, this.nodeType.subtitle, 'internal') as string | undefined;
}
if (this.data.parameters.operation !== undefined) {
const operation = this.data.parameters.operation as string;
if (this.nodeType === null) {
return operation;
}
const operationData = this.nodeType.properties.find((property) => {
return property.name === 'operation';
});
if (operationData === undefined) {
return operation;
}
if (operationData.options === undefined) {
return operation;
}
const optionData = operationData.options.find((option) => {
return (option as INodePropertyOptions).value === this.data.parameters.operation;
});
if (optionData === undefined) {
return operation;
}
return optionData.name;
}
return undefined;
return this.getNodeSubtitle(this.data, this.nodeType, this.workflow);
},
workflowRunning (): boolean {
return this.$store.getters.isActionActive('workflowRunning');
@ -186,7 +149,7 @@ export default mixins(nodeBase, workflowHelpers).extend({
this.disableNodes([this.data]);
},
executeNode () {
this.$emit('runWorkflow', this.data.name);
this.$emit('runWorkflow', this.data.name, 'Node.executeNode');
},
deleteNode () {
Vue.nextTick(() => {

View file

@ -24,10 +24,13 @@
<script lang="ts">
import Vue from 'vue';
import { externalHooks } from "@/components/mixins/externalHooks";
import { INodeTypeDescription } from 'n8n-workflow';
import NodeCreateItem from '@/components/NodeCreateItem.vue';
export default Vue.extend({
import mixins from "vue-typed-mixins";
export default mixins(externalHooks).extend({
name: 'NodeCreateList',
components: {
NodeCreateItem,
@ -70,13 +73,18 @@ export default Vue.extend({
return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
});
this.$externalHooks().run('nodeCreateList.filteredNodeTypesComputed', { nodeFilter: this.nodeFilter, result: returnData, selectedType: this.selectedType });
return returnData;
},
},
watch: {
nodeFilter (newVal, oldVal) {
nodeFilter (newValue, oldValue) {
// Reset the index whenver the filter-value changes
this.activeNodeTypeIndex = 0;
this.$externalHooks().run('nodeCreateList.nodeFilterChanged', { oldValue, newValue, selectedType: this.selectedType, filteredNodes: this.filteredNodeTypes });
},
selectedType (newValue, oldValue) {
this.$externalHooks().run('nodeCreateList.selectedTypeChanged', { oldValue, newValue });
},
},
methods: {
@ -105,6 +113,12 @@ export default Vue.extend({
this.$emit('nodeTypeSelected', nodeTypeName);
},
},
async mounted() {
this.$externalHooks().run('nodeCreateList.mounted');
},
async destroyed() {
this.$externalHooks().run('nodeCreateList.destroyed');
},
});
</script>

View file

@ -5,7 +5,7 @@
<el-button
v-if="node && !isReadOnly"
:disabled="workflowRunning"
@click.stop="runWorkflow(node.name)"
@click.stop="runWorkflow(node.name, 'RunData.ExecuteNodeButton')"
class="execute-node-button"
:title="`Executes this ${node.name} node after executing any previous nodes that have not yet returned data`"
>
@ -228,6 +228,7 @@ import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue';
import NodeErrorView from '@/components/Error/NodeViewError.vue';
import { copyPaste } from '@/components/mixins/copyPaste';
import { externalHooks } from "@/components/mixins/externalHooks";
import { genericHelpers } from '@/components/mixins/genericHelpers';
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import { workflowRun } from '@/components/mixins/workflowRun';
@ -239,6 +240,7 @@ const deselectedPlaceholder = '_!^&*';
export default mixins(
copyPaste,
externalHooks,
genericHelpers,
nodeHelpers,
workflowRun,
@ -617,8 +619,9 @@ export default mixins(
jsonData () {
this.refreshDataSize();
},
displayMode () {
displayMode (newValue, oldValue) {
this.closeBinaryDataDisplay();
this.$externalHooks().run('runData.displayModeChanged', { newValue, oldValue });
},
maxRunIndex () {
this.runIndex = Math.min(this.runIndex, this.maxRunIndex);

View file

@ -167,6 +167,7 @@
<script lang="ts">
import Vue from 'vue';
import { externalHooks } from '@/components/mixins/externalHooks';
import { restApi } from '@/components/mixins/restApi';
import { genericHelpers } from '@/components/mixins/genericHelpers';
import { showMessage } from '@/components/mixins/showMessage';
@ -180,6 +181,7 @@ import {
import mixins from 'vue-typed-mixins';
export default mixins(
externalHooks,
genericHelpers,
restApi,
showMessage,
@ -225,6 +227,7 @@ export default mixins(
if (newValue) {
this.openDialog();
}
this.$externalHooks().run('workflowSettings.dialogVisibleChanged', { dialogVisible: newValue });
},
},
methods: {
@ -456,6 +459,8 @@ export default mixins(
}
}
const oldSettings = JSON.parse(JSON.stringify(this.$store.getters.workflowSettings));
this.$store.commit('setWorkflowSettings', localWorkflowSettings);
this.isLoading = false;
@ -467,6 +472,8 @@ export default mixins(
});
this.closeDialog();
this.$externalHooks().run('workflowSettings.saveSettings', { oldSettings });
},
toggleTimeout() {
this.workflowSettings.executionTimeout = this.workflowSettings.executionTimeout === -1 ? 0 : -1;

View file

@ -13,6 +13,8 @@ import {
IRunData,
IRunExecutionData,
ITaskDataConnections,
INode,
INodePropertyOptions,
} from 'n8n-workflow';
import {
@ -321,5 +323,43 @@ export const nodeHelpers = mixins(
this.updateNodeCredentialIssues(node);
}
},
// @ts-ignore
getNodeSubtitle (data, nodeType, workflow): string | undefined {
if (data.notesInFlow) {
return data.notes;
}
if (nodeType !== null && nodeType.subtitle !== undefined) {
return workflow.expression.getSimpleParameterValue(data as INode, nodeType.subtitle, 'internal') as string | undefined;
}
if (data.parameters.operation !== undefined) {
const operation = data.parameters.operation as string;
if (nodeType === null) {
return operation;
}
const operationData:INodeProperties = nodeType.properties.find((property: INodeProperties) => {
return property.name === 'operation';
});
if (operationData === undefined) {
return operation;
}
if (operationData.options === undefined) {
return operation;
}
const optionData = operationData.options.find((option) => {
return (option as INodePropertyOptions).value === data.parameters.operation;
});
if (optionData === undefined) {
return operation;
}
return optionData.name;
}
return undefined;
},
},
});

View file

@ -8,6 +8,7 @@ import {
IPushDataTestWebhook,
} from '../../Interface';
import { externalHooks } from '@/components/mixins/externalHooks';
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import { showMessage } from '@/components/mixins/showMessage';
import { titleChange } from '@/components/mixins/titleChange';
@ -15,6 +16,7 @@ import { titleChange } from '@/components/mixins/titleChange';
import mixins from 'vue-typed-mixins';
export const pushConnection = mixins(
externalHooks,
nodeHelpers,
showMessage,
titleChange,
@ -202,6 +204,7 @@ export const pushConnection = mixins(
const runDataExecuted = pushData.data;
let runDataExecutedErrorMessage;
// @ts-ignore
const workflow = this.getWorkflow();
if (runDataExecuted.finished !== true) {
@ -221,6 +224,9 @@ export const pushConnection = mixins(
: runDataExecuted.data.resultData.error.message;
errorMessage = `There was a problem executing the workflow:<br /><strong>"${receivedError}"</strong>`;
}
runDataExecutedErrorMessage = errorMessage;
this.$titleSet(workflow.name, 'ERROR');
this.$showMessage({
title: 'Problem executing workflow',
@ -249,6 +255,20 @@ export const pushConnection = mixins(
// Set the node execution issues on all the nodes which produced an error so that
// it can be displayed in the node-view
this.updateNodesExecutionIssues();
let itemsCount = 0;
if(runDataExecuted.data.resultData.lastNodeExecuted && !runDataExecutedErrorMessage) {
itemsCount = runDataExecuted.data.resultData.runData[runDataExecuted.data.resultData.lastNodeExecuted][0].data!.main[0]!.length;
}
this.$externalHooks().run('pushConnection.executionFinished', {
itemsCount,
nodeName: runDataExecuted.data.resultData.lastNodeExecuted,
errorMessage: runDataExecutedErrorMessage,
runDataExecutedStartData: runDataExecuted.data.startData,
resultDataError: runDataExecuted.data.resultData.error,
});
} else if (receivedData.type === 'executionStarted') {
const pushData = receivedData.data as IPushDataExecutionStarted;

View file

@ -2,9 +2,13 @@ import Vue from 'vue';
import { Notification } from 'element-ui';
import { ElNotificationOptions } from 'element-ui/types/notification';
import mixins from 'vue-typed-mixins';
import { externalHooks } from '@/components/mixins/externalHooks';
// export const showMessage = {
export const showMessage = Vue.extend({
export const showMessage = mixins(externalHooks).extend({
methods: {
$showMessage (messageData: ElNotificationOptions) {
messageData.dangerouslyUseHTMLString = true;
@ -21,6 +25,7 @@ export const showMessage = Vue.extend({
type: 'error',
duration: 0,
});
this.$externalHooks().run('showMessage.showError', { title, message, errorMessage: error.message });
},
},
});

View file

@ -53,7 +53,7 @@ export const workflowRun = mixins(
return response;
},
async runWorkflow (nodeName: string): Promise<IExecutionPushResponse | undefined> {
async runWorkflow (nodeName: string, source?: string): Promise<IExecutionPushResponse | undefined> {
if (this.$store.getters.isActionActive('workflowRunning') === true) {
return;
}
@ -84,7 +84,7 @@ export const workflowRun = mixins(
duration: 0,
});
this.$titleSet(workflow.name as string, 'ERROR');
this.$externalHooks().run('workflow.runError', { errorMessages });
this.$externalHooks().run('workflowRun.runError', { errorMessages, nodeName });
return;
}
}
@ -172,7 +172,11 @@ export const workflowRun = mixins(
};
this.$store.commit('setWorkflowExecutionData', executionData);
return await this.runWorkflowApi(startRunData);
const runWorkflowApiResponse = await this.runWorkflowApi(startRunData);
this.$externalHooks().run('workflowRun.runWorkflow', { nodeName, source });
return runWorkflowApiResponse;
} catch (error) {
this.$titleSet(workflow.name as string, 'ERROR');
this.$showError(error, 'Problem running workflow', 'There was a problem running the workflow:');

View file

@ -198,7 +198,7 @@ export default mixins(
this.createNodeActive = false;
},
nodes: {
async handler (val, oldVal) {
async handler (value, oldValue) {
// Load a workflow
let workflowId = null as string | null;
if (this.$route && this.$route.params.name) {
@ -208,7 +208,7 @@ export default mixins(
deep: true,
},
connections: {
async handler (val, oldVal) {
async handler (value, oldValue) {
// Load a workflow
let workflowId = null as string | null;
if (this.$route && this.$route.params.name) {
@ -332,6 +332,7 @@ export default mixins(
},
openNodeCreator () {
this.createNodeActive = true;
this.$externalHooks().run('nodeView.createNodeActiveChanged', { source: 'add_node_button' });
},
async openExecution (executionId: string) {
this.resetWorkspace();
@ -354,6 +355,8 @@ export default mixins(
this.$store.commit('setWorkflowExecutionData', data);
await this.addNodes(JSON.parse(JSON.stringify(data.workflowData.nodes)), JSON.parse(JSON.stringify(data.workflowData.connections)));
this.$externalHooks().run('execution.open', { workflowId: data.workflowData.id, workflowName: data.workflowData.name, executionId });
},
async openWorkflow (workflowId: string) {
this.resetWorkspace();
@ -1052,6 +1055,8 @@ export default mixins(
this.$store.commit('setStateDirty', true);
this.$externalHooks().run('nodeView.addNodeButton', { nodeTypeName });
// Automatically deselect all nodes and select the current one and also active
// current node
this.deselectAllNodes();
@ -1174,6 +1179,7 @@ export default mixins(
// Display the node-creator
this.createNodeActive = true;
this.$externalHooks().run('nodeView.createNodeActiveChanged', { source: 'node_connection_drop' });
});
this.instance.bind('connection', (info: OnConnectionBindInfo) => {