mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix(editor): Render HTML in the log view (#17586)
This commit is contained in:
@@ -1 +1,6 @@
|
||||
<svg viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M1.63636 0H8.18182C9.08556 0 9.81818 0.732625 9.81818 1.63636C9.81818 2.5401 9.08556 3.27273 8.18182 3.27273H1.63636C0.732626 3.27273 0 2.5401 0 1.63636C0 0.732625 0.732625 0 1.63636 0ZM1.63636 1.09091C1.33512 1.09091 1.09091 1.33512 1.09091 1.63636C1.09091 1.93761 1.33512 2.18182 1.63636 2.18182H8.18182C8.48306 2.18182 8.72727 1.93761 8.72727 1.63636C8.72727 1.33512 8.48306 1.09091 8.18182 1.09091H1.63636Z M7.09091 4.36353H11.4545C12.3583 4.36353 13.0909 5.09615 13.0909 5.99989C13.0909 6.90363 12.3583 7.63625 11.4545 7.63625H7.09091C6.18717 7.63625 5.45454 6.90363 5.45454 5.99989C5.45454 5.09615 6.18717 4.36353 7.09091 4.36353ZM7.09091 5.45443C6.78966 5.45443 6.54545 5.69864 6.54545 5.99989C6.54545 6.30114 6.78966 6.54534 7.09091 6.54534H11.4545C11.7558 6.54534 12 6.30114 12 5.99989C12 5.69864 11.7558 5.45443 11.4545 5.45443H7.09091Z M7.09091 8.72729H11.4545C12.3583 8.72729 13.0909 9.45992 13.0909 10.3637C13.0909 11.2674 12.3583 12 11.4545 12H7.09091C6.18717 12 5.45454 11.2674 5.45454 10.3637C5.45454 9.45992 6.18717 8.72729 7.09091 8.72729ZM7.09091 9.8182C6.78966 9.8182 6.54545 10.0624 6.54545 10.3637C6.54545 10.6649 6.78966 10.9091 7.09091 10.9091H11.4545C11.7558 10.9091 12 10.6649 12 10.3637C12 10.0624 11.7558 9.8182 11.4545 9.8182H7.09091Z" /></svg>
|
||||
<svg viewBox="0 -1 14 14" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M1.63636 0H8.18182C9.08556 0 9.81818 0.732625 9.81818 1.63636C9.81818 2.5401 9.08556 3.27273 8.18182 3.27273H1.63636C0.732626 3.27273 0 2.5401 0 1.63636C0 0.732625 0.732625 0 1.63636 0ZM1.63636 1.09091C1.33512 1.09091 1.09091 1.33512 1.09091 1.63636C1.09091 1.93761 1.33512 2.18182 1.63636 2.18182H8.18182C8.48306 2.18182 8.72727 1.93761 8.72727 1.63636C8.72727 1.33512 8.48306 1.09091 8.18182 1.09091H1.63636Z M7.09091 4.36353H11.4545C12.3583 4.36353 13.0909 5.09615 13.0909 5.99989C13.0909 6.90363 12.3583 7.63625 11.4545 7.63625H7.09091C6.18717 7.63625 5.45454 6.90363 5.45454 5.99989C5.45454 5.09615 6.18717 4.36353 7.09091 4.36353ZM7.09091 5.45443C6.78966 5.45443 6.54545 5.69864 6.54545 5.99989C6.54545 6.30114 6.78966 6.54534 7.09091 6.54534H11.4545C11.7558 6.54534 12 6.30114 12 5.99989C12 5.69864 11.7558 5.45443 11.4545 5.45443H7.09091Z M7.09091 8.72729H11.4545C12.3583 8.72729 13.0909 9.45992 13.0909 10.3637C13.0909 11.2674 12.3583 12 11.4545 12H7.09091C6.18717 12 5.45454 11.2674 5.45454 10.3637C5.45454 9.45992 6.18717 8.72729 7.09091 8.72729ZM7.09091 9.8182C6.78966 9.8182 6.54545 10.0624 6.54545 10.3637C6.54545 10.6649 6.78966 10.9091 7.09091 10.9091H11.4545C11.7558 10.9091 12 10.6649 12 10.3637C12 10.0624 11.7558 9.8182 11.4545 9.8182H7.09091Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -3,7 +3,6 @@ import BoltFilled from './custom/bolt-filled.svg';
|
||||
import Continue from './custom/Continue.svg';
|
||||
import EmptyOutput from './custom/EmptyOutput.svg';
|
||||
import GripLinesVertical from './custom/grip-lines-vertical.svg';
|
||||
import Json from './custom/json.svg';
|
||||
import PopOut from './custom/pop-out.svg';
|
||||
import Retry from './custom/Retry.svg';
|
||||
import RunOnce from './custom/RunOnce.svg';
|
||||
@@ -36,6 +35,7 @@ import IconLucideBell from '~icons/lucide/bell';
|
||||
import IconLucideBook from '~icons/lucide/book';
|
||||
import IconLucideBot from '~icons/lucide/bot';
|
||||
import IconLucideBox from '~icons/lucide/box';
|
||||
import IconLucideBraces from '~icons/lucide/braces';
|
||||
import IconLucideBrain from '~icons/lucide/brain';
|
||||
import IconLucideBug from '~icons/lucide/bug';
|
||||
import IconLucideCalculator from '~icons/lucide/calculator';
|
||||
@@ -208,7 +208,7 @@ export const deprecatedIconSet = {
|
||||
'status-warning': StatusWarning,
|
||||
'vector-square': VectorSquare,
|
||||
schema: Schema,
|
||||
json: Json,
|
||||
json: IconLucideBraces,
|
||||
binary: Binary,
|
||||
text: Text,
|
||||
toolbox: Toolbox,
|
||||
@@ -415,7 +415,7 @@ export const updatedIconSet = {
|
||||
'retry-on-fail': Retry,
|
||||
'execute-once': RunOnce,
|
||||
schema: Schema,
|
||||
json: Json,
|
||||
json: IconLucideBraces,
|
||||
binary: Binary,
|
||||
text: Text,
|
||||
toolbox: Toolbox,
|
||||
|
||||
@@ -130,14 +130,18 @@ export const defaultNodeDescriptions = Object.values(defaultNodeTypes).map(
|
||||
({ type }) => type.description,
|
||||
) as INodeTypeDescription[];
|
||||
|
||||
const nodeTypes = mock<INodeTypes>({
|
||||
getByName(nodeType) {
|
||||
return defaultNodeTypes[nodeType].type;
|
||||
},
|
||||
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
||||
return NodeHelpers.getVersionedNodeType(defaultNodeTypes[nodeType].type, version);
|
||||
},
|
||||
});
|
||||
export function createMockNodeTypes(data: INodeTypeData) {
|
||||
return mock<INodeTypes>({
|
||||
getByName(nodeType) {
|
||||
return data[nodeType].type;
|
||||
},
|
||||
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
||||
return NodeHelpers.getVersionedNodeType(data[nodeType].type, version);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const nodeTypes = createMockNodeTypes(defaultNodeTypes);
|
||||
|
||||
export function createTestWorkflowObject({
|
||||
id = uuid(),
|
||||
@@ -148,6 +152,7 @@ export function createTestWorkflowObject({
|
||||
staticData = {},
|
||||
settings = {},
|
||||
pinData = {},
|
||||
...rest
|
||||
}: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
@@ -157,6 +162,7 @@ export function createTestWorkflowObject({
|
||||
staticData?: IDataObject;
|
||||
settings?: IWorkflowSettings;
|
||||
pinData?: IPinData;
|
||||
nodeTypes?: INodeTypes;
|
||||
} = {}) {
|
||||
return new Workflow({
|
||||
id,
|
||||
@@ -167,7 +173,7 @@ export function createTestWorkflowObject({
|
||||
staticData,
|
||||
settings,
|
||||
pinData,
|
||||
nodeTypes,
|
||||
nodeTypes: rest.nodeTypes ?? nodeTypes,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -112,3 +112,8 @@ Object.defineProperty(HTMLCanvasElement.prototype, 'getContext', {
|
||||
writable: true,
|
||||
value: vi.fn(),
|
||||
});
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'scrollTo', {
|
||||
writable: true,
|
||||
value: vi.fn(),
|
||||
});
|
||||
|
||||
@@ -721,7 +721,6 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
if (props.paneType === 'output') {
|
||||
setDisplayMode();
|
||||
activatePane();
|
||||
}
|
||||
|
||||
@@ -1231,6 +1230,10 @@ function init() {
|
||||
if (isNDVV2.value) {
|
||||
pageSize.value = RUN_DATA_DEFAULT_PAGE_SIZE;
|
||||
}
|
||||
|
||||
if (props.paneType === 'output') {
|
||||
setDisplayMode();
|
||||
}
|
||||
}
|
||||
|
||||
function closeBinaryDataDisplay() {
|
||||
@@ -1337,14 +1340,14 @@ function enableNode() {
|
||||
}
|
||||
}
|
||||
|
||||
const shouldDisplayHtml = computed(
|
||||
() =>
|
||||
node.value?.type === HTML_NODE_TYPE &&
|
||||
node.value.parameters.operation === 'generateHtmlTemplate',
|
||||
);
|
||||
|
||||
function setDisplayMode() {
|
||||
if (!activeNode.value) return;
|
||||
|
||||
const shouldDisplayHtml =
|
||||
activeNode.value.type === HTML_NODE_TYPE &&
|
||||
activeNode.value.parameters.operation === 'generateHtmlTemplate';
|
||||
|
||||
if (shouldDisplayHtml) {
|
||||
if (shouldDisplayHtml.value) {
|
||||
emit('displayModeChange', 'html');
|
||||
}
|
||||
}
|
||||
@@ -1461,10 +1464,7 @@ defineExpose({ enterEditMode });
|
||||
:value="displayMode"
|
||||
:has-binary-data="binaryData.length > 0"
|
||||
:pane-type="paneType"
|
||||
:node-generates-html="
|
||||
activeNode?.type === HTML_NODE_TYPE &&
|
||||
activeNode.parameters.operation === 'generateHtmlTemplate'
|
||||
"
|
||||
:node-generates-html="shouldDisplayHtml"
|
||||
:has-renderable-data="hasParsedAiContent"
|
||||
@change="onDisplayModeChange"
|
||||
/>
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { fireEvent, within } from '@testing-library/vue';
|
||||
import { fireEvent, waitFor, within } from '@testing-library/vue';
|
||||
import { renderComponent } from '@/__tests__/render';
|
||||
import LogDetailsPanel from './LogDetailsPanel.vue';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import { createTestingPinia, type TestingPinia } from '@pinia/testing';
|
||||
import { h } from 'vue';
|
||||
import {
|
||||
createMockNodeTypes,
|
||||
createTestNode,
|
||||
createTestTaskData,
|
||||
createTestWorkflow,
|
||||
createTestWorkflowObject,
|
||||
defaultNodeTypes,
|
||||
mockLoadedNodeType,
|
||||
} from '@/__tests__/mocks';
|
||||
import { LOG_DETAILS_PANEL_STATE } from '@/features/logs/logs.constants';
|
||||
import type { LogEntry } from '../logs.types';
|
||||
import { createTestLogEntry } from '../__test__/mocks';
|
||||
import { NodeConnectionTypes } from 'n8n-workflow';
|
||||
import { HTML_NODE_TYPE } from '@/constants';
|
||||
|
||||
describe('LogDetailsPanel', () => {
|
||||
let pinia: TestingPinia;
|
||||
@@ -182,4 +186,47 @@ describe('LogDetailsPanel', () => {
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render output data in HTML mode for HTML node', async () => {
|
||||
const nodeA = createTestNode({ name: 'A' });
|
||||
const nodeB = createTestNode({
|
||||
name: 'B',
|
||||
type: HTML_NODE_TYPE,
|
||||
});
|
||||
const runDataA = createTestTaskData({ data: { [NodeConnectionTypes.Main]: [[{ json: {} }]] } });
|
||||
const runDataB = createTestTaskData({
|
||||
data: { [NodeConnectionTypes.Main]: [[{ json: { html: '<h1>Hi!</h1>' } }]] },
|
||||
source: [{ previousNode: 'A' }],
|
||||
});
|
||||
const workflow = createTestWorkflowObject({
|
||||
nodes: [nodeA, nodeB],
|
||||
nodeTypes: createMockNodeTypes({
|
||||
...defaultNodeTypes,
|
||||
[HTML_NODE_TYPE]: mockLoadedNodeType(HTML_NODE_TYPE),
|
||||
}),
|
||||
});
|
||||
const execution = { resultData: { runData: { A: [runDataA], B: [runDataB] } } };
|
||||
const logA = createLogEntry({ node: nodeA, runData: runDataA, workflow, execution });
|
||||
const logB = createLogEntry({ node: nodeB, runData: runDataB, workflow, execution });
|
||||
|
||||
// HACK: Setting parameters after creating workflow because validation removes parameters that are not define in node types.
|
||||
nodeB.parameters = { operation: 'generateHtmlTemplate' };
|
||||
|
||||
const props = {
|
||||
isOpen: true,
|
||||
panels: LOG_DETAILS_PANEL_STATE.BOTH,
|
||||
collapsingInputTableColumnName: null,
|
||||
collapsingOutputTableColumnName: null,
|
||||
};
|
||||
|
||||
const rendered = render({ ...props, logEntry: logB });
|
||||
|
||||
await waitFor(() => expect(rendered.container.querySelectorAll('iframe')).toHaveLength(1));
|
||||
await rendered.rerender({ ...props, logEntry: logA });
|
||||
await waitFor(() => expect(rendered.container.querySelectorAll('iframe')).toHaveLength(0));
|
||||
|
||||
// Re-selecting node B should render HTML again
|
||||
await rendered.rerender({ ...props, logEntry: logB });
|
||||
await waitFor(() => expect(rendered.container.querySelectorAll('iframe')).toHaveLength(1));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user