mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-26 05:04:05 -08:00
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:
parent
f4481e24e8
commit
3b969d2cd1
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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[] {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue