fix(Move Binary Data Node): Stringify objects before encoding them in MoveBinaryData (#4882)

* stringify objects before encoding them objects in MoveBinaryData

* add fileSize and fileType on MoveBinaryData converted data

* show `view` option for text files as well

* improve how JSON binary data is shown in the UI
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2022-12-11 14:10:54 +01:00 committed by GitHub
parent f4481e24e8
commit 3b969d2cd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 80 additions and 38 deletions

View file

@ -64,7 +64,7 @@ import {
NodeExecutionWithMetadata, NodeExecutionWithMetadata,
IPairedItemData, IPairedItemData,
deepCopy, deepCopy,
BinaryFileType, fileTypeFromMimeType,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { Agent } from 'https'; import { Agent } from 'https';
@ -836,13 +836,6 @@ export async function getBinaryDataBuffer(
return BinaryDataManager.getInstance().retrieveBinaryData(binaryData); 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. * Store an incoming IBinaryData & related buffer using the configured binary data manager.
* *

View file

@ -1,5 +1,5 @@
<template> <template>
<div v-if="windowVisible" class="binary-data-window"> <div v-if="windowVisible" :class="['binary-data-window', binaryData?.fileType]">
<n8n-button <n8n-button
@click.stop="closeWindow" @click.stop="closeWindow"
size="small" size="small"
@ -98,6 +98,10 @@ export default mixins(
overflow: hidden; overflow: hidden;
text-align: center; text-align: center;
&.json {
overflow: auto;
}
.binary-data-window-wrapper { .binary-data-window-wrapper {
margin-top: .5em; margin-top: .5em;
padding: 0 1em; padding: 0 1em;

View file

@ -11,6 +11,12 @@
<source :src="embedSource" :type="binaryData.mimeType"> <source :src="embedSource" :type="binaryData.mimeType">
{{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }} {{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
</video> </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()"/> <embed v-else :src="embedSource" class="binary-data" :class="embedClass()"/>
</span> </span>
</span> </span>
@ -19,38 +25,56 @@
<script lang="ts"> <script lang="ts">
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { restApi } from '@/mixins/restApi'; 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( export default mixins(
restApi, restApi,
) )
.extend({ .extend({
name: 'BinaryDataDisplayEmbed', name: 'BinaryDataDisplayEmbed',
props: [ components: {
'binaryData', // IBinaryData VueJsonPretty,
], },
props: {
binaryData: {
type: Object as PropType<IBinaryData>,
required: true,
},
},
data() { data() {
return { return {
isLoading: true, isLoading: true,
embedSource: '', embedSource: '',
error: false, error: false,
jsonData: '',
}; };
}, },
async mounted() { async mounted() {
const id = this.binaryData?.id; const id = this.binaryData?.id;
if(!id) { const isJSONData = this.binaryData.fileType === 'json';
this.embedSource = 'data:' + this.binaryData.mimeType + ';base64,' + this.binaryData.data;
this.isLoading = false;
return;
}
if(!id) {
if (isJSONData) {
this.jsonData = jsonParse(atob(this.binaryData.data));
} else {
this.embedSource = 'data:' + this.binaryData.mimeType + ';base64,' + this.binaryData.data;
}
} else {
try { try {
this.embedSource = this.restApi().getBinaryUrl(id); const binaryUrl = this.restApi().getBinaryUrl(id);
this.isLoading = false; if (isJSONData) {
this.jsonData = await (await fetch(binaryUrl)).json();
} else {
this.embedSource = binaryUrl;
}
} catch (e) { } catch (e) {
this.isLoading = false;
this.error = true; this.error = true;
} }
}
this.isLoading = false;
}, },
methods: { methods: {
embedClass(): string[] { embedClass(): string[] {

View file

@ -1060,15 +1060,15 @@ export default mixins(
this.updateNodesExecutionIssues(); this.updateNodesExecutionIssues();
}, },
isViewable (index: number, key: string): boolean { isViewable (index: number, key: string): boolean {
const { fileType }: IBinaryData = this.binaryData[index][key]; const { fileType } = this.binaryData[index][key];
return !!fileType && ['image', 'video'].includes(fileType); return !!fileType && ['image', 'video', 'text', 'json'].includes(fileType);
}, },
isDownloadable (index: number, key: string): boolean { isDownloadable (index: number, key: string): boolean {
const { mimeType, fileName }: IBinaryData = this.binaryData[index][key]; const { mimeType, fileName } = this.binaryData[index][key];
return !!(mimeType && fileName); return !!(mimeType && fileName);
}, },
async downloadBinaryData (index: number, key: string) { 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) { if(id) {
const url = this.restApi().getBinaryUrl(id); const url = this.restApi().getBinaryUrl(id);

View file

@ -1,4 +1,5 @@
import { get, set, unset } from 'lodash'; import { get, set, unset } from 'lodash';
import prettyBytes from 'pretty-bytes';
import { BINARY_ENCODING, IExecuteFunctions } from 'n8n-core'; import { BINARY_ENCODING, IExecuteFunctions } from 'n8n-core';
@ -12,6 +13,7 @@ import {
INodeTypeDescription, INodeTypeDescription,
jsonParse, jsonParse,
NodeOperationError, NodeOperationError,
fileTypeFromMimeType,
} from 'n8n-workflow'; } from 'n8n-workflow';
import iconv from 'iconv-lite'; import iconv from 'iconv-lite';
@ -415,20 +417,26 @@ export class MoveBinaryData implements INodeType {
newItem.binary = {}; newItem.binary = {};
} }
const mimeType = (options.mimeType as string) || 'application/json';
const convertedValue: IBinaryData = {
data: '',
mimeType,
fileType: fileTypeFromMimeType(mimeType),
};
if (options.dataIsBase64 !== true) { if (options.dataIsBase64 !== true) {
if (options.useRawData !== true) { if (options.useRawData !== true || typeof value === 'object') {
value = JSON.stringify(value); value = JSON.stringify(value);
} }
value = iconv convertedValue.fileSize = prettyBytes(value.length);
.encode(value as string, encoding, { addBOM: options.addBOM as boolean })
.toString(BINARY_ENCODING);
}
const convertedValue: IBinaryData = { convertedValue.data = iconv
data: value as string, .encode(value, encoding, { addBOM: options.addBOM as boolean })
mimeType: (options.mimeType as string) || 'application/json', .toString(BINARY_ENCODING);
}; } else {
convertedValue.data = value as unknown as string;
}
if (options.fileName) { if (options.fileName) {
convertedValue.fileName = options.fileName as string; convertedValue.fileName = options.fileName as string;

View file

@ -797,6 +797,7 @@
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",
"pg": "^8.3.0", "pg": "^8.3.0",
"pg-promise": "^10.5.8", "pg-promise": "^10.5.8",
"pretty-bytes": "^5.6.0",
"promise-ftp": "^1.3.5", "promise-ftp": "^1.3.5",
"redis": "^3.1.1", "redis": "^3.1.1",
"request": "^2.88.2", "request": "^2.88.2",

View file

@ -27,7 +27,7 @@ export type IAllExecuteFunctions =
| ITriggerFunctions | ITriggerFunctions
| IWebhookFunctions; | IWebhookFunctions;
export type BinaryFileType = 'text' | 'image' | 'video'; export type BinaryFileType = 'text' | 'json' | 'image' | 'video';
export interface IBinaryData { export interface IBinaryData {
[key: string]: string | undefined; [key: string]: string | undefined;
data: string; data: string;

View file

@ -18,7 +18,7 @@ export * from './WorkflowErrors';
export * from './WorkflowHooks'; export * from './WorkflowHooks';
export * from './VersionedNodeType'; export * from './VersionedNodeType';
export { LoggerProxy, NodeHelpers, ObservableObject, TelemetryHelpers }; export { LoggerProxy, NodeHelpers, ObservableObject, TelemetryHelpers };
export { deepCopy, jsonParse, sleep } from './utils'; export { deepCopy, jsonParse, sleep, fileTypeFromMimeType } from './utils';
export { export {
isINodeProperties, isINodeProperties,
isINodePropertyOptions, isINodePropertyOptions,

View file

@ -1,3 +1,5 @@
import { BinaryFileType } from './Interfaces';
export type Primitives = string | number | boolean | bigint | symbol | null | undefined; export type Primitives = string | number | boolean | bigint | symbol | null | undefined;
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */
@ -64,3 +66,11 @@ export const sleep = async (ms: number): Promise<void> =>
new Promise((resolve) => { new Promise((resolve) => {
setTimeout(resolve, ms); setTimeout(resolve, ms);
}); });
export function fileTypeFromMimeType(mimeType: string): BinaryFileType | undefined {
if (mimeType.startsWith('application/json')) return 'json';
if (mimeType.startsWith('image/')) return 'image';
if (mimeType.startsWith('video/')) return 'video';
if (mimeType.startsWith('text/')) return 'text';
return;
}

View file

@ -738,6 +738,7 @@ importers:
pdf-parse: ^1.1.1 pdf-parse: ^1.1.1
pg: ^8.3.0 pg: ^8.3.0
pg-promise: ^10.5.8 pg-promise: ^10.5.8
pretty-bytes: ^5.6.0
promise-ftp: ^1.3.5 promise-ftp: ^1.3.5
redis: ^3.1.1 redis: ^3.1.1
request: ^2.88.2 request: ^2.88.2
@ -796,6 +797,7 @@ importers:
pdf-parse: 1.1.1 pdf-parse: 1.1.1
pg: 8.8.0 pg: 8.8.0
pg-promise: 10.12.0 pg-promise: 10.12.0
pretty-bytes: 5.6.0
promise-ftp: 1.3.5_promise-ftp-common@1.1.5 promise-ftp: 1.3.5_promise-ftp-common@1.1.5
redis: 3.1.2 redis: 3.1.2
request: 2.88.2 request: 2.88.2