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,
IPairedItemData,
deepCopy,
BinaryFileType,
fileTypeFromMimeType,
} from 'n8n-workflow';
import { Agent } from 'https';
@ -836,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.
*

View file

@ -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"
@ -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;

View file

@ -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>
@ -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[] {

View file

@ -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);

View file

@ -1,4 +1,5 @@
import { get, set, unset } from 'lodash';
import prettyBytes from 'pretty-bytes';
import { BINARY_ENCODING, IExecuteFunctions } from 'n8n-core';
@ -12,6 +13,7 @@ import {
INodeTypeDescription,
jsonParse,
NodeOperationError,
fileTypeFromMimeType,
} from 'n8n-workflow';
import iconv from 'iconv-lite';
@ -415,20 +417,26 @@ 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 })
.toString(BINARY_ENCODING);
}
convertedValue.fileSize = prettyBytes(value.length);
const convertedValue: IBinaryData = {
data: value as string,
mimeType: (options.mimeType as string) || 'application/json',
};
convertedValue.data = iconv
.encode(value, encoding, { addBOM: options.addBOM as boolean })
.toString(BINARY_ENCODING);
} else {
convertedValue.data = value as unknown as string;
}
if (options.fileName) {
convertedValue.fileName = options.fileName as string;

View file

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

View file

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

View file

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

View file

@ -1,3 +1,5 @@
import { BinaryFileType } from './Interfaces';
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 */
@ -64,3 +66,11 @@ export const sleep = async (ms: number): Promise<void> =>
new Promise((resolve) => {
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
pg: ^8.3.0
pg-promise: ^10.5.8
pretty-bytes: ^5.6.0
promise-ftp: ^1.3.5
redis: ^3.1.1
request: ^2.88.2
@ -796,6 +797,7 @@ importers:
pdf-parse: 1.1.1
pg: 8.8.0
pg-promise: 10.12.0
pretty-bytes: 5.6.0
promise-ftp: 1.3.5_promise-ftp-common@1.1.5
redis: 3.1.2
request: 2.88.2