Skip to content

Commit

Permalink
Merge branch 'master' into ADO-32-e2e-undo
Browse files Browse the repository at this point in the history
* master:
  fix(editor): Avoid adding manual trigger node when webhook node is added (#4887)
  ci: Add libc6-compat to nightly docker builds (no-changelog) (#4885)
  fix(Move Binary Data Node): Stringify objects before encoding them in MoveBinaryData (#4882)
  ci: Upgrade pnpm and turborepo (no-changelog) (#4878)
  fix(editor): Fix for broken tab navigation (#4881)
  feat(editor): Add undo/redo support for canvas actions (#4787)
  fix(Split In Batches Node): Fix issue with pairedItem (#4873)
  fix: Update duplicate action (#4858)

# Conflicts:
#	packages/editor-ui/src/Interface.ts
#	packages/editor-ui/src/mixins/history.ts
#	packages/editor-ui/src/views/NodeView.vue
  • Loading branch information
MiloradFilipovic committed Dec 12, 2022
2 parents 68ee807 + b689d2d commit 692e8c4
Show file tree
Hide file tree
Showing 20 changed files with 194 additions and 84 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test-workflows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:

- uses: pnpm/[email protected]
with:
version: 7.14.2
version: 7.18.1

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
Expand Down
1 change: 1 addition & 0 deletions docker/images/n8n-custom/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ COPY turbo.json package.json .npmrc pnpm-lock.yaml pnpm-workspace.yaml tsconfig.
COPY scripts ./scripts
COPY packages ./packages

RUN apk add --update libc6-compat
RUN corepack enable && corepack prepare --activate
RUN chown -R node:node .
USER node
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
"homepage": "https://n8n.io",
"engines": {
"node": ">=16.9",
"pnpm": ">=7.5"
"pnpm": ">=7.18"
},
"packageManager": "pnpm@7.14.2",
"packageManager": "pnpm@7.18.1",
"scripts": {
"preinstall": "node scripts/block-npm-install.js",
"build": "turbo run build",
Expand Down Expand Up @@ -53,7 +53,7 @@
"supertest": "^6.2.2",
"ts-jest": "^29.0.3",
"tsc-watch": "^5.0.3",
"turbo": "1.5.5",
"turbo": "1.6.3",
"typescript": "^4.8.4"
},
"pnpm": {
Expand Down
24 changes: 16 additions & 8 deletions packages/core/src/NodeExecuteFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
IOAuth2Options,
IPollFunctions,
IRunExecutionData,
ISourceData,
ITaskDataConnections,
ITriggerFunctions,
IWebhookData,
Expand All @@ -63,7 +64,7 @@ import {
NodeExecutionWithMetadata,
IPairedItemData,
deepCopy,
BinaryFileType,
fileTypeFromMimeType,
} from 'n8n-workflow';

import { Agent } from 'https';
Expand Down Expand Up @@ -835,13 +836,6 @@ export async function getBinaryDataBuffer(
return BinaryDataManager.getInstance().retrieveBinaryData(binaryData);
}

function fileTypeFromMimeType(mimeType: string): BinaryFileType | undefined {
if (mimeType.startsWith('image/')) return 'image';
if (mimeType.startsWith('video/')) return 'video';
if (mimeType.startsWith('text/') || mimeType.startsWith('application/json')) return 'text';
return;
}

/**
* Store an incoming IBinaryData & related buffer using the configured binary data manager.
*
Expand Down Expand Up @@ -2320,6 +2314,13 @@ export function getExecuteFunctions(

return inputData[inputName][inputIndex] as INodeExecutionData[];
},
getInputSourceData: (inputIndex = 0, inputName = 'main') => {
if (executeData?.source === null) {
// Should never happen as n8n sets it automatically
throw new Error('Source data is missing!');
}
return executeData.source[inputName][inputIndex];
},
getNodeParameter: (
parameterName: string,
itemIndex: number,
Expand Down Expand Up @@ -2580,6 +2581,13 @@ export function getExecuteSingleFunctions(

return allItems[itemIndex];
},
getInputSourceData: (inputIndex = 0, inputName = 'main') => {
if (executeData?.source === null) {
// Should never happen as n8n sets it automatically
throw new Error('Source data is missing!');
}
return executeData.source[inputName][inputIndex] as ISourceData;
},
getItemIndex() {
return itemIndex;
},
Expand Down
1 change: 0 additions & 1 deletion packages/editor-ui/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import {
INodeActionTypeDescription,
} from 'n8n-workflow';
import { FAKE_DOOR_FEATURES } from './constants';
import {ICredentialsDb} from "n8n";
import { BulkCommand, Undoable } from '@/models/history';

export * from 'n8n-design-system/src/types';
Expand Down
6 changes: 5 additions & 1 deletion packages/editor-ui/src/components/BinaryDataDisplay.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div v-if="windowVisible" class="binary-data-window">
<div v-if="windowVisible" :class="['binary-data-window', binaryData?.fileType]">
<n8n-button
@click.stop="closeWindow"
size="small"
Expand Down Expand Up @@ -98,6 +98,10 @@ export default mixins(
overflow: hidden;
text-align: center;
&.json {
overflow: auto;
}
.binary-data-window-wrapper {
margin-top: .5em;
padding: 0 1em;
Expand Down
52 changes: 38 additions & 14 deletions packages/editor-ui/src/components/BinaryDataDisplayEmbed.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
<source :src="embedSource" :type="binaryData.mimeType">
{{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
</video>
<vue-json-pretty
v-else-if="binaryData.fileType === 'json'"
:data="jsonData"
:deep="3"
:showLength="true"
/>
<embed v-else :src="embedSource" class="binary-data" :class="embedClass()"/>
</span>
</span>
Expand All @@ -19,38 +25,56 @@
<script lang="ts">
import mixins from 'vue-typed-mixins';
import { restApi } from '@/mixins/restApi';
import type { IBinaryData } from 'n8n-workflow';
import { IBinaryData, jsonParse } from 'n8n-workflow';
import type { PropType } from 'vue';
import VueJsonPretty from 'vue-json-pretty';
export default mixins(
restApi,
)
.extend({
name: 'BinaryDataDisplayEmbed',
props: [
'binaryData', // IBinaryData
],
components: {
VueJsonPretty,
},
props: {
binaryData: {
type: Object as PropType<IBinaryData>,
required: true,
},
},
data() {
return {
isLoading: true,
embedSource: '',
error: false,
jsonData: '',
};
},
async mounted() {
const id = this.binaryData?.id;
const isJSONData = this.binaryData.fileType === 'json';
if(!id) {
this.embedSource = 'data:' + this.binaryData.mimeType + ';base64,' + this.binaryData.data;
this.isLoading = false;
return;
if (isJSONData) {
this.jsonData = jsonParse(atob(this.binaryData.data));
} else {
this.embedSource = 'data:' + this.binaryData.mimeType + ';base64,' + this.binaryData.data;
}
} else {
try {
const binaryUrl = this.restApi().getBinaryUrl(id);
if (isJSONData) {
this.jsonData = await (await fetch(binaryUrl)).json();
} else {
this.embedSource = binaryUrl;
}
} catch (e) {
this.error = true;
}
}
try {
this.embedSource = this.restApi().getBinaryUrl(id);
this.isLoading = false;
} catch (e) {
this.isLoading = false;
this.error = true;
}
this.isLoading = false;
},
methods: {
embedClass(): string[] {
Expand Down
8 changes: 4 additions & 4 deletions packages/editor-ui/src/components/RunData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1060,15 +1060,15 @@ export default mixins(
this.updateNodesExecutionIssues();
},
isViewable (index: number, key: string): boolean {
const { fileType }: IBinaryData = this.binaryData[index][key];
return !!fileType && ['image', 'video'].includes(fileType);
const { fileType } = this.binaryData[index][key];
return !!fileType && ['image', 'video', 'text', 'json'].includes(fileType);
},
isDownloadable (index: number, key: string): boolean {
const { mimeType, fileName }: IBinaryData = this.binaryData[index][key];
const { mimeType, fileName } = this.binaryData[index][key];
return !!(mimeType && fileName);
},
async downloadBinaryData (index: number, key: string) {
const { id, data, fileName, fileExtension, mimeType }: IBinaryData = this.binaryData[index][key];
const { id, data, fileName, fileExtension, mimeType } = this.binaryData[index][key];
if(id) {
const url = this.restApi().getBinaryUrl(id);
Expand Down
6 changes: 5 additions & 1 deletion packages/editor-ui/src/mixins/history.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MAIN_HEADER_TABS } from './../constants';
import { useNDVStore } from '@/stores/ndv';
import { BulkCommand, Undoable } from '@/models/history';
import { useHistoryStore } from '@/stores/history';
Expand All @@ -9,6 +10,7 @@ import { Command } from '@/models/history';
import { debounceHelper } from '@/mixins/debounce';
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
import Vue from 'vue';
import { getNodeViewTab } from '@/utils';

const UNDO_REDO_DEBOUNCE_INTERVAL = 100;

Expand All @@ -32,7 +34,9 @@ export const historyHelper = mixins(debounceHelper, deviceSupportHelpers).extend
},
methods: {
handleKeyDown(event: KeyboardEvent) {
if (event.repeat) return;
const currentNodeViewTab = getNodeViewTab(this.$route);

if (event.repeat || currentNodeViewTab !== MAIN_HEADER_TABS.WORKFLOW) return;
if (this.isCtrlKeyPressed(event) && event.key === 'z') {
event.preventDefault();
if (!this.isNDVOpen) {
Expand Down
2 changes: 1 addition & 1 deletion packages/editor-ui/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@
"duplicateWorkflowDialog.chooseOrCreateATag": "Choose or create a tag",
"duplicateWorkflowDialog.duplicateWorkflow": "Duplicate Workflow",
"duplicateWorkflowDialog.enterWorkflowName": "Enter workflow name",
"duplicateWorkflowDialog.save": "@:_reusableBaseText.save",
"duplicateWorkflowDialog.save": "Duplicate",
"duplicateWorkflowDialog.errors.missingName.title": "Name missing",
"duplicateWorkflowDialog.errors.missingName.message": "Please enter a name.",
"duplicateWorkflowDialog.errors.forbidden.title": "Duplicate workflow failed",
Expand Down
6 changes: 3 additions & 3 deletions packages/editor-ui/src/stores/nodeCreator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import startCase from 'lodash.startCase';
import { defineStore } from "pinia";
import { INodePropertyCollection, INodePropertyOptions, IDataObject, INodeProperties, INodeTypeDescription, deepCopy, INodeParameters, INodeActionTypeDescription } from 'n8n-workflow';
import { STORES, MANUAL_TRIGGER_NODE_TYPE, CORE_NODES_CATEGORY, CALENDLY_TRIGGER_NODE_TYPE, TRIGGER_NODE_FILTER } from "@/constants";
import { STORES, MANUAL_TRIGGER_NODE_TYPE, CORE_NODES_CATEGORY, CALENDLY_TRIGGER_NODE_TYPE, TRIGGER_NODE_FILTER, WEBHOOK_NODE_TYPE } from "@/constants";
import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useWorkflowsStore } from './workflows';
import { CUSTOM_API_CALL_KEY, ALL_NODE_FILTER } from '@/constants';
Expand Down Expand Up @@ -276,11 +276,11 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, {
if(!nodeType) return [];

const { workflowTriggerNodes } = useWorkflowsStore();
const isTriggerAction = nodeType.toLocaleLowerCase().includes('trigger');
const isTrigger = nodeType.toLocaleLowerCase().includes('trigger') || nodeType === WEBHOOK_NODE_TYPE;
const workflowContainsTrigger = workflowTriggerNodes.length > 0;
const isTriggerPanel = useNodeCreatorStore().selectedType === TRIGGER_NODE_FILTER;

const nodeTypes = !isTriggerAction && !workflowContainsTrigger && isTriggerPanel
const nodeTypes = !isTrigger && !workflowContainsTrigger && isTriggerPanel
? [MANUAL_TRIGGER_NODE_TYPE, nodeType]
: [nodeType];

Expand Down
12 changes: 6 additions & 6 deletions packages/editor-ui/src/views/NodeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ export default mixins(
next();
return;
}
// Make sure workflow id is empty when leaving the editor
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
const result = this.uiStore.stateIsDirty;
if (result) {
const confirmModal = await this.confirmModal(
Expand Down Expand Up @@ -1136,7 +1137,7 @@ export default mixins(
});
this.historyStore.startRecordingUndo();
nodesToDelete.forEach((nodeName: string) => {
this.removeNode(nodeName, true, true);
this.removeNode(nodeName, true, false);
});
setTimeout(() => {
this.historyStore.stopRecordingUndo();
Expand Down Expand Up @@ -2610,7 +2611,7 @@ export default mixins(
});
});
},
removeNode(nodeName: string, trackHistory = false, partOfBulkDelete = false) {
removeNode(nodeName: string, trackHistory = false, trackBulk = true) {
if (!this.editAllowedCheck()) {
return;
}
Expand All @@ -2620,7 +2621,7 @@ export default mixins(
return;
}
if (trackHistory && !partOfBulkDelete) {
if (trackHistory && trackBulk) {
this.historyStore.startRecordingUndo();
}
Expand Down Expand Up @@ -2715,7 +2716,7 @@ export default mixins(
this.historyStore.pushCommandToUndo(new RemoveNodeCommand(node, this));
}
}, 0); // allow other events to finish like drag stop
if (trackHistory && !partOfBulkDelete) {
if (trackHistory && trackBulk) {
const recordingTimeout = waitForNewConnection ? 100 : 0;
setTimeout(() => {
this.historyStore.stopRecordingUndo();
Expand Down Expand Up @@ -3547,7 +3548,6 @@ export default mixins(
dataPinningEventBus.$off('pin-data', this.addPinDataConnections);
dataPinningEventBus.$off('unpin-data', this.removePinDataConnections);
nodeViewEventBus.$off('saveWorkflow', this.saveCurrentWorkflowExternal);
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
},
destroyed() {
this.resetWorkspace();
Expand Down
24 changes: 16 additions & 8 deletions packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { get, set, unset } from 'lodash';
import prettyBytes from 'pretty-bytes';

import { BINARY_ENCODING, IExecuteFunctions } from 'n8n-core';

Expand All @@ -12,6 +13,7 @@ import {
INodeTypeDescription,
jsonParse,
NodeOperationError,
fileTypeFromMimeType,
} from 'n8n-workflow';

import iconv from 'iconv-lite';
Expand Down Expand Up @@ -415,21 +417,27 @@ export class MoveBinaryData implements INodeType {
newItem.binary = {};
}

const mimeType = (options.mimeType as string) || 'application/json';
const convertedValue: IBinaryData = {
data: '',
mimeType,
fileType: fileTypeFromMimeType(mimeType),
};

if (options.dataIsBase64 !== true) {
if (options.useRawData !== true) {
if (options.useRawData !== true || typeof value === 'object') {
value = JSON.stringify(value);
}

value = iconv
.encode(value as string, encoding, { addBOM: options.addBOM as boolean })
convertedValue.fileSize = prettyBytes(value.length);

convertedValue.data = iconv
.encode(value, encoding, { addBOM: options.addBOM as boolean })
.toString(BINARY_ENCODING);
} else {
convertedValue.data = value as unknown as string;
}

const convertedValue: IBinaryData = {
data: value as string,
mimeType: (options.mimeType as string) || 'application/json',
};

if (options.fileName) {
convertedValue.fileName = options.fileName as string;
}
Expand Down
Loading

0 comments on commit 692e8c4

Please sign in to comment.