fix(editor): Improve error messages around pinned data (#9632)

This commit is contained in:
Milorad FIlipović 2024-06-05 13:48:55 +02:00 committed by GitHub
parent 37531cdb7d
commit a8bb53f4e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 51 additions and 15 deletions

View file

@ -144,6 +144,19 @@ describe('Data pinning', () => {
.should('contain', 'Workflow has reached the maximum allowed pinned data size'); .should('contain', 'Workflow has reached the maximum allowed pinned data size');
}); });
it('Should show an error when pin data JSON in invalid', () => {
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
ndv.getters.container().should('be.visible');
ndv.getters.pinDataButton().should('not.exist');
ndv.getters.editPinnedDataButton().should('be.visible');
ndv.actions.setPinnedData('[ { "name": "First item", "code": 2dsa }]')
workflowPage.getters
.errorToast()
.should('contain', 'Unable to save due to invalid JSON');
});
it('Should be able to reference paired items in a node located before pinned data', () => { it('Should be able to reference paired items in a node located before pinned data', () => {
workflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); workflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
workflowPage.actions.addNodeToCanvas(HTTP_REQUEST_NODE_NAME, true, true); workflowPage.actions.addNodeToCanvas(HTTP_REQUEST_NODE_NAME, true, true);

View file

@ -151,14 +151,15 @@ export class NDV extends BasePage {
cy.contains('Expression').invoke('show').click(); cy.contains('Expression').invoke('show').click();
this.getters.inlineExpressionEditorInput().click(); this.getters.inlineExpressionEditorInput().click();
}, },
setPinnedData: (data: object) => { setPinnedData: (data: object | string) => {
const pinnedData = typeof data === 'string' ? data : JSON.stringify(data);
this.getters.editPinnedDataButton().click(); this.getters.editPinnedDataButton().click();
this.getters.pinnedDataEditor().click(); this.getters.pinnedDataEditor().click();
this.getters this.getters
.pinnedDataEditor() .pinnedDataEditor()
.type( .type(
`{selectall}{backspace}${JSON.stringify(data).replace(new RegExp('{', 'g'), '{{}')}`, `{selectall}{backspace}${pinnedData.replace(new RegExp('{', 'g'), '{{}')}`,
{ {
delay: 0, delay: 0,
}, },

View file

@ -1294,9 +1294,15 @@ export default defineComponent({
this.clearAllStickyNotifications(); this.clearAllStickyNotifications();
try { try {
this.pinnedData.setData(clearJsonKey(value) as INodeExecutionData[], 'save-edit'); const clearedValue = clearJsonKey(value) as INodeExecutionData[];
try {
this.pinnedData.setData(clearedValue, 'save-edit');
} catch (error) { } catch (error) {
console.error(error); // setData function already shows toasts on error, so just return here
return;
}
} catch (error) {
this.showError(error, this.$locale.baseText('ndv.pinData.error.syntaxError.title'));
return; return;
} }

View file

@ -8,7 +8,7 @@ import {
MAX_WORKFLOW_SIZE, MAX_WORKFLOW_SIZE,
PIN_DATA_NODE_TYPES_DENYLIST, PIN_DATA_NODE_TYPES_DENYLIST,
} from '@/constants'; } from '@/constants';
import { stringSizeInBytes } from '@/utils/typesUtils'; import { stringSizeInBytes, toMegaBytes } from '@/utils/typesUtils';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import type { INodeUi, IRunDataDisplayMode } from '@/Interface'; import type { INodeUi, IRunDataDisplayMode } from '@/Interface';
import { useExternalHooks } from '@/composables/useExternalHooks'; import { useExternalHooks } from '@/composables/useExternalHooks';
@ -158,19 +158,29 @@ export function usePinnedData(
if (newPinDataSize > MAX_PINNED_DATA_SIZE) { if (newPinDataSize > MAX_PINNED_DATA_SIZE) {
toast.showError( toast.showError(
new Error(i18n.baseText('ndv.pinData.error.tooLarge.description')), new Error(
i18n.baseText('ndv.pinData.error.tooLarge.description', {
interpolate: {
size: toMegaBytes(newPinDataSize),
limit: toMegaBytes(MAX_PINNED_DATA_SIZE),
},
}),
),
i18n.baseText('ndv.pinData.error.tooLarge.title'), i18n.baseText('ndv.pinData.error.tooLarge.title'),
); );
return false; return false;
} }
if ( const workflowSize = stringSizeInBytes(workflowJson) + newPinDataSize;
stringSizeInBytes(workflowJson) + newPinDataSize > const limit = MAX_WORKFLOW_SIZE - MAX_EXPECTED_REQUEST_SIZE;
MAX_WORKFLOW_SIZE - MAX_EXPECTED_REQUEST_SIZE if (workflowSize > limit) {
) {
toast.showError( toast.showError(
new Error(i18n.baseText('ndv.pinData.error.tooLargeWorkflow.description')), new Error(
i18n.baseText('ndv.pinData.error.tooLargeWorkflow.description', {
interpolate: { size: toMegaBytes(workflowSize), limit: toMegaBytes(limit) },
}),
),
i18n.baseText('ndv.pinData.error.tooLargeWorkflow.title'), i18n.baseText('ndv.pinData.error.tooLargeWorkflow.title'),
); );

View file

@ -945,10 +945,11 @@
"ndv.pinData.beforeClosing.title": "Save output changes before closing?", "ndv.pinData.beforeClosing.title": "Save output changes before closing?",
"ndv.pinData.beforeClosing.cancel": "Discard", "ndv.pinData.beforeClosing.cancel": "Discard",
"ndv.pinData.beforeClosing.confirm": "Save", "ndv.pinData.beforeClosing.confirm": "Save",
"ndv.pinData.error.tooLarge.title": "Pinned data too big", "ndv.pinData.error.syntaxError.title": "Unable to save due to invalid JSON",
"ndv.pinData.error.tooLarge.description": "Workflow has reached the maximum allowed pinned data size", "ndv.pinData.error.tooLarge.title": "Unable to pin data due to size limit",
"ndv.pinData.error.tooLargeWorkflow.title": "Pinned data too big", "ndv.pinData.error.tooLarge.description": "Workflow has reached the maximum allowed pinned data size ({size} mb / {limit} mb)",
"ndv.pinData.error.tooLargeWorkflow.description": "Workflow has reached the maximum allowed size", "ndv.pinData.error.tooLargeWorkflow.title": "Unable to pin data due to size limit",
"ndv.pinData.error.tooLargeWorkflow.description": "Workflow has reached the maximum allowed size ({size} mb / {limit} mb)",
"ndv.httpRequest.credentialOnly.docsNotice": "Use the <a target=\"_blank\" href=\"{docsUrl}\">{nodeName} docs</a> to construct your request. We'll take care of the authentication part if you add a {nodeName} credential below.", "ndv.httpRequest.credentialOnly.docsNotice": "Use the <a target=\"_blank\" href=\"{docsUrl}\">{nodeName} docs</a> to construct your request. We'll take care of the authentication part if you add a {nodeName} credential below.",
"noTagsView.readyToOrganizeYourWorkflows": "Ready to organize your workflows?", "noTagsView.readyToOrganizeYourWorkflows": "Ready to organize your workflows?",
"noTagsView.withWorkflowTagsYouReFree": "With workflow tags, you're free to create the perfect tagging system for your flows", "noTagsView.withWorkflowTagsYouReFree": "With workflow tags, you're free to create the perfect tagging system for your flows",

View file

@ -63,6 +63,11 @@ export function stringSizeInBytes(input: string | IDataObject | IDataObject[] |
return new Blob([typeof input === 'string' ? input : JSON.stringify(input)]).size; return new Blob([typeof input === 'string' ? input : JSON.stringify(input)]).size;
} }
export function toMegaBytes(bytes: number, decimalPlaces: number = 2): number {
const megabytes = bytes / 1024 / 1024;
return parseFloat(megabytes.toFixed(decimalPlaces));
}
export function shorten(s: string, limit: number, keep: number) { export function shorten(s: string, limit: number, keep: number) {
if (s.length <= limit) { if (s.length <= limit) {
return s; return s;