1
1
mirror of https://github.com/n8n-io/n8n.git synced 2024-09-17 16:08:12 +03:00

feat(editor): Make PDF and Audio binary-data viewable in the UI (#7367)

fixes #7361
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-10-09 17:43:57 +02:00 committed by GitHub
parent 732b15a1fa
commit 8187be1b7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 150 additions and 58 deletions

View File

@ -7,6 +7,10 @@
<source :src="embedSource" :type="binaryData.mimeType" />
{{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
</video>
<audio v-if="binaryData.fileType === 'audio'" controls autoplay>
<source :src="embedSource" :type="binaryData.mimeType" />
{{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
</audio>
<vue-json-pretty
v-else-if="binaryData.fileType === 'json'"
:data="jsonData"
@ -92,7 +96,8 @@ export default defineComponent({
max-width: calc(100% - 1em);
}
&.other {
&.other,
&.pdf {
height: calc(100% - 1em);
width: calc(100% - 1em);
}

View File

@ -382,7 +382,7 @@
v-for="(binaryData, key) in binaryDataEntry"
:key="index + '_' + key"
>
<div>
<div :data-test-id="'ndv-binary-data_' + index">
<div :class="$style.binaryHeader">
{{ key }}
</div>
@ -432,7 +432,7 @@
v-if="isViewable(index, key)"
size="small"
:label="$locale.baseText('runData.showBinaryData')"
class="binary-data-show-data-button"
data-test-id="ndv-view-binary-data"
@click="displayBinaryData(index, key)"
/>
<n8n-button
@ -440,7 +440,7 @@
size="small"
type="secondary"
:label="$locale.baseText('runData.downloadBinaryData')"
class="binary-data-show-data-button"
data-test-id="ndv-download-binary-data"
@click="downloadBinaryData(index, key)"
/>
</div>
@ -1320,7 +1320,7 @@ export default defineComponent({
},
isViewable(index: number, key: string): boolean {
const { fileType } = this.binaryData[index][key];
return !!fileType && ['image', 'video', 'text', 'json'].includes(fileType);
return !!fileType && ['image', 'audio', 'video', 'text', 'json', 'pdf'].includes(fileType);
},
isDownloadable(index: number, key: string): boolean {
const { mimeType, fileName } = this.binaryData[index][key];

View File

@ -6,30 +6,102 @@ import RunData from '@/components/RunData.vue';
import { STORES, VIEWS } from '@/constants';
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
import { createComponentRenderer } from '@/__tests__/render';
const renderComponent = createComponentRenderer(RunData, {
props: {
nodeUi: {
name: 'Test Node',
},
},
data() {
return {
canPinData: true,
};
},
global: {
mocks: {
$route: {
name: VIEWS.WORKFLOW,
},
},
},
});
import type { IRunDataDisplayMode } from '@/Interface';
describe('RunData', () => {
it('should render data correctly even when "item.json" has another "json" key', async () => {
const { html, getByText, getAllByTestId, getByTestId } = renderComponent({
const { getByText, getAllByTestId, getByTestId } = render(
[
{
json: {
id: 1,
name: 'Test 1',
json: {
data: 'Json data 1',
},
},
},
{
json: {
id: 2,
name: 'Test 2',
json: {
data: 'Json data 2',
},
},
},
],
'schema',
);
await userEvent.click(getByTestId('ndv-pin-data'));
await waitFor(() => getAllByTestId('run-data-schema-item'), { timeout: 1000 });
expect(getByText('Test 1')).toBeInTheDocument();
expect(getByText('Json data 1')).toBeInTheDocument();
});
it('should render view and download buttons for PDFs', async () => {
const { getByTestId } = render(
[
{
json: {},
binary: {
data: {
fileName: 'test.pdf',
fileType: 'pdf',
mimeType: 'application/pdf',
},
},
},
],
'binary',
);
expect(getByTestId('ndv-view-binary-data')).toBeInTheDocument();
expect(getByTestId('ndv-download-binary-data')).toBeInTheDocument();
expect(getByTestId('ndv-binary-data_0')).toBeInTheDocument();
});
it('should not render a view button for unknown content-type', async () => {
const { getByTestId, queryByTestId } = render(
[
{
json: {},
binary: {
data: {
fileName: 'test.xyz',
mimeType: 'application/octet-stream',
},
},
},
],
'binary',
);
expect(queryByTestId('ndv-view-binary-data')).not.toBeInTheDocument();
expect(getByTestId('ndv-download-binary-data')).toBeInTheDocument();
expect(getByTestId('ndv-binary-data_0')).toBeInTheDocument();
});
const render = (outputData: unknown[], displayMode: IRunDataDisplayMode) =>
createComponentRenderer(RunData, {
props: {
nodeUi: {
name: 'Test Node',
},
},
data() {
return {
canPinData: true,
showData: true,
};
},
global: {
mocks: {
$route: {
name: VIEWS.WORKFLOW,
},
},
},
})({
props: {
nodeUi: {
id: '1',
@ -49,7 +121,7 @@ describe('RunData', () => {
},
[STORES.NDV]: {
output: {
displayMode: 'schema',
displayMode,
},
activeNodeName: 'Test Node',
},
@ -89,28 +161,7 @@ describe('RunData', () => {
startTime: new Date().getTime(),
executionTime: new Date().getTime(),
data: {
main: [
[
{
json: {
id: 1,
name: 'Test 1',
json: {
data: 'Json data 1',
},
},
},
{
json: {
id: 2,
name: 'Test 2',
json: {
data: 'Json data 2',
},
},
},
],
],
main: [outputData],
},
source: [null],
},
@ -123,10 +174,4 @@ describe('RunData', () => {
},
}),
});
await userEvent.click(getByTestId('ndv-pin-data'));
await waitFor(() => getAllByTestId('run-data-schema-item'), { timeout: 1000 });
expect(getByText('Test 1')).toBeInTheDocument();
expect(getByText('Json data 1')).toBeInTheDocument();
});
});

View File

@ -35,6 +35,7 @@
"fileExtension": "pdf",
"fileName": "sample-encrypted.pdf",
"fileSize": "18.9 kB",
"fileType": "pdf",
"mimeType": "application/pdf"
}
},

View File

@ -32,6 +32,7 @@
"fileExtension": "pdf",
"fileName": "sample.pdf",
"fileSize": "17.8 kB",
"fileType": "pdf",
"mimeType": "application/pdf"
}
},

View File

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

View File

@ -113,8 +113,10 @@ export const sleep = async (ms: number): Promise<void> =>
export function fileTypeFromMimeType(mimeType: string): BinaryFileType | undefined {
if (mimeType.startsWith('application/json')) return 'json';
if (mimeType.startsWith('image/')) return 'image';
if (mimeType.startsWith('audio/')) return 'audio';
if (mimeType.startsWith('video/')) return 'video';
if (mimeType.startsWith('text/')) return 'text';
if (mimeType.startsWith('text/') || mimeType.startsWith('application/javascript')) return 'text';
if (mimeType.startsWith('application/pdf')) return 'pdf';
return;
}

View File

@ -1,4 +1,4 @@
import { jsonParse, jsonStringify, deepCopy, isObjectEmpty } from '@/utils';
import { jsonParse, jsonStringify, deepCopy, isObjectEmpty, fileTypeFromMimeType } from '@/utils';
describe('isObjectEmpty', () => {
it('should handle null and undefined', () => {
@ -190,3 +190,41 @@ describe('deepCopy', () => {
expect(copy.deep.arr.slice(-1)[0]).not.toBe(object);
});
});
describe('fileTypeFromMimeType', () => {
it('should recognize json', () => {
expect(fileTypeFromMimeType('application/json')).toEqual('json');
});
it('should recognize image', () => {
expect(fileTypeFromMimeType('image/jpeg')).toEqual('image');
expect(fileTypeFromMimeType('image/png')).toEqual('image');
expect(fileTypeFromMimeType('image/avif')).toEqual('image');
expect(fileTypeFromMimeType('image/webp')).toEqual('image');
});
it('should recognize audio', () => {
expect(fileTypeFromMimeType('audio/wav')).toEqual('audio');
expect(fileTypeFromMimeType('audio/webm')).toEqual('audio');
expect(fileTypeFromMimeType('audio/ogg')).toEqual('audio');
expect(fileTypeFromMimeType('audio/mp3')).toEqual('audio');
});
it('should recognize video', () => {
expect(fileTypeFromMimeType('video/mp4')).toEqual('video');
expect(fileTypeFromMimeType('video/webm')).toEqual('video');
expect(fileTypeFromMimeType('video/ogg')).toEqual('video');
});
it('should recognize text', () => {
expect(fileTypeFromMimeType('text/plain')).toEqual('text');
expect(fileTypeFromMimeType('text/css')).toEqual('text');
expect(fileTypeFromMimeType('text/html')).toEqual('text');
expect(fileTypeFromMimeType('text/javascript')).toEqual('text');
expect(fileTypeFromMimeType('application/javascript')).toEqual('text');
});
it('should recognize pdf', () => {
expect(fileTypeFromMimeType('application/pdf')).toEqual('pdf');
});
});