feat(editor): Add data pinning functionality (#3511)

* feat: Design system color improvements and button component redesign.

* feat: Added button focus state and unit tests.

* refactor: Aligned n8n-button usage inside of editor-ui.

* test: Updated snapshots.

* refactor: Extracted focus outline width into scss variable.

* fix: Fixed select input border-radius.

* refactor: Removed element-ui references in button.

* fix: Fixed scss variable imports.

* feat: Added color-neutral variable story.

* fix: Fixed color-secondary variable definition.

* feat: Added color-white story.

* test: Updated button snapshot.

* feat: Replaced zoom buttons with new n8n-icon-button.

* feat: Added stories for float utilities.

* chore: Updated color shades generation code for later use.

* chore: Removed color-white code.

* chore: Updated story properties for button components.

* fix: Added el-button fallback for places where el-button is not replaceable (messagebox).

* feat: Reverted to css modules. Replaced el-button with n8n-button at application level.

* test: Updated button snapshot.

* fix: Fixed element-ui locally referenced buttons (via components: {}).

* fix: Updated colors. Removed irrelevant validation. Added ElButton override component.

* test: Updated button override snapshot.

* fix: Various button adjustments and fixes.

* fix: Updated button disabled state.

* test: Updated snapshots.

* fix: Consolidated css variables changes.

* Data pinning (#3512)

* refactor: Aligned n8n-button usage inside of editor-ui.

* feat: Added edit data button on json hover.

* feat: Extracted code editor into separate form component.

* feat: Added edit data button on json hover.

* feat: Added pinData and edit mode methods.

* 🔥 Remove conflict markers

* ✏️ Update i18n keys

*  Add JSON validation

* 🗃️ Add `pinData` column to `workflow_entity`

* 📘 Tighten type

*  Make `pinData` column nullable

*  Adjust workflow endpoints for pin data

* 📘 Improve types

* ✏️ Improve wording

* Inject pindata into items flow (#3420)

*  Inject pin data - Second approach

* 🔥 Remove unneeded lint exception

* feat: Added edit data button on json hover.

* feat: Extracted code editor into separate form component.

* feat: Added edit data button on json hover.

* fix: Fixed rebase conflicts.

*  Undo button change

* 🐛 Fix runNode call

Adjust per update in bdb84130d6

* 🧪 Fix workflow tests

* 🐛 More merge conflict fixes

* feat: Added pin/unpin button and store mutations.

* feat: Size check. Various design and ux improvements.

*  Add transformer

*  Hoist pin data

*  Adjust endpoints for hoisted pin data

* 📘 Expand interface

* 🐛 Fix stray array

* 👕 Fix build

* 👕 Add lint exception

* 👕 Fix header

* 🎨 Add color secondary tints

*  Create `HeaderMessage` component

*  Adjust `InfoTip` component

*  Add `HeaderMessage` to `RunData`

* 🐛 Fix console error

* 👕 Fix lint

*  Consolidate `HeaderMessage` and `Callout`

*  Undo `InfoTip` changes

* 🔥 Remove duplicate icons

*  Simplify template

* 🎨 Change cursor for action text

* 👕 Fix lint

*  Add URL

* 🐛 Fix handler name

*  Use constant

* ♻️ Refactor per feedback

* fix: Various fixes after data pinning relocation.

* fix: Added store mutation for setting pinned data.

* feat: Added pinned state for workflow canvas node.

* fix: Fixed workflow saving.

* fix: Removed pinData hoisting (no longer necessary).

* feat: Added canPinData flag to hide for input pane and binary data. Fixed unpin and execute flow.

*  Fixes for canvas pin data (#3587)

*  Fixes for canvas pin data

* 📘 Rename type

* 🧪 Fix unrelated Public API test

* 🔥 Remove logging

* feat: Updated pinData mixin to no longer include extra fields.

*  Output same pindata for every run

* 🎨 Fix cropping

* 🔥 Remove unrelated logging

* feat: Moved edit button next to pin button.

* feat: Changed data to be inserted for empty state.

* chore: Changed invalid editor output translation.

* feat: Added error line reporting on JSON Validation.

* feat: Migrated pinData edit mode to store.

* chore: Merged duplicate node border color condition.

* feat: Moved pin data validation to mixin. Added check before closing ndv modal.

* fix: Changed pinned data size calculation to discard active node pin data.

* feat: Added support for rename and delete node with pin data.

* feat: Simplified editing state. Fixed edit mode in input panel after store migration.

* feat: Various data pinning improvements.

* fix: Fixed callout link underline.

* refactor: Added support for both string and objects for data size check.

* feat: Added disabled node check for input panel. Fixed monaco editor resizing.

* fix: Fixed edit mode footer size.

*  Fix pindata items per run

* 👕 Remove unneeded exception

* refactor: Added isValidPinData() helper method.

* refactor: Changed how string size in bytes in calculated.g

* refactor: Updated pinData mixin interface.

* refactor: Merged filter and reduce in pinDataSize calculation.

* fix: Changed code-editor to correct type.

* fix: Added insert test data message to trigger nodes.

* feat: Disabled data pinning for multiple output nodes.

* refactor: Updated ndv.input.disabled translation to include node name.

* refactor: Aligned n8n-button usage inside of editor-ui.

* feat: Added edit data button on json hover.

* feat: Extracted code editor into separate form component.

* feat: Added edit data button on json hover.

* feat: Added pinData and edit mode methods.

* 🔥 Remove conflict markers

* ✏️ Update i18n keys

*  Add JSON validation

* 🗃️ Add `pinData` column to `workflow_entity`

* 📘 Tighten type

*  Make `pinData` column nullable

*  Adjust workflow endpoints for pin data

* 📘 Improve types

* ✏️ Improve wording

* Inject pindata into items flow (#3420)

*  Inject pin data - Second approach

* 🔥 Remove unneeded lint exception

* feat: Added edit data button on json hover.

* feat: Extracted code editor into separate form component.

* feat: Added edit data button on json hover.

* fix: Fixed rebase conflicts.

*  Undo button change

* 🐛 Fix runNode call

Adjust per update in bdb84130d6

* 🧪 Fix workflow tests

* 🐛 More merge conflict fixes

* feat: Added pin/unpin button and store mutations.

* feat: Size check. Various design and ux improvements.

*  Add transformer

*  Hoist pin data

*  Adjust endpoints for hoisted pin data

* 📘 Expand interface

* 🐛 Fix stray array

* 👕 Fix build

* 🎨 Add color secondary tints

*  Create `HeaderMessage` component

*  Adjust `InfoTip` component

*  Add `HeaderMessage` to `RunData`

* 🐛 Fix console error

* 👕 Fix lint

*  Consolidate `HeaderMessage` and `Callout`

*  Undo `InfoTip` changes

* 🔥 Remove duplicate icons

*  Simplify template

* 🎨 Change cursor for action text

* 👕 Fix lint

*  Add URL

* 🐛 Fix handler name

*  Use constant

* ♻️ Refactor per feedback

* fix: Various fixes after data pinning relocation.

* fix: Added store mutation for setting pinned data.

* feat: Added pinned state for workflow canvas node.

*  Fixes for canvas pin data (#3587)

*  Fixes for canvas pin data

* 📘 Rename type

* 🧪 Fix unrelated Public API test

* 🔥 Remove logging

* feat: Updated pinData mixin to no longer include extra fields.

* fix: Removed pinData hoisting (no longer necessary).

* chore: Merged duplicate node border color condition.

*  Output same pindata for every run

* 🎨 Fix cropping

* 🐛 Fix excess closing template tag

* fix: Removed rogue template tag after merge.

* fix: Fixed code-editor resizing when moving ndv panel.

* feat: Added node duplication pin data.

*  Implement telemetry

* ♻️ Add clarifications from call

* fix: Fixed run data header height.

* feat: Removed border from pin data callout.

* feat: Added line-break before 'or insert pin data'.

* feat: Changed enterEditMode to always insert test data if there's no execution data.

* feat: Removed copy output tooltip.

* feat: Removed unpin tooltip.

* fix: Removed thumbtack icon rotation.

* fix: Removed run info from Edit Output title.

* feat: Hid edit and pin buttons when editing.

* feat: Updated monaco code-editor padding and borders.

* feat: Progress on pinData error message format

* feat: Updated copy feature to work without any selected value.

* feat: Moved save and cancel buttons. Cleared notifications on save.

* feat: Changed pin data beforeClosing confirm text.

* feat: Closing ndv when discarding or saving pindata on close.

* feat: Added split in batches node to pin data denylist.

* fix: Added missing margin-bottom to webhook node.

* feat: Moved thumbtack icon to the right, replacing the checkmark.

* fix: Hid pagination while editing.

* feat: Added pin data discovery flow.

* feat: Changed pin data discovery flow to avoid tooltip glitching.

* fix: Changed copy selection to copy all input data.

* feat: Updated pin data validation error message for unexpected single quotes.

* fix: Replaced :manual='true' prop with manual shorthand.

* fix: Removed unused variable.

* chore: Renamed translation key to node.discovery.pinData.

* refactor: Extracted isPinDataNodeType to pinData mixin.

* fix: Updated watch condition to improve performance.

* refactor: Renamed some pin data variables and methods as per review.

* fix: Added partial translation for JSON.parse pin data error messages.

* chore: Temporarily disabled failing unit test.

* 🧪 Fix data pinning workflow retrieval test

* 🔥 Remove unused imports

* 🔥 Remove leftover line

*  Skip pindata node issues on BE

*  Skip pindata node issues on FE

*  Hide `RunInfo` for pindata node

*  Hide purple banner in edit output mode

* feat: Updated data pinning discoverability flow.

* fix: Fixed paginated data pinning.

* fix: Disabled pin data in read only mode.

* 🐛 Fix runtime error with non-array

* fix: Loading pin data when opening execution.

*  Adjust stale data warning for pinned data

*  Skip auth in endpoint

*  Mark start node for pinned trigger

* ✏️ Comment on passthrough

* 🔥 Remove comment

* Final pindata metrics changes (#3673)

* 🐛 Fix `pinData` tracked as `0`

*  Add `is_pinned` to `nodesGraph`

* 📘 Extend `IWorkflowBase`

*  Handle `pinData` being `undefined`

*  Add `data_pinning_tooltip_presented`

* ♻️ Refactor to remove circular dependency

* fix: Added pin data handling when importing workflow. (#3698)

* 🔥 Remove helper from WorkflowExecute

*  Add logic for single pinned trigger

* 👕 Remove lint exception

* fix: Added pin data handling in importWorkflowExact.

* N8N-4077 data pinning discoverability part 2 (#3701)

* fix: Fixed pin data discovery tooltip position when moving canvas.

* feat: Updated data pinning discovery tooltip copy.

* Fix data pinning build (#3702)

*  Disable edit button for disabled node

*  Ensure disabled pinned nodes are passthrough

* 🐛 Fix JSON key unfurling in edit mode

*  Improve implementation

* 🐛 Fix console error

* fix: Fixed copying pinned output data. (#3715)

* Fix pinning for webhook responding with output from last node (#3719)

* fix: Fixed entering edit mode after refresh.

* fix: Fixed type error during build.

* fix: RunData import formatting.

* chore: Updated pin data types.

* fix: Added missing type to stringSizeInBytes.

Co-authored-by: Iván Ovejero <ivov.src@gmail.com>

* fix: Showing pin data without executing the node only in output pane.

* fix: Updated no data message when previous node not executed.

* feat: Added expression input and evaluation for pin data nodes without execution.

* chore: Fixed linting issues and removed remnant console.log().

* chore: Undone package-lock changes.

* fix: Removed pin data store changes.

* fix: Created a new object using vuex runExecutionData.

* fix: Fixed bug appearing when adding a new node after executing.

* fix: Fix editor-ui build

* feat: Added green node connectors when having pin data output.

* chore: Fixed linting errors.

* fix: Added pin data eventBus unsubscribe.

* fix: Added pin data color check after adding a connection.

* 🎨 Add pindata styles

Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
This commit is contained in:
Alex Grozav
2022-07-20 18:50:39 +03:00
committed by GitHub
parent fb67543b2f
commit 15693b0056
99 changed files with 4145 additions and 1868 deletions

View File

@@ -74,7 +74,12 @@
:class="['add-sticky-button', showStickyButton ? 'visible-button' : '']"
@click="nodeTypeSelected(STICKY_NODE_TYPE)"
>
<n8n-icon-button size="large" :icon="['far', 'note-sticky']" type="outline" :title="$locale.baseText('nodeView.addSticky')"/>
<n8n-icon-button
size="medium"
type="secondary"
:icon="['far', 'note-sticky']"
:title="$locale.baseText('nodeView.addSticky')"
/>
</div>
</div>
</div>
@@ -84,33 +89,27 @@
@closeNodeCreator="closeNodeCreator"
/>
<div :class="{ 'zoom-menu': true, 'regular-zoom-menu': !isDemo, 'demo-zoom-menu': isDemo, expanded: !sidebarMenuCollapsed }">
<button @click="zoomToFit" class="button-white" :title="$locale.baseText('nodeView.zoomToFit')">
<font-awesome-icon icon="expand"/>
</button>
<button @click="zoomIn()" class="button-white" :title="$locale.baseText('nodeView.zoomIn')">
<font-awesome-icon icon="search-plus"/>
</button>
<button @click="zoomOut()" class="button-white" :title="$locale.baseText('nodeView.zoomOut')">
<font-awesome-icon icon="search-minus"/>
</button>
<button
<n8n-icon-button @click="zoomToFit" type="tertiary" size="large" :title="$locale.baseText('nodeView.zoomToFit')" icon="expand" />
<n8n-icon-button @click="zoomIn" type="tertiary" size="large" :title="$locale.baseText('nodeView.zoomIn')" icon="search-plus" />
<n8n-icon-button @click="zoomOut" type="tertiary" size="large" :title="$locale.baseText('nodeView.zoomOut')" icon="search-minus" />
<n8n-icon-button
v-if="nodeViewScale !== 1 && !isDemo"
@click="resetZoom()"
class="button-white"
@click="resetZoom"
type="tertiary"
size="large"
:title="$locale.baseText('nodeView.resetZoom')"
>
<font-awesome-icon icon="undo" :title="$locale.baseText('nodeView.resetZoom')"/>
</button>
icon="undo"
/>
</div>
<div class="workflow-execute-wrapper" v-if="!isReadOnly">
<n8n-button
@click.stop="onRunWorkflow"
:loading="workflowRunning"
:label="runButtonText"
:title="$locale.baseText('nodeView.executesTheWorkflowFromTheStartOrWebhookNode')"
size="large"
icon="play-circle"
:title="$locale.baseText('nodeView.executesTheWorkflowFromTheStartOrWebhookNode')"
:type="workflowRunning ? 'light' : 'primary'"
type="primary"
/>
<n8n-icon-button
@@ -118,7 +117,7 @@
icon="stop"
size="large"
class="stop-execution"
type="light"
type="secondary"
:title="stopExecutionInProgress
? $locale.baseText('nodeView.stoppingCurrentExecution')
: $locale.baseText('nodeView.stopCurrentExecution')
@@ -133,7 +132,7 @@
icon="stop"
size="large"
:title="$locale.baseText('nodeView.stopWaitingForWebhookCall')"
type="light"
type="secondary"
@click.stop="stopWaitingForWebhook()"
/>
@@ -196,6 +195,7 @@ import {
TelemetryHelpers,
ITelemetryTrackProperties,
IWorkflowBase,
PinData,
} from 'n8n-workflow';
import {
ICredentialsResponse,
@@ -220,6 +220,7 @@ import {
import '../plugins/N8nCustomConnectorType';
import '../plugins/PlusEndpointType';
import {dataPinningEventBus} from "@/event-bus/data-pinning-event-bus";
interface AddNodeOptions {
position?: XYPosition;
@@ -549,6 +550,7 @@ export default mixins(
this.$store.commit('setWorkflowId', PLACEHOLDER_EMPTY_WORKFLOW_ID);
this.$store.commit('setWorkflowExecutionData', data);
this.$store.commit('setWorkflowPinData', data.workflowData.pinData);
await this.addNodes(JSON.parse(JSON.stringify(data.workflowData.nodes)), JSON.parse(JSON.stringify(data.workflowData.connections)));
this.$nextTick(() => {
@@ -556,6 +558,7 @@ export default mixins(
this.$store.commit('setStateDirty', false);
});
this.$externalHooks().run('execution.open', { workflowId: data.workflowData.id, workflowName: data.workflowData.name, executionId });
this.$telemetry.track('User opened read-only execution', { workflow_id: data.workflowData.id, execution_mode: data.mode, execution_finished: data.finished });
@@ -610,6 +613,11 @@ export default mixins(
this.resetWorkspace();
data.workflow.nodes = CanvasHelpers.getFixedNodesList(data.workflow.nodes);
await this.addNodes(data.workflow.nodes, data.workflow.connections);
if (data.workflow.pinData) {
this.$store.commit('setWorkflowPinData', data.workflow.pinData);
}
this.$nextTick(() => {
this.zoomToFit();
});
@@ -678,6 +686,7 @@ export default mixins(
this.$store.commit('setWorkflowId', workflowId);
this.$store.commit('setWorkflowName', {newName: data.name, setStateDirty: false});
this.$store.commit('setWorkflowSettings', data.settings || {});
this.$store.commit('setWorkflowPinData', data.pinData || {});
const tags = (data.tags || []) as ITag[];
this.$store.commit('tags/upsertTags', tags);
@@ -1312,6 +1321,10 @@ export default mixins(
});
});
if (workflowData.pinData) {
this.$store.commit('setWorkflowPinData', workflowData.pinData);
}
const tagsEnabled = this.$store.getters['settings/areTagsEnabled'];
if (importTags && tagsEnabled && Array.isArray(workflowData.tags)) {
const allTags: ITag[] = await this.$store.dispatch('tags/fetchAll');
@@ -2077,6 +2090,10 @@ export default mixins(
// so if we do not connect we have to save the connection manually
this.$store.commit('addConnection', connectionProperties);
}
setTimeout(() => {
this.addPinDataConnections(this.$store.getters.pinData);
});
},
__removeConnection (connection: [IConnection, IConnection], removeVisualConnection = false) {
if (removeVisualConnection === true) {
@@ -2168,6 +2185,14 @@ export default mixins(
await this.addNodes([newNodeData]);
const pinData = this.$store.getters['pinDataByNodeName'](nodeName);
if (pinData) {
this.$store.commit('pinData', {
node: newNodeData,
data: pinData,
});
}
this.$store.commit('setStateDirty', true);
// Automatically deselect all nodes and select the current one and also active
@@ -2271,6 +2296,7 @@ export default mixins(
if (connection) {
const output = outputMap[sourceOutputIndex][targetNodeName][targetInputIndex];
if (!output || !output.total) {
CanvasHelpers.resetConnection(connection);
}
@@ -2832,7 +2858,7 @@ export default mixins(
}
this.$store.commit('removeAllConnections', {setStateDirty: false});
this.$store.commit('removeAllNodes', {setStateDirty: false});
this.$store.commit('removeAllNodes', { setStateDirty: false, removePinData: true });
// Reset workflow execution data
this.$store.commit('setWorkflowExecutionData', null);
@@ -2940,6 +2966,31 @@ export default mixins(
await this.importWorkflowData(workflowData);
}
},
addPinDataConnections(pinData: PinData) {
Object.keys(pinData).forEach((nodeName) => {
// @ts-ignore
const connections = this.instance.getConnections({
source: NODE_NAME_PREFIX + this.getNodeIndex(nodeName),
}) as Connection[];
connections.forEach((connection) => {
CanvasHelpers.addConnectionOutputSuccess(connection, {
total: pinData[nodeName].length,
iterations: 0,
});
});
});
},
removePinDataConnections(pinData: PinData) {
Object.keys(pinData).forEach((nodeName) => {
// @ts-ignore
const connections = this.instance.getConnections({
source: NODE_NAME_PREFIX + this.getNodeIndex(nodeName),
}) as Connection[];
connections.forEach(CanvasHelpers.resetConnection);
});
},
},
async mounted () {
@@ -2989,10 +3040,14 @@ export default mixins(
setTimeout(() => {
this.$store.dispatch('users/showPersonalizationSurvey');
this.checkForNewVersions();
this.addPinDataConnections(this.$store.getters.pinData);
}, 0);
});
this.$externalHooks().run('nodeView.mount');
dataPinningEventBus.$on('pin-data', this.addPinDataConnections);
dataPinningEventBus.$on('unpin-data', this.removePinDataConnections);
},
destroyed () {
@@ -3002,6 +3057,9 @@ export default mixins(
this.$root.$off('newWorkflow', this.newWorkflow);
this.$root.$off('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
dataPinningEventBus.$off('pin-data', this.addPinDataConnections);
dataPinningEventBus.$off('unpin-data', this.removePinDataConnections);
},
});
</script>
@@ -3013,8 +3071,8 @@ export default mixins(
position: fixed;
left: $--sidebar-width + $--zoom-menu-margin;
width: 200px;
bottom: 45px;
width: 210px;
bottom: 44px;
line-height: 25px;
color: #444;
padding-right: 5px;
@@ -3026,6 +3084,16 @@ export default mixins(
button {
border: var(--border-base);
}
> * {
+ * {
margin-left: var(--spacing-3xs);
}
&:hover {
transform: scale(1.1);
}
}
}
.regular-zoom-menu {
@@ -3198,23 +3266,6 @@ export default mixins(
text-align: right;
}
.button-white {
border: none;
padding: 0.3em;
margin: 0 0.1em;
border-radius: 3px;
font-size: 1.2em;
background: #fff;
width: 40px;
height: 40px;
color: #666;
cursor: pointer;
&:hover {
transform: scale(1.1);
}
}
.connection-actions {
&:hover {
display: block !important;