diff --git a/cypress/e2e/13-pinning.cy.ts b/cypress/e2e/13-pinning.cy.ts index a9ccc7881857e..a27b830ce6a57 100644 --- a/cypress/e2e/13-pinning.cy.ts +++ b/cypress/e2e/13-pinning.cy.ts @@ -144,6 +144,19 @@ describe('Data pinning', () => { .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', () => { workflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); workflowPage.actions.addNodeToCanvas(HTTP_REQUEST_NODE_NAME, true, true); diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts index 6adca4befde96..0cb7609fb2305 100644 --- a/cypress/pages/ndv.ts +++ b/cypress/pages/ndv.ts @@ -151,14 +151,15 @@ export class NDV extends BasePage { cy.contains('Expression').invoke('show').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.pinnedDataEditor().click(); this.getters .pinnedDataEditor() .type( - `{selectall}{backspace}${JSON.stringify(data).replace(new RegExp('{', 'g'), '{{}')}`, + `{selectall}{backspace}${pinnedData.replace(new RegExp('{', 'g'), '{{}')}`, { delay: 0, }, diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 5937425a09ce4..7d3ac28d9b379 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -1294,9 +1294,15 @@ export default defineComponent({ this.clearAllStickyNotifications(); 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) { + // setData function already shows toasts on error, so just return here + return; + } } catch (error) { - console.error(error); + this.showError(error, this.$locale.baseText('ndv.pinData.error.syntaxError.title')); return; } diff --git a/packages/editor-ui/src/composables/usePinnedData.ts b/packages/editor-ui/src/composables/usePinnedData.ts index cd05abf2d6809..716c3e8d9460a 100644 --- a/packages/editor-ui/src/composables/usePinnedData.ts +++ b/packages/editor-ui/src/composables/usePinnedData.ts @@ -8,7 +8,7 @@ import { MAX_WORKFLOW_SIZE, PIN_DATA_NODE_TYPES_DENYLIST, } from '@/constants'; -import { stringSizeInBytes } from '@/utils/typesUtils'; +import { stringSizeInBytes, toMegaBytes } from '@/utils/typesUtils'; import { useWorkflowsStore } from '@/stores/workflows.store'; import type { INodeUi, IRunDataDisplayMode } from '@/Interface'; import { useExternalHooks } from '@/composables/useExternalHooks'; @@ -158,19 +158,29 @@ export function usePinnedData( if (newPinDataSize > MAX_PINNED_DATA_SIZE) { 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'), ); return false; } - if ( - stringSizeInBytes(workflowJson) + newPinDataSize > - MAX_WORKFLOW_SIZE - MAX_EXPECTED_REQUEST_SIZE - ) { + const workflowSize = stringSizeInBytes(workflowJson) + newPinDataSize; + const limit = MAX_WORKFLOW_SIZE - MAX_EXPECTED_REQUEST_SIZE; + if (workflowSize > limit) { 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'), ); diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 27cd1c76b4145..1495915890e9e 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -945,10 +945,11 @@ "ndv.pinData.beforeClosing.title": "Save output changes before closing?", "ndv.pinData.beforeClosing.cancel": "Discard", "ndv.pinData.beforeClosing.confirm": "Save", - "ndv.pinData.error.tooLarge.title": "Pinned data too big", - "ndv.pinData.error.tooLarge.description": "Workflow has reached the maximum allowed pinned data size", - "ndv.pinData.error.tooLargeWorkflow.title": "Pinned data too big", - "ndv.pinData.error.tooLargeWorkflow.description": "Workflow has reached the maximum allowed size", + "ndv.pinData.error.syntaxError.title": "Unable to save due to invalid JSON", + "ndv.pinData.error.tooLarge.title": "Unable to pin data due to size limit", + "ndv.pinData.error.tooLarge.description": "Workflow has reached the maximum allowed pinned data size ({size} mb / {limit} mb)", + "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 {nodeName} docs 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.withWorkflowTagsYouReFree": "With workflow tags, you're free to create the perfect tagging system for your flows", diff --git a/packages/editor-ui/src/utils/typesUtils.ts b/packages/editor-ui/src/utils/typesUtils.ts index a3c3d8469222e..c89d79ab09652 100644 --- a/packages/editor-ui/src/utils/typesUtils.ts +++ b/packages/editor-ui/src/utils/typesUtils.ts @@ -63,6 +63,11 @@ export function stringSizeInBytes(input: string | IDataObject | IDataObject[] | 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) { if (s.length <= limit) { return s;