feat: Replace all Vue.set usages with direct assignment and spread operator (no-changelog) (#6280)

* refactor: replace all Vue.set usages with direct assignment and spread operator

* chore: fix linting issue

* fix: fix updateNodeAtIndex function

* fix: various post-refactoring fixes

* fix: refactor recently added Vue.set directive
This commit is contained in:
Alex Grozav 2023-06-15 15:30:05 +03:00 committed by GitHub
parent c2afed4ca1
commit 596cf07e42
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 620 additions and 307 deletions

View file

@ -29,7 +29,6 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.visit();
});
it('should add switch node and test connections', () => {
WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, true);
@ -114,7 +113,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.zoomToFit();
cy.get('.plus-draggable-endpoint').filter(':visible').should('not.have.class', 'ep-success');
cy.get('.jtk-connector.success').should('have.length', 3);
cy.get('.jtk-connector.success').should('have.length', 4);
cy.get('.jtk-connector').should('have.length', 4);
});

View file

@ -16,12 +16,10 @@ describe('Data mapping', () => {
beforeEach(() => {
workflowPage.actions.visit();
cy.window().then(
(win) => {
// @ts-ignore
win.preventNodeViewBeforeUnload = true;
},
);
cy.window().then((win) => {
// @ts-ignore
win.preventNodeViewBeforeUnload = true;
});
});
it('maps expressions from table header', () => {
@ -303,19 +301,28 @@ describe('Data mapping', () => {
ndv.getters.parameterInput('keepOnlySet').find('input[type="checkbox"]').should('exist');
ndv.getters.parameterInput('keepOnlySet').find('input[type="text"]').should('not.exist');
ndv.getters.inputDataContainer().should('exist').find('span').contains('count').realMouseDown().realMouseMove(100, 100);
ndv.getters
.inputDataContainer()
.should('exist')
.find('span')
.contains('count')
.realMouseDown()
.realMouseMove(100, 100);
cy.wait(50);
ndv.getters.parameterInput('keepOnlySet').find('input[type="checkbox"]').should('not.exist');
ndv.getters.parameterInput('keepOnlySet').find('input[type="text"]')
ndv.getters
.parameterInput('keepOnlySet')
.find('input[type="text"]')
.should('exist')
.invoke('css', 'border')
.then((border) => expect(border).to.include('dashed rgb(90, 76, 194)'));
ndv.getters.parameterInput('value').find('input[type="text"]')
.should('exist')
.invoke('css', 'border')
.then((border) => expect(border).to.include('dashed rgb(90, 76, 194)'));
ndv.getters
.parameterInput('value')
.find('input[type="text"]')
.should('exist')
.invoke('css', 'border')
.then((border) => expect(border).to.include('dashed rgb(90, 76, 194)'));
});
});

View file

@ -13,14 +13,17 @@ export class NDV extends BasePage {
outputPanel: () => cy.getByTestId('output-panel'),
executingLoader: () => cy.getByTestId('ndv-executing'),
inputDataContainer: () => this.getters.inputPanel().findChildByTestId('ndv-data-container'),
inputDisplayMode: () => this.getters.inputPanel().findChildByTestId('ndv-run-data-display-mode').first(),
inputDisplayMode: () =>
this.getters.inputPanel().findChildByTestId('ndv-run-data-display-mode').first(),
outputDataContainer: () => this.getters.outputPanel().findChildByTestId('ndv-data-container'),
outputDisplayMode: () => this.getters.outputPanel().findChildByTestId('ndv-run-data-display-mode').first(),
outputDisplayMode: () =>
this.getters.outputPanel().findChildByTestId('ndv-run-data-display-mode').first(),
pinDataButton: () => cy.getByTestId('ndv-pin-data'),
editPinnedDataButton: () => cy.getByTestId('ndv-edit-pinned-data'),
pinnedDataEditor: () => this.getters.outputPanel().find('.cm-editor .cm-scroller'),
runDataPaneHeader: () => cy.getByTestId('run-data-pane-header'),
savePinnedDataButton: () => this.getters.runDataPaneHeader().find('button').filter(':visible').contains('Save'),
savePinnedDataButton: () =>
this.getters.runDataPaneHeader().find('button').filter(':visible').contains('Save'),
outputTableRows: () => this.getters.outputDataContainer().find('table tr'),
outputTableHeaders: () => this.getters.outputDataContainer().find('table thead th'),
outputTableRow: (row: number) => this.getters.outputTableRows().eq(row),
@ -52,10 +55,13 @@ export class NDV extends BasePage {
outputBranches: () => this.getters.outputPanel().findChildByTestId('branches'),
inputBranches: () => this.getters.inputPanel().findChildByTestId('branches'),
resourceLocator: (paramName: string) => cy.getByTestId(`resource-locator-${paramName}`),
resourceLocatorInput: (paramName: string) => this.getters.resourceLocator(paramName).find('[data-test-id="rlc-input-container"]'),
resourceLocatorDropdown: (paramName: string) => this.getters.resourceLocator(paramName).find('[data-test-id="resource-locator-dropdown"]'),
resourceLocatorInput: (paramName: string) =>
this.getters.resourceLocator(paramName).find('[data-test-id="rlc-input-container"]'),
resourceLocatorDropdown: (paramName: string) =>
this.getters.resourceLocator(paramName).find('[data-test-id="resource-locator-dropdown"]'),
resourceLocatorErrorMessage: () => cy.getByTestId('rlc-error-container'),
resourceLocatorModeSelector: (paramName: string) => this.getters.resourceLocator(paramName).find('[data-test-id="rlc-mode-selector"]'),
resourceLocatorModeSelector: (paramName: string) =>
this.getters.resourceLocator(paramName).find('[data-test-id="rlc-mode-selector"]'),
};
actions = {
@ -82,7 +88,9 @@ export class NDV extends BasePage {
this.getters.editPinnedDataButton().click();
this.getters.pinnedDataEditor().click();
this.getters.pinnedDataEditor().type(`{selectall}{backspace}${JSON.stringify(data).replace(new RegExp('{', 'g'), '{{}')}`);
this.getters
.pinnedDataEditor()
.type(`{selectall}{backspace}${JSON.stringify(data).replace(new RegExp('{', 'g'), '{{}')}`);
this.actions.savePinnedData();
},
@ -131,15 +139,11 @@ export class NDV extends BasePage {
},
changeInputRunSelector: (runName: string) => {
this.getters.inputRunSelector().click();
cy.get('.el-select-dropdown:visible .el-select-dropdown__item')
.contains(runName)
.click();
cy.get('.el-select-dropdown:visible .el-select-dropdown__item').contains(runName).click();
},
changeOutputRunSelector: (runName: string) => {
this.getters.outputRunSelector().click();
cy.get('.el-select-dropdown:visible .el-select-dropdown__item')
.contains(runName)
.click();
cy.get('.el-select-dropdown:visible .el-select-dropdown__item').contains(runName).click();
},
toggleOutputRunLinking: () => {
this.getters.outputRunSelector().find('button').click();
@ -159,7 +163,10 @@ export class NDV extends BasePage {
this.getters.resourceLocatorInput(paramName).type(value);
},
validateExpressionPreview: (paramName: string, value: string) => {
this.getters.parameterExpressionPreview(paramName).find('span').should('include.html', asEncodedHTML(value));
this.getters
.parameterExpressionPreview(paramName)
.find('span')
.should('include.html', asEncodedHTML(value));
},
};
}
@ -172,4 +179,3 @@ function asEncodedHTML(str: string): string {
.replace(/"/g, '"')
.replace(/ /g, ' ');
}

View file

@ -975,13 +975,10 @@ export interface ITagsState {
fetchedUsageCount: boolean;
}
export type Modals =
| {
[key: string]: ModalState;
}
| {
[CREDENTIAL_EDIT_MODAL_KEY]: NewCredentialsModal;
};
export type Modals = {
[CREDENTIAL_EDIT_MODAL_KEY]: NewCredentialsModal;
[key: string]: ModalState;
};
export type ModalState = {
open: boolean;

View file

@ -11,7 +11,6 @@ import {
} from '@/utils';
import type { INodeProperties, INodeTypeDescription, NodeParameterValue } from 'n8n-workflow';
import { computed, onMounted, ref } from 'vue';
import Vue from 'vue';
export interface Props {
credentialType: Object;
@ -27,7 +26,7 @@ const ndvStore = useNDVStore();
const props = defineProps<Props>();
const selected = ref('');
const authRelatedFieldsValues = ref({} as { [key: string]: NodeParameterValue });
const authRelatedFieldsValues = ref<{ [key: string]: NodeParameterValue }>({});
onMounted(() => {
if (activeNodeType.value?.credentials) {
@ -43,7 +42,10 @@ onMounted(() => {
// Populate default values of related fields
authRelatedFields.value.forEach((field) => {
Vue.set(authRelatedFieldsValues.value, field.name, field.default);
authRelatedFieldsValues.value = {
...authRelatedFieldsValues.value,
[field.name]: field.default as NodeParameterValue,
};
});
});
@ -102,7 +104,10 @@ function onAuthTypeChange(newType: string): void {
}
function valueChanged(data: IUpdateInformation): void {
Vue.set(authRelatedFieldsValues.value, data.name, data.value);
authRelatedFieldsValues.value = {
...authRelatedFieldsValues.value,
[data.name]: data.value as NodeParameterValue,
};
}
defineExpose({

View file

@ -264,7 +264,7 @@ export default defineComponent({
);
},
credentialTypeName(): string {
return (this.credentialType as ICredentialType).name;
return (this.credentialType as ICredentialType)?.name;
},
credentialOwnerName(): string {
return this.credentialsStore.getCredentialOwnerNameById(`${this.credentialId}`);

View file

@ -109,7 +109,6 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
@ -234,12 +233,15 @@ export default defineComponent({
});
if (this.currentUser) {
Vue.set(this.credentialData, 'ownedBy', {
id: this.currentUser.id,
firstName: this.currentUser.firstName,
lastName: this.currentUser.lastName,
email: this.currentUser.email,
});
this.credentialData = {
...this.credentialData,
ownedBy: {
id: this.currentUser.id,
firstName: this.currentUser.firstName,
lastName: this.currentUser.lastName,
email: this.currentUser.email,
},
};
}
} else {
await this.loadCurrentCredential();
@ -251,7 +253,10 @@ export default defineComponent({
!this.credentialData.hasOwnProperty(property.name) &&
!this.credentialType.__overwrittenProperties?.includes(property.name)
) {
Vue.set(this.credentialData, property.name, property.default as CredentialInformation);
this.credentialData = {
...this.credentialData,
[property.name]: property.default as CredentialInformation,
};
}
}
}
@ -594,12 +599,18 @@ export default defineComponent({
);
}
this.credentialData = currentCredentials.data || {};
this.credentialData = (currentCredentials.data as ICredentialDataDecryptedObject) || {};
if (currentCredentials.sharedWith) {
Vue.set(this.credentialData, 'sharedWith', currentCredentials.sharedWith);
this.credentialData = {
...this.credentialData,
sharedWith: currentCredentials.sharedWith as IDataObject[],
};
}
if (currentCredentials.ownedBy) {
Vue.set(this.credentialData, 'ownedBy', currentCredentials.ownedBy);
this.credentialData = {
...this.credentialData,
ownedBy: currentCredentials.ownedBy as IDataObject[],
};
}
this.credentialName = currentCredentials.name;
@ -650,7 +661,10 @@ export default defineComponent({
}
},
onChangeSharedWith(sharees: IDataObject[]) {
Vue.set(this.credentialData, 'sharedWith', sharees);
this.credentialData = {
...this.credentialData,
sharedWith: sharees,
};
this.hasUnsavedChanges = true;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -997,7 +1011,11 @@ export default defineComponent({
const params =
'scrollbars=no,resizable=yes,status=no,titlebar=noe,location=no,toolbar=no,menubar=no,width=500,height=700';
const oauthPopup = window.open(url, 'OAuth2 Authorization', params);
Vue.set(this.credentialData, 'oauthTokenData', null);
this.credentialData = {
...this.credentialData,
oauthTokenData: null as unknown as CredentialInformation,
};
const receiveMessage = (event: MessageEvent) => {
// // TODO: Add check that it came from n8n
@ -1009,7 +1027,11 @@ export default defineComponent({
// Set some kind of data that status changes.
// As data does not get displayed directly it does not matter what data.
Vue.set(this.credentialData, 'oauthTokenData', {});
this.credentialData = {
...this.credentialData,
oauthTokenData: {} as CredentialInformation,
};
this.credentialsStore.enableOAuthCredential(credential);
// Close the window
@ -1061,7 +1083,10 @@ export default defineComponent({
}
for (const property of this.credentialType.properties) {
if (!this.credentialType.__overwrittenProperties?.includes(property.name)) {
Vue.set(this.credentialData, property.name, property.default as CredentialInformation);
this.credentialData = {
...this.credentialData,
[property.name]: property.default as CredentialInformation,
};
}
}
},

View file

@ -432,7 +432,7 @@ export default defineComponent({
this.allVisibleSelected = !this.allVisibleSelected;
if (!this.allVisibleSelected) {
this.allExistingSelected = false;
Vue.set(this, 'selectedItems', {});
this.selectedItems = {};
} else {
this.selectAllVisibleExecutions();
}
@ -441,7 +441,10 @@ export default defineComponent({
if (this.selectedItems[executionId]) {
Vue.delete(this.selectedItems, executionId);
} else {
Vue.set(this.selectedItems, executionId, true);
this.selectedItems = {
...this.selectedItems,
[executionId]: true,
};
}
this.allVisibleSelected =
Object.keys(this.selectedItems).length === this.combinedExecutions.length;
@ -502,7 +505,7 @@ export default defineComponent({
handleClearSelection(): void {
this.allVisibleSelected = false;
this.allExistingSelected = false;
Vue.set(this, 'selectedItems', {});
this.selectedItems = {};
},
async onFilterChanged(filter: ExecutionFilterType) {
this.filter = filter;
@ -635,7 +638,7 @@ export default defineComponent({
this.finishedExecutionsCount = pastExecutions.count;
this.finishedExecutionsCountEstimated = pastExecutions.estimated;
Vue.set(this, 'finishedExecutions', alreadyPresentExecutionsFiltered);
this.finishedExecutions = alreadyPresentExecutionsFiltered;
this.workflowsStore.addToCurrentExecutions(alreadyPresentExecutionsFiltered);
this.adjustSelectionAfterMoreItemsLoaded();
@ -706,7 +709,8 @@ export default defineComponent({
},
async loadWorkflows() {
try {
const workflows = await this.workflowsStore.fetchAllWorkflows();
const workflows =
(await this.workflowsStore.fetchAllWorkflows()) as IWorkflowShortResponse[];
workflows.sort((a, b) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) {
return -1;
@ -717,13 +721,12 @@ export default defineComponent({
return 0;
});
// @ts-ignore
workflows.unshift({
id: 'all',
name: this.$locale.baseText('executionsList.allWorkflows'),
});
} as IWorkflowShortResponse);
Vue.set(this, 'workflows', workflows);
this.workflows = workflows;
} catch (error) {
this.showError(
error,
@ -900,7 +903,7 @@ export default defineComponent({
await this.refreshData();
if (this.allVisibleSelected) {
Vue.set(this, 'selectedItems', {});
this.selectedItems = {};
this.selectAllVisibleExecutions();
}
} catch (error) {
@ -922,7 +925,7 @@ export default defineComponent({
},
selectAllVisibleExecutions() {
this.combinedExecutions.forEach((execution: IExecutionsSummary) => {
Vue.set(this.selectedItems, execution.id, true);
this.selectedItems = { ...this.selectedItems, [execution.id]: true };
});
},
adjustSelectionAfterMoreItemsLoaded() {

View file

@ -409,8 +409,8 @@ export default defineComponent({
},
},
watch: {
activeNode(node: INodeUi | null) {
if (node && !this.isActiveStickyNode) {
activeNode(node: INodeUi | null, oldNode: INodeUi | null) {
if (node && node.name !== oldNode?.name && !this.isActiveStickyNode) {
this.runInputIndex = -1;
this.runOutputIndex = -1;
this.isLinkingEnabled = true;

View file

@ -533,8 +533,10 @@ export default defineComponent({
Vue.delete(this.nodeValues, lastNamePart);
} else {
// Value should be set
// @ts-ignore
Vue.set(this.nodeValues, lastNamePart, value);
this.nodeValues = {
...this.nodeValues,
[lastNamePart as string]: value,
};
}
} else {
// Data is on lower level
@ -556,14 +558,22 @@ export default defineComponent({
} else {
// Value should be set
if (typeof value === 'object') {
// @ts-ignore
Vue.set(get(this.nodeValues, nameParts.join('.')), lastNamePart, deepCopy(value));
set(
get(this.nodeValues, nameParts.join('.')) as Record<string, unknown>,
lastNamePart as string,
deepCopy(value),
);
} else {
// @ts-ignore
Vue.set(get(this.nodeValues, nameParts.join('.')), lastNamePart, value);
set(
get(this.nodeValues, nameParts.join('.')) as Record<string, unknown>,
lastNamePart as string,
value,
);
}
}
}
this.nodeValues = { ...this.nodeValues };
},
credentialSelected(updateInformation: INodeUpdatePropertiesInformation) {
// Update the values on the node
@ -660,7 +670,7 @@ export default defineComponent({
if (Array.isArray(data)) {
data.splice(parseInt(index, 10), 1);
Vue.set(nodeParameters as object, path, data);
set(nodeParameters as object, path, data);
}
} else {
if (newValue === undefined) {
@ -744,7 +754,7 @@ export default defineComponent({
if (Array.isArray(data)) {
data.splice(parseInt(index, 10), 1);
Vue.set(nodeParameters as object, path, data);
set(nodeParameters as object, path, data);
}
} else {
if (newValue === undefined) {
@ -791,7 +801,10 @@ export default defineComponent({
// A property on the node itself changed
// Update data in settings
Vue.set(this.nodeValues, parameterData.name, newValue);
this.nodeValues = {
...this.nodeValues,
[parameterData.name]: newValue,
};
// Update data in vuex
const updateInformation = {
@ -818,58 +831,91 @@ export default defineComponent({
const foundNodeSettings = [];
if (this.node.color) {
foundNodeSettings.push('color');
Vue.set(this.nodeValues, 'color', this.node.color);
this.nodeValues = {
...this.nodeValues,
color: this.node.color,
};
}
if (this.node.notes) {
foundNodeSettings.push('notes');
Vue.set(this.nodeValues, 'notes', this.node.notes);
this.nodeValues = {
...this.nodeValues,
notes: this.node.notes,
};
}
if (this.node.alwaysOutputData) {
foundNodeSettings.push('alwaysOutputData');
Vue.set(this.nodeValues, 'alwaysOutputData', this.node.alwaysOutputData);
this.nodeValues = {
...this.nodeValues,
alwaysOutputData: this.node.alwaysOutputData,
};
}
if (this.node.executeOnce) {
foundNodeSettings.push('executeOnce');
Vue.set(this.nodeValues, 'executeOnce', this.node.executeOnce);
this.nodeValues = {
...this.nodeValues,
executeOnce: this.node.executeOnce,
};
}
if (this.node.continueOnFail) {
foundNodeSettings.push('continueOnFail');
Vue.set(this.nodeValues, 'continueOnFail', this.node.continueOnFail);
this.nodeValues = {
...this.nodeValues,
continueOnFail: this.node.continueOnFail,
};
}
if (this.node.notesInFlow) {
foundNodeSettings.push('notesInFlow');
Vue.set(this.nodeValues, 'notesInFlow', this.node.notesInFlow);
this.nodeValues = {
...this.nodeValues,
notesInFlow: this.node.notesInFlow,
};
}
if (this.node.retryOnFail) {
foundNodeSettings.push('retryOnFail');
Vue.set(this.nodeValues, 'retryOnFail', this.node.retryOnFail);
this.nodeValues = {
...this.nodeValues,
retryOnFail: this.node.retryOnFail,
};
}
if (this.node.maxTries) {
foundNodeSettings.push('maxTries');
Vue.set(this.nodeValues, 'maxTries', this.node.maxTries);
this.nodeValues = {
...this.nodeValues,
maxTries: this.node.maxTries,
};
}
if (this.node.waitBetweenTries) {
foundNodeSettings.push('waitBetweenTries');
Vue.set(this.nodeValues, 'waitBetweenTries', this.node.waitBetweenTries);
this.nodeValues = {
...this.nodeValues,
waitBetweenTries: this.node.waitBetweenTries,
};
}
// Set default node settings
for (const nodeSetting of this.nodeSettings) {
if (!foundNodeSettings.includes(nodeSetting.name)) {
// Set default value
Vue.set(this.nodeValues, nodeSetting.name, nodeSetting.default);
this.nodeValues = {
...this.nodeValues,
[nodeSetting.name]: nodeSetting.default,
};
}
}
Vue.set(this.nodeValues, 'parameters', deepCopy(this.node.parameters));
this.nodeValues = {
...this.nodeValues,
parameters: deepCopy(this.node.parameters),
};
} else {
this.nodeValid = false;
}

View file

@ -194,7 +194,7 @@ import {
defaultMessageEventBusDestinationSentryOptions,
} from 'n8n-workflow';
import type { PropType } from 'vue';
import Vue, { defineComponent } from 'vue';
import { defineComponent } from 'vue';
import { LOG_STREAM_MODAL_KEY, MODAL_CONFIRM } from '@/constants';
import Modal from '@/components/Modal.vue';
import { useMessage } from '@/composables';
@ -249,7 +249,9 @@ export default defineComponent({
showRemoveConfirm: false,
typeSelectValue: '',
typeSelectPlaceholder: 'Destination Type',
nodeParameters: deepCopy(defaultMessageEventBusDestinationOptions),
nodeParameters: deepCopy(
defaultMessageEventBusDestinationOptions,
) as MessageEventBusDestinationOptions,
webhookDescription: webhookModalDescription,
sentryDescription: sentryModalDescription,
syslogDescription: syslogModalDescription,
@ -400,13 +402,13 @@ export default defineComponent({
// Apply the new value
if (parameterData.value === undefined && parameterPathArray !== null) {
// Delete array item
const path = parameterPathArray[1];
const path = parameterPathArray[1] as keyof MessageEventBusDestinationOptions;
const index = parameterPathArray[2];
const data = get(nodeParameters, path);
if (Array.isArray(data)) {
data.splice(parseInt(index, 10), 1);
Vue.set(nodeParameters, path, data);
nodeParameters[path] = data as never;
}
} else {
if (newValue === undefined) {

View file

@ -39,7 +39,7 @@
</template>
<script lang="ts">
import Vue, { defineComponent } from 'vue';
import { defineComponent } from 'vue';
import type { ITag } from '@/Interface';
import IntersectionObserver from './IntersectionObserver.vue';
@ -109,7 +109,7 @@ export default defineComponent({
methods: {
onObserved({ el, isIntersecting }: { el: HTMLElement; isIntersecting: boolean }) {
if (el.dataset.id) {
Vue.set(this.$data.visibility, el.dataset.id, isIntersecting);
this.$data.visibility = { ...this.$data.visibility, [el.dataset.id]: isIntersecting };
}
},
onClick(e: MouseEvent, tag: TagEl) {

View file

@ -337,7 +337,6 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
@ -537,7 +536,7 @@ export default defineComponent({
workflowSettings.maxExecutionTimeout = this.rootStore.maxExecutionTimeout;
}
Vue.set(this, 'workflowSettings', workflowSettings);
this.workflowSettings = workflowSettings;
this.timeoutHMS = this.convertToHMS(workflowSettings.executionTimeout);
this.isLoading = false;
@ -752,7 +751,7 @@ export default defineComponent({
}
},
async loadWorkflows() {
const workflows = await this.workflowsStore.fetchAllWorkflows();
const workflows = (await this.workflowsStore.fetchAllWorkflows()) as IWorkflowShortResponse[];
workflows.sort((a, b) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) {
return -1;
@ -763,13 +762,12 @@ export default defineComponent({
return 0;
});
// @ts-ignore
workflows.unshift({
id: undefined as unknown as string,
name: this.$locale.baseText('workflowSettings.noWorkflow'),
});
} as IWorkflowShortResponse);
Vue.set(this, 'workflows', workflows);
this.workflows = workflows;
},
async saveSettings() {
// Set that the active state should be changed

View file

@ -220,10 +220,13 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
},
upsertCredential(credential: ICredentialsResponse): void {
if (credential.id) {
Vue.set(this.credentials, credential.id, {
...this.credentials[credential.id],
...credential,
});
this.credentials = {
...this.credentials,
[credential.id]: {
...this.credentials[credential.id],
...credential,
},
};
}
},
enableOAuthCredential(credential: ICredentialsResponse): void {
@ -351,31 +354,38 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
// Enterprise edition actions
setCredentialOwnedBy(payload: { credentialId: string; ownedBy: Partial<IUser> }) {
Vue.set(this.credentials[payload.credentialId], 'ownedBy', payload.ownedBy);
this.credentials[payload.credentialId] = {
...this.credentials[payload.credentialId],
ownedBy: payload.ownedBy,
};
},
async setCredentialSharedWith(payload: { sharedWith: IUser[]; credentialId: string }) {
if (useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
await setCredentialSharedWith(useRootStore().getRestApiContext, payload.credentialId, {
shareWithIds: payload.sharedWith.map((sharee) => sharee.id),
});
Vue.set(this.credentials[payload.credentialId], 'sharedWith', payload.sharedWith);
this.credentials[payload.credentialId] = {
...this.credentials[payload.credentialId],
sharedWith: payload.sharedWith,
};
}
},
addCredentialSharee(payload: { credentialId: string; sharee: Partial<IUser> }): void {
Vue.set(
this.credentials[payload.credentialId],
'sharedWith',
(this.credentials[payload.credentialId].sharedWith || []).concat([payload.sharee]),
);
this.credentials[payload.credentialId] = {
...this.credentials[payload.credentialId],
sharedWith: (this.credentials[payload.credentialId].sharedWith || []).concat([
payload.sharee,
]),
};
},
removeCredentialSharee(payload: { credentialId: string; sharee: Partial<IUser> }): void {
Vue.set(
this.credentials[payload.credentialId],
'sharedWith',
(this.credentials[payload.credentialId].sharedWith || []).filter(
this.credentials[payload.credentialId] = {
...this.credentials[payload.credentialId],
sharedWith: (this.credentials[payload.credentialId].sharedWith || []).filter(
(sharee) => sharee.id !== payload.sharee.id,
),
);
};
},
async getCredentialTranslation(credentialType: string): Promise<object> {

View file

@ -2,7 +2,6 @@ import { CLOUD_BASE_URL_PRODUCTION, CLOUD_BASE_URL_STAGING, STORES } from '@/con
import type { IRestApiContext, RootState } from '@/Interface';
import type { IDataObject } from 'n8n-workflow';
import { defineStore } from 'pinia';
import Vue from 'vue';
import { useNodeTypesStore } from './nodeTypes.store';
const { VUE_APP_URL_BASE_API } = import.meta.env;
@ -76,44 +75,44 @@ export const useRootStore = defineStore(STORES.ROOT, {
actions: {
setUrlBaseWebhook(urlBaseWebhook: string): void {
const url = urlBaseWebhook.endsWith('/') ? urlBaseWebhook : `${urlBaseWebhook}/`;
Vue.set(this, 'urlBaseWebhook', url);
this.urlBaseWebhook = url;
},
setUrlBaseEditor(urlBaseEditor: string): void {
const url = urlBaseEditor.endsWith('/') ? urlBaseEditor : `${urlBaseEditor}/`;
Vue.set(this, 'urlBaseEditor', url);
this.urlBaseEditor = url;
},
setEndpointWebhook(endpointWebhook: string): void {
Vue.set(this, 'endpointWebhook', endpointWebhook);
this.endpointWebhook = endpointWebhook;
},
setEndpointWebhookTest(endpointWebhookTest: string): void {
Vue.set(this, 'endpointWebhookTest', endpointWebhookTest);
this.endpointWebhookTest = endpointWebhookTest;
},
setTimezone(timezone: string): void {
Vue.set(this, 'timezone', timezone);
this.timezone = timezone;
},
setExecutionTimeout(executionTimeout: number): void {
Vue.set(this, 'executionTimeout', executionTimeout);
this.executionTimeout = executionTimeout;
},
setMaxExecutionTimeout(maxExecutionTimeout: number): void {
Vue.set(this, 'maxExecutionTimeout', maxExecutionTimeout);
this.maxExecutionTimeout = maxExecutionTimeout;
},
setVersionCli(version: string): void {
Vue.set(this, 'versionCli', version);
this.versionCli = version;
},
setInstanceId(instanceId: string): void {
Vue.set(this, 'instanceId', instanceId);
this.instanceId = instanceId;
},
setOauthCallbackUrls(urls: IDataObject): void {
Vue.set(this, 'oauthCallbackUrls', urls);
this.oauthCallbackUrls = urls;
},
setN8nMetadata(metadata: IDataObject): void {
Vue.set(this, 'n8nMetadata', metadata);
this.n8nMetadata = metadata as RootState['n8nMetadata'];
},
setDefaultLocale(locale: string): void {
Vue.set(this, 'defaultLocale', locale);
this.defaultLocale = locale;
},
setIsNpmAvailable(isNpmAvailable: boolean): void {
Vue.set(this, 'isNpmAvailable', isNpmAvailable);
this.isNpmAvailable = isNpmAvailable;
},
},
});

View file

@ -8,7 +8,6 @@ import type {
} from '@/Interface';
import type { INodeIssues, IRunData } from 'n8n-workflow';
import { defineStore } from 'pinia';
import Vue from 'vue';
import { useWorkflowsStore } from './workflows.store';
export const useNDVStore = defineStore(STORES.NDV, {
@ -128,38 +127,47 @@ export const useNDVStore = defineStore(STORES.NDV, {
},
},
actions: {
setInputNodeName(name: string | undefined): void {
Vue.set(this.input, 'nodeName', name);
setInputNodeName(nodeName: string | undefined): void {
this.input = {
...this.input,
nodeName,
};
},
setInputRunIndex(run?: string): void {
Vue.set(this.input, 'run', run);
setInputRunIndex(run?: number): void {
this.input = {
...this.input,
run,
};
},
setMainPanelDimensions(params: {
panelType: string;
dimensions: { relativeLeft?: number; relativeRight?: number; relativeWidth?: number };
}): void {
Vue.set(this.mainPanelDimensions, params.panelType, {
...this.mainPanelDimensions[params.panelType],
...params.dimensions,
});
this.mainPanelDimensions = {
...this.mainPanelDimensions,
[params.panelType]: {
...this.mainPanelDimensions[params.panelType],
...params.dimensions,
},
};
},
setNDVSessionId(): void {
Vue.set(this, 'sessionId', `ndv-${Math.random().toString(36).slice(-8)}`);
this.sessionId = `ndv-${Math.random().toString(36).slice(-8)}`;
},
resetNDVSessionId(): void {
Vue.set(this, 'sessionId', '');
this.sessionId = '';
},
setPanelDisplayMode(params: { pane: NodePanelType; mode: IRunDataDisplayMode }): void {
Vue.set(this[params.pane], 'displayMode', params.mode);
this[params.pane].displayMode = params.mode;
},
setOutputPanelEditModeEnabled(isEnabled: boolean): void {
Vue.set(this.output.editMode, 'enabled', isEnabled);
this.output.editMode.enabled = isEnabled;
},
setOutputPanelEditModeValue(payload: string): void {
Vue.set(this.output.editMode, 'value', payload);
this.output.editMode.value = payload;
},
setMappableNDVInputFocus(paramName: string): void {
Vue.set(this, 'focusedMappableInput', paramName);
this.focusedMappableInput = paramName;
},
draggableStartDragging({ type, data }: { type: string; data: string }): void {
this.draggable = {
@ -180,10 +188,10 @@ export const useNDVStore = defineStore(STORES.NDV, {
};
},
setDraggableStickyPos(position: XYPosition | null): void {
Vue.set(this.draggable, 'stickyPosition', position);
this.draggable.stickyPosition = position;
},
setDraggableCanDrop(canDrop: boolean): void {
Vue.set(this.draggable, 'canDrop', canDrop);
this.draggable.canDrop = canDrop;
},
setMappingTelemetry(telemetry: { [key: string]: string | number | boolean }): void {
this.mappingTelemetry = { ...this.mappingTelemetry, ...telemetry };
@ -192,13 +200,13 @@ export const useNDVStore = defineStore(STORES.NDV, {
this.mappingTelemetry = {};
},
setHoveringItem(item: null | NDVState['hoveringItem']): void {
Vue.set(this, 'hoveringItem', item);
this.hoveringItem = item;
},
setNDVBranchIndex(e: { pane: 'input' | 'output'; branchIndex: number }): void {
Vue.set(this[e.pane], 'branch', e.branchIndex);
this[e.pane].branch = e.branchIndex;
},
setNDVPanelDataIsEmpty(payload: { panel: 'input' | 'output'; isEmpty: boolean }): void {
Vue.set(this[payload.panel].data, 'isEmpty', payload.isEmpty);
this[payload.panel].data.isEmpty = payload.isEmpty;
},
disableMappingHint(store = true) {
this.isMappingOnboarded = true;
@ -207,11 +215,19 @@ export const useNDVStore = defineStore(STORES.NDV, {
}
},
updateNodeParameterIssues(issues: INodeIssues): void {
const activeNode = this.activeNode;
const workflowsStore = useWorkflowsStore();
const activeNode = workflowsStore.getNodeByName(this.activeNodeName || '');
if (activeNode) {
Vue.set(activeNode, 'issues', {
...activeNode.issues,
...issues,
const nodeIndex = workflowsStore.workflow.nodes.findIndex((node) => {
return node.name === activeNode.name;
});
workflowsStore.updateNodeAtIndex(nodeIndex, {
issues: {
...activeNode.issues,
...issues,
},
});
}
},

View file

@ -25,7 +25,6 @@ import type {
ResourceMapperFields,
} from 'n8n-workflow';
import { defineStore } from 'pinia';
import Vue from 'vue';
import { useCredentialsStore } from './credentials.store';
import { useRootStore } from './n8nRoot.store';
@ -120,7 +119,7 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
},
{ ...this.nodeTypes },
);
Vue.set(this, 'nodeTypes', nodeTypes);
this.nodeTypes = nodeTypes;
},
removeNodeTypes(nodeTypesToRemove: INodeTypeDescription[]): void {
this.nodeTypes = nodeTypesToRemove.reduce(

View file

@ -26,7 +26,6 @@ import type {
WorkflowSettings,
} from 'n8n-workflow';
import { defineStore } from 'pinia';
import Vue from 'vue';
import { useRootStore } from './n8nRoot.store';
import { useUIStore } from './ui.store';
import { useUsersStore } from './users.store';
@ -220,13 +219,19 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
useVersionsStore().setVersionNotificationSettings(settings.versionNotifications);
},
stopShowingSetupPage(): void {
Vue.set(this.userManagement, 'showSetupOnFirstLoad', false);
this.userManagement.showSetupOnFirstLoad = false;
},
disableTemplates(): void {
Vue.set(this.settings.templates, 'enabled', false);
this.settings = {
...this.settings,
templates: {
...this.settings.templates,
enabled: false,
},
};
},
setPromptsData(promptsData: IN8nPrompts): void {
Vue.set(this, 'promptsData', promptsData);
this.promptsData = promptsData;
},
setAllowedModules(allowedModules: { builtIn?: string[]; external?: string[] }): void {
this.settings.allowedModules = allowedModules;
@ -315,13 +320,13 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
return runLdapSync(rootStore.getRestApiContext, data);
},
setSaveDataErrorExecution(newValue: string) {
Vue.set(this, 'saveDataErrorExecution', newValue);
this.saveDataErrorExecution = newValue;
},
setSaveDataSuccessExecution(newValue: string) {
Vue.set(this, 'saveDataSuccessExecution', newValue);
this.saveDataSuccessExecution = newValue;
},
setSaveManualExecutions(saveManualExecutions: boolean) {
Vue.set(this, 'saveManualExecutions', saveManualExecutions);
this.saveManualExecutions = saveManualExecutions;
},
async getTimezones(): Promise<IDataObject> {
const rootStore = useRootStore();

View file

@ -45,9 +45,15 @@ export const useTagsStore = defineStore(STORES.TAGS, {
...currentTag,
...tag,
};
Vue.set(this.tags, tagId, newTag);
this.tags = {
...this.tags,
[tagId]: newTag,
};
} else {
Vue.set(this.tags, tagId, tag);
this.tags = {
...this.tags,
[tagId]: tag,
};
}
});
},

View file

@ -10,7 +10,6 @@ import type {
ITemplatesWorkflowFull,
IWorkflowTemplate,
} from '@/Interface';
import Vue from 'vue';
import { useSettingsStore } from './settings.store';
import {
getCategories,
@ -104,27 +103,38 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
actions: {
addCategories(categories: ITemplatesCategory[]): void {
categories.forEach((category: ITemplatesCategory) => {
Vue.set(this.categories, category.id, category);
this.categories = {
...this.categories,
[category.id]: category,
};
});
},
addCollections(collections: Array<ITemplatesCollection | ITemplatesCollectionFull>): void {
collections.forEach((collection) => {
const workflows = (collection.workflows || []).map((workflow) => ({ id: workflow.id }));
const cachedCollection = this.collections[collection.id] || {};
Vue.set(this.collections, collection.id, {
...cachedCollection,
...collection,
workflows,
});
this.collections = {
...this.collections,
[collection.id]: {
...cachedCollection,
...collection,
workflows,
},
};
});
},
addWorkflows(workflows: Array<ITemplatesWorkflow | ITemplatesWorkflowFull>): void {
workflows.forEach((workflow: ITemplatesWorkflow) => {
const cachedWorkflow = this.workflows[workflow.id] || {};
Vue.set(this.workflows, workflow.id, {
...cachedWorkflow,
...workflow,
});
this.workflows = {
...this.workflows,
[workflow.id]: {
...cachedWorkflow,
...workflow,
},
};
});
},
addCollectionSearch(data: {
@ -133,9 +143,13 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
}): void {
const collectionIds = data.collections.map((collection) => collection.id);
const searchKey = getSearchKey(data.query);
Vue.set(this.collectionSearches, searchKey, {
collectionIds,
});
this.collectionSearches = {
...this.collectionSearches,
[searchKey]: {
collectionIds,
},
};
},
addWorkflowsSearch(data: {
totalWorkflows: number;
@ -146,18 +160,24 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
const searchKey = getSearchKey(data.query);
const cachedResults = this.workflowSearches[searchKey];
if (!cachedResults) {
Vue.set(this.workflowSearches, searchKey, {
workflowIds,
totalWorkflows: data.totalWorkflows,
});
this.workflowSearches = {
...this.workflowSearches,
[searchKey]: {
workflowIds: workflowIds as unknown as string[],
totalWorkflows: data.totalWorkflows,
},
};
return;
}
Vue.set(this.workflowSearches, searchKey, {
workflowIds: [...cachedResults.workflowIds, ...workflowIds],
totalWorkflows: data.totalWorkflows,
});
this.workflowSearches = {
...this.workflowSearches,
[searchKey]: {
workflowIds: [...cachedResults.workflowIds, ...workflowIds] as string[],
totalWorkflows: data.totalWorkflows,
},
};
},
setWorkflowSearchLoading(query: ITemplatesQuery): void {
const searchKey = getSearchKey(query);
@ -166,7 +186,10 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
return;
}
Vue.set(this.workflowSearches[searchKey], 'loadingMore', true);
this.workflowSearches[searchKey] = {
...this.workflowSearches[searchKey],
loadingMore: true,
};
},
setWorkflowSearchLoaded(query: ITemplatesQuery): void {
const searchKey = getSearchKey(query);
@ -175,7 +198,10 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
return;
}
Vue.set(this.workflowSearches[searchKey], 'loadingMore', false);
this.workflowSearches[searchKey] = {
...this.workflowSearches[searchKey],
loadingMore: false,
};
},
resetSessionId(): void {
this.previousSessionId = this.currentSessionId;

View file

@ -42,7 +42,6 @@ import type {
UIState,
XYPosition,
} from '@/Interface';
import Vue from 'vue';
import { defineStore } from 'pinia';
import { useRootStore } from './n8nRoot.store';
import { getCurlToJson } from '@/api/curlHelper';
@ -51,6 +50,7 @@ import { useSettingsStore } from './settings.store';
import { useCloudPlanStore } from './cloudPlan.store';
import type { BaseTextKey } from '@/plugins/i18n';
import { i18n as locale } from '@/plugins/i18n';
import type { Modals, NewCredentialsModal } from '@/Interface';
import { useTelemetryStore } from '@/stores/telemetry.store';
export const useUIStore = defineStore(STORES.UI, {
@ -332,36 +332,57 @@ export const useUIStore = defineStore(STORES.UI, {
},
},
actions: {
setMode(name: string, mode: string): void {
Vue.set(this.modals[name], 'mode', mode);
setMode(name: keyof Modals, mode: string): void {
this.modals[name] = {
...this.modals[name],
mode,
};
},
setActiveId(name: string, id: string): void {
Vue.set(this.modals[name], 'activeId', id);
setActiveId(name: keyof Modals, activeId: string): void {
this.modals[name] = {
...this.modals[name],
activeId,
};
},
setShowAuthSelector(name: string, show: boolean) {
Vue.set(this.modals[name], 'showAuthSelector', show);
setShowAuthSelector(name: keyof Modals, showAuthSelector: boolean) {
this.modals[name] = {
...this.modals[name],
showAuthSelector,
} as NewCredentialsModal;
},
setModalData(payload: { name: string; data: Record<string, unknown> }) {
Vue.set(this.modals[payload.name], 'data', payload.data);
setModalData(payload: { name: keyof Modals; data: Record<string, unknown> }) {
this.modals[payload.name] = {
...this.modals[payload.name],
data: payload.data,
};
},
openModal(name: string): void {
Vue.set(this.modals[name], 'open', true);
this.modalStack = [name].concat(this.modalStack);
openModal(name: keyof Modals): void {
this.modals[name] = {
...this.modals[name],
open: true,
};
this.modalStack = [name].concat(this.modalStack) as string[];
},
openModalWithData(payload: { name: string; data: Record<string, unknown> }): void {
openModalWithData(payload: { name: keyof Modals; data: Record<string, unknown> }): void {
this.setModalData(payload);
this.openModal(payload.name);
},
closeModal(name: string): void {
Vue.set(this.modals[name], 'open', false);
closeModal(name: keyof Modals): void {
this.modals[name] = {
...this.modals[name],
open: false,
};
this.modalStack = this.modalStack.filter((openModalName: string) => {
return name !== openModalName;
});
},
closeAllModals(): void {
Object.keys(this.modals).forEach((name: string) => {
Object.keys(this.modals).forEach((name) => {
if (this.modals[name].open) {
Vue.set(this.modals[name], 'open', false);
this.modals[name] = {
...this.modals[name],
open: false,
};
}
});
this.modalStack = [];
@ -385,10 +406,16 @@ export const useUIStore = defineStore(STORES.UI, {
};
},
setDraggableStickyPos(position: XYPosition): void {
Vue.set(this.draggable, 'stickyPosition', position);
this.draggable = {
...this.draggable,
stickyPosition: position,
};
},
setDraggableCanDrop(canDrop: boolean): void {
Vue.set(this.draggable, 'canDrop', canDrop);
this.draggable = {
...this.draggable,
canDrop,
};
},
openDeleteUserModal(id: string): void {
this.setActiveId(DELETE_USER_MODAL_KEY, id);
@ -460,17 +487,23 @@ export const useUIStore = defineStore(STORES.UI, {
}
},
resetSelectedNodes(): void {
Vue.set(this, 'selectedNodes', []);
this.selectedNodes = [];
},
addSidebarMenuItems(menuItems: IMenuItem[]) {
const updated = this.sidebarMenuItems.concat(menuItems);
Vue.set(this, 'sidebarMenuItems', updated);
this.sidebarMenuItems = updated;
},
setCurlCommand(payload: { name: string; command: string }): void {
Vue.set(this.modals[payload.name], 'curlCommand', payload.command);
this.modals[payload.name] = {
...this.modals[payload.name],
curlCommand: payload.command,
};
},
setHttpNodeParameters(payload: { name: string; parameters: string }): void {
Vue.set(this.modals[payload.name], 'httpNodeParameters', payload.parameters);
this.modals[payload.name] = {
...this.modals[payload.name],
httpNodeParameters: payload.parameters,
};
},
toggleSidebarMenuCollapse(): void {
this.sidebarMenuCollapsed = !this.sidebarMenuCollapsed;

View file

@ -35,7 +35,6 @@ import type {
import { getCredentialPermissions } from '@/permissions';
import { getPersonalizedNodeTypes, isAuthorized, PERMISSIONS, ROLE } from '@/utils';
import { defineStore } from 'pinia';
import Vue from 'vue';
import { useRootStore } from './n8nRoot.store';
import { usePostHog } from './posthog.store';
import { useSettingsStore } from './settings.store';
@ -133,17 +132,29 @@ export const useUsersStore = defineStore(STORES.USERS, {
isPendingUser: isPendingUser(updatedUser),
isOwner: updatedUser.globalRole?.name === ROLE.Owner,
};
Vue.set(this.users, user.id, user);
this.users = {
...this.users,
[user.id]: user,
};
});
},
deleteUserById(userId: string): void {
Vue.delete(this.users, userId);
const { [userId]: _, ...users } = this.users;
this.users = users;
},
setPersonalizationAnswers(answers: IPersonalizationLatestVersion): void {
if (!this.currentUser) {
return;
}
Vue.set(this.currentUser, 'personalizationAnswers', answers);
this.users = {
...this.users,
[this.currentUser.id]: {
...this.currentUser,
personalizationAnswers: answers,
},
};
},
async loginWithCookie(): Promise<void> {
const rootStore = useRootStore();

View file

@ -1,4 +1,3 @@
import Vue from 'vue';
import type { IUser } from '../Interface';
import { setWorkflowSharedWith } from '@/api/workflows.ee';
import { EnterpriseEditionFeature, STORES } from '@/constants';
@ -29,8 +28,14 @@ export const useWorkflowsEEStore = defineStore(STORES.WORKFLOWS_EE, {
setWorkflowOwnedBy(payload: { workflowId: string; ownedBy: Partial<IUser> }): void {
const workflowsStore = useWorkflowsStore();
Vue.set(workflowsStore.workflowsById[payload.workflowId], 'ownedBy', payload.ownedBy);
Vue.set(workflowsStore.workflow, 'ownedBy', payload.ownedBy);
workflowsStore.workflowsById[payload.workflowId] = {
...workflowsStore.workflowsById[payload.workflowId],
ownedBy: payload.ownedBy,
};
workflowsStore.workflow = {
...workflowsStore.workflow,
ownedBy: payload.ownedBy,
};
},
setWorkflowSharedWith(payload: {
workflowId: string;
@ -38,30 +43,34 @@ export const useWorkflowsEEStore = defineStore(STORES.WORKFLOWS_EE, {
}): void {
const workflowsStore = useWorkflowsStore();
Vue.set(workflowsStore.workflowsById[payload.workflowId], 'sharedWith', payload.sharedWith);
Vue.set(workflowsStore.workflow, 'sharedWith', payload.sharedWith);
workflowsStore.workflowsById[payload.workflowId] = {
...workflowsStore.workflowsById[payload.workflowId],
sharedWith: payload.sharedWith,
};
workflowsStore.workflow = {
...workflowsStore.workflow,
sharedWith: payload.sharedWith,
};
},
addWorkflowSharee(payload: { workflowId: string; sharee: Partial<IUser> }): void {
const workflowsStore = useWorkflowsStore();
Vue.set(
workflowsStore.workflowsById[payload.workflowId],
'sharedWith',
(workflowsStore.workflowsById[payload.workflowId].sharedWith || []).concat([
workflowsStore.workflowsById[payload.workflowId] = {
...workflowsStore.workflowsById[payload.workflowId],
sharedWith: (workflowsStore.workflowsById[payload.workflowId].sharedWith || []).concat([
payload.sharee,
]),
);
};
},
removeWorkflowSharee(payload: { workflowId: string; sharee: Partial<IUser> }): void {
const workflowsStore = useWorkflowsStore();
Vue.set(
workflowsStore.workflowsById[payload.workflowId],
'sharedWith',
(workflowsStore.workflowsById[payload.workflowId].sharedWith || []).filter(
workflowsStore.workflowsById[payload.workflowId] = {
...workflowsStore.workflowsById[payload.workflowId],
sharedWith: (workflowsStore.workflowsById[payload.workflowId].sharedWith || []).filter(
(sharee) => sharee.id !== payload.sharee.id,
),
);
};
},
async saveWorkflowSharedWith(payload: {
sharedWith: Array<Partial<IUser>>;

View file

@ -18,6 +18,7 @@ import type {
IExecutionsListResponse,
IExecutionsStopData,
INewWorkflowData,
INodeMetadata,
INodeUi,
INodeUpdatePropertiesInformation,
IPushDataExecutionFinished,
@ -26,6 +27,7 @@ import type {
IStartRunData,
IUpdateInformation,
IUsedCredential,
IUser,
IWorkflowDataUpdate,
IWorkflowDb,
IWorkflowsMap,
@ -44,6 +46,7 @@ import type {
INodeCredentialsDetails,
INodeExecutionData,
INodeIssueData,
INodeIssueObjectProperty,
INodeParameters,
INodeTypeData,
INodeTypes,
@ -83,6 +86,7 @@ import { useNodeTypesStore } from './nodeTypes.store';
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
import { useUsersStore } from '@/stores/users.store';
import { useSettingsStore } from '@/stores/settings.store';
import type { NodeMetadataMap } from '@/Interface';
const createEmptyWorkflow = (): IWorkflowDb => ({
id: PLACEHOLDER_EMPTY_WORKFLOW_ID,
@ -416,7 +420,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
this.workflow = createEmptyWorkflow();
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
Vue.set(this.workflow, 'ownedBy', usersStore.currentUser);
this.workflow = {
...this.workflow,
ownedBy: usersStore.currentUser as IUser,
};
}
},
@ -509,10 +516,13 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
},
addWorkflow(workflow: IWorkflowDb): void {
Vue.set(this.workflowsById, workflow.id, {
...this.workflowsById[workflow.id],
...deepCopy(workflow),
});
this.workflowsById = {
...this.workflowsById,
[workflow.id]: {
...this.workflowsById[workflow.id],
...deepCopy(workflow),
},
};
},
setWorkflowActive(workflowId: string): void {
@ -576,57 +586,60 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
},
setWorkflowSettings(workflowSettings: IWorkflowSettings): void {
Vue.set(this.workflow, 'settings', workflowSettings);
this.workflow = {
...this.workflow,
settings: workflowSettings as IWorkflowDb['settings'],
};
},
setWorkflowPinData(pinData: IPinData): void {
Vue.set(this.workflow, 'pinData', pinData || {});
this.workflow = {
...this.workflow,
pinData: pinData || {},
};
dataPinningEventBus.emit('pin-data', pinData || {});
},
setWorkflowTagIds(tags: string[]): void {
Vue.set(this.workflow, 'tags', tags);
this.workflow = {
...this.workflow,
tags,
};
},
addWorkflowTagIds(tags: string[]): void {
Vue.set(this.workflow, 'tags', [...new Set([...(this.workflow.tags || []), ...tags])]);
this.workflow = {
...this.workflow,
tags: [...new Set([...(this.workflow.tags || []), ...tags])] as IWorkflowDb['tags'],
};
},
removeWorkflowTagId(tagId: string): void {
const tags = this.workflow.tags as string[];
const updated = tags.filter((id: string) => id !== tagId);
Vue.set(this.workflow, 'tags', updated);
this.workflow = {
...this.workflow,
tags: updated as IWorkflowDb['tags'],
};
},
setWorkflow(workflow: IWorkflowDb): void {
Vue.set(this, 'workflow', workflow);
if (!this.workflow.hasOwnProperty('active')) {
Vue.set(this.workflow, 'active', false);
}
if (!this.workflow.hasOwnProperty('connections')) {
Vue.set(this.workflow, 'connections', {});
}
if (!this.workflow.hasOwnProperty('createdAt')) {
Vue.set(this.workflow, 'createdAt', -1);
}
if (!this.workflow.hasOwnProperty('updatedAt')) {
Vue.set(this.workflow, 'updatedAt', -1);
}
if (!this.workflow.hasOwnProperty('id')) {
Vue.set(this.workflow, 'id', PLACEHOLDER_EMPTY_WORKFLOW_ID);
}
if (!this.workflow.hasOwnProperty('nodes')) {
Vue.set(this.workflow, 'nodes', []);
}
if (!this.workflow.hasOwnProperty('settings')) {
Vue.set(this.workflow, 'settings', {});
}
this.workflow = workflow;
this.workflow = {
...this.workflow,
...(!this.workflow.hasOwnProperty('active') ? { active: false } : {}),
...(!this.workflow.hasOwnProperty('connections') ? { connections: {} } : {}),
...(!this.workflow.hasOwnProperty('createdAt') ? { createdAt: -1 } : {}),
...(!this.workflow.hasOwnProperty('updatedAt') ? { updatedAt: -1 } : {}),
...(!this.workflow.hasOwnProperty('id') ? { id: PLACEHOLDER_EMPTY_WORKFLOW_ID } : {}),
...(!this.workflow.hasOwnProperty('nodes') ? { nodes: [] } : {}),
...(!this.workflow.hasOwnProperty('settings') ? { settings: {} } : {}),
};
},
pinData(payload: { node: INodeUi; data: INodeExecutionData[] }): void {
if (!this.workflow.pinData) {
Vue.set(this.workflow, 'pinData', {});
this.workflow = { ...this.workflow, pinData: {} };
}
if (!Array.isArray(payload.data)) {
@ -637,7 +650,13 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
isJsonKeyObject(item) ? item : { json: item },
);
Vue.set(this.workflow.pinData!, payload.node.name, storedPinData);
this.workflow = {
...this.workflow,
pinData: {
...this.workflow.pinData,
[payload.node.name]: storedPinData,
},
};
const uiStore = useUIStore();
uiStore.stateIsDirty = true;
@ -647,11 +666,14 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
unpinData(payload: { node: INodeUi }): void {
if (!this.workflow.pinData) {
Vue.set(this.workflow, 'pinData', {});
this.workflow = { ...this.workflow, pinData: {} };
}
Vue.set(this.workflow.pinData!, payload.node.name, undefined);
delete this.workflow.pinData![payload.node.name];
const { [payload.node.name]: _, ...pinData } = this.workflow.pinData!;
this.workflow = {
...this.workflow,
pinData,
};
const uiStore = useUIStore();
uiStore.stateIsDirty = true;
@ -670,10 +692,25 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
// Check if source node and type exist already and if not add them
if (!this.workflow.connections.hasOwnProperty(sourceData.node)) {
Vue.set(this.workflow.connections, sourceData.node, {});
this.workflow = {
...this.workflow,
connections: {
...this.workflow.connections,
[sourceData.node]: {},
},
};
}
if (!this.workflow.connections[sourceData.node].hasOwnProperty(sourceData.type)) {
Vue.set(this.workflow.connections[sourceData.node], sourceData.type, []);
this.workflow = {
...this.workflow,
connections: {
...this.workflow.connections,
[sourceData.node]: {
...this.workflow.connections[sourceData.node],
[sourceData.type]: [],
},
},
};
}
if (
this.workflow.connections[sourceData.node][sourceData.type].length <
@ -817,12 +854,19 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
uiStore.lastSelectedNode = nameData.new;
}
Vue.set(this.nodeMetadata, nameData.new, this.nodeMetadata[nameData.old]);
this.nodeMetadata = { ...this.nodeMetadata, [nameData.new]: this.nodeMetadata[nameData.old] };
Vue.delete(this.nodeMetadata, nameData.old);
if (this.workflow.pinData && this.workflow.pinData.hasOwnProperty(nameData.old)) {
Vue.set(this.workflow.pinData, nameData.new, this.workflow.pinData[nameData.old]);
Vue.delete(this.workflow.pinData, nameData.old);
this.workflow = {
...this.workflow,
pinData: {
...this.workflow.pinData,
[nameData.new]: this.workflow.pinData[nameData.old],
},
};
Vue.delete(this.workflow.pinData!, nameData.old);
}
this.workflowExecutionPairedItemMappings = getPairedItemsMapping(this.workflowExecutionData);
@ -835,13 +879,31 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
return true;
},
updateNodeAtIndex(nodeIndex: number, nodeData: Partial<INodeUi>): void {
if (nodeIndex !== -1) {
const node = this.workflow.nodes[nodeIndex];
this.workflow = {
...this.workflow,
nodes: [
...this.workflow.nodes.slice(0, nodeIndex),
{ ...node, ...nodeData },
...this.workflow.nodes.slice(nodeIndex + 1),
],
};
}
},
setNodeIssue(nodeIssueData: INodeIssueData): boolean {
const node = this.workflow.nodes.find((node) => {
const nodeIndex = this.workflow.nodes.findIndex((node) => {
return node.name === nodeIssueData.node;
});
if (!node) {
if (nodeIndex === -1) {
return false;
}
const node = this.workflow.nodes[nodeIndex!];
if (nodeIssueData.value === null) {
// Remove the value if one exists
if (node.issues === undefined || node.issues[nodeIssueData.type] === undefined) {
@ -853,10 +915,17 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
Vue.delete(node.issues, nodeIssueData.type);
} else {
if (node.issues === undefined) {
Vue.set(node, 'issues', {});
this.updateNodeAtIndex(nodeIndex, {
issues: {},
});
}
// Set/Overwrite the value
Vue.set(node.issues!, nodeIssueData.type, nodeIssueData.value);
this.updateNodeAtIndex(nodeIndex, {
issues: {
...node.issues,
[nodeIssueData.type]: nodeIssueData.value as INodeIssueObjectProperty,
},
});
}
return true;
},
@ -871,7 +940,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
this.workflow.nodes.push(nodeData);
// Init node metadata
if (!this.nodeMetadata[nodeData.name]) {
Vue.set(this.nodeMetadata, nodeData.name, {});
this.nodeMetadata = { ...this.nodeMetadata, [nodeData.name]: {} as INodeMetadata };
}
},
@ -899,7 +968,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
}
if (data.removePinData) {
Vue.set(this.workflow, 'pinData', {});
this.workflow = {
...this.workflow,
pinData: {},
};
}
this.workflow.nodes.splice(0, this.workflow.nodes.length);
@ -908,26 +980,29 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
updateNodeProperties(updateInformation: INodeUpdatePropertiesInformation): void {
// Find the node that should be updated
const node = this.workflow.nodes.find((node) => {
const nodeIndex = this.workflow.nodes.findIndex((node) => {
return node.name === updateInformation.name;
});
if (node) {
if (nodeIndex !== -1) {
for (const key of Object.keys(updateInformation.properties)) {
const uiStore = useUIStore();
uiStore.stateIsDirty = true;
Vue.set(node, key, updateInformation.properties[key]);
this.updateNodeAtIndex(nodeIndex, {
[key]: updateInformation.properties[key],
});
}
}
},
setNodeValue(updateInformation: IUpdateInformation): void {
// Find the node that should be updated
const node = this.workflow.nodes.find((node) => {
const nodeIndex = this.workflow.nodes.findIndex((node) => {
return node.name === updateInformation.name;
});
if (node === undefined || node === null || !updateInformation.key) {
if (nodeIndex === -1 || !updateInformation.key) {
throw new Error(
`Node with the name "${updateInformation.name}" could not be found to set parameter.`,
);
@ -935,21 +1010,26 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
const uiStore = useUIStore();
uiStore.stateIsDirty = true;
Vue.set(node, updateInformation.key, updateInformation.value);
this.updateNodeAtIndex(nodeIndex, {
[updateInformation.key]: updateInformation.value,
});
},
setNodeParameters(updateInformation: IUpdateInformation, append?: boolean): void {
// Find the node that should be updated
const node = this.workflow.nodes.find((node) => {
const nodeIndex = this.workflow.nodes.findIndex((node) => {
return node.name === updateInformation.name;
});
if (node === undefined || node === null) {
if (nodeIndex === -1) {
throw new Error(
`Node with the name "${updateInformation.name}" could not be found to set parameter.`,
);
}
const node = this.workflow.nodes[nodeIndex];
const uiStore = useUIStore();
uiStore.stateIsDirty = true;
const newParameters =
@ -957,13 +1037,17 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
? { ...node.parameters, ...updateInformation.value }
: updateInformation.value;
Vue.set(node, 'parameters', newParameters);
this.updateNodeAtIndex(nodeIndex, {
parameters: newParameters as INodeParameters,
});
if (!this.nodeMetadata[node.name]) {
Vue.set(this.nodeMetadata, node.name, {});
}
Vue.set(this.nodeMetadata[node.name], 'parametersLastUpdatedAt', Date.now());
this.nodeMetadata = {
...this.nodeMetadata,
[node.name]: {
...this.nodeMetadata[node.name],
parametersLastUpdatedAt: Date.now(),
},
} as NodeMetadataMap;
},
setLastNodeParameters(updateInformation: IUpdateInformation) {
@ -989,9 +1073,21 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
throw new Error('The "workflowExecutionData" is not initialized!');
}
if (this.workflowExecutionData.data.resultData.runData[pushData.nodeName] === undefined) {
Vue.set(this.workflowExecutionData.data.resultData.runData, pushData.nodeName, []);
this.workflowExecutionData = {
...this.workflowExecutionData,
data: {
...this.workflowExecutionData.data,
resultData: {
...this.workflowExecutionData.data.resultData,
runData: {
...this.workflowExecutionData.data.resultData.runData,
[pushData.nodeName]: [],
},
},
},
};
}
this.workflowExecutionData.data.resultData.runData[pushData.nodeName].push(pushData.data);
this.workflowExecutionData.data!.resultData.runData[pushData.nodeName].push(pushData.data);
this.workflowExecutionPairedItemMappings = getPairedItemsMapping(this.workflowExecutionData);
},
clearNodeExecutionData(nodeName: string): void {
@ -1033,28 +1129,37 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
finishedActiveExecution: IPushDataExecutionFinished | IPushDataUnsavedExecutionFinished,
): void {
// Find the execution to set to finished
const activeExecution = this.activeExecutions.find((execution) => {
const activeExecutionIndex = this.activeExecutions.findIndex((execution) => {
return execution.id === finishedActiveExecution.executionId;
});
if (activeExecution === undefined) {
if (activeExecutionIndex === -1) {
// The execution could not be found
return;
}
if (finishedActiveExecution.executionId !== undefined) {
Vue.set(activeExecution, 'id', finishedActiveExecution.executionId);
}
const activeExecution = this.activeExecutions[activeExecutionIndex];
this.activeExecutions = [
...this.activeExecutions.slice(0, activeExecutionIndex),
{
...activeExecution,
...(finishedActiveExecution.executionId !== undefined
? { id: finishedActiveExecution.executionId }
: {}),
finished: finishedActiveExecution.data.finished,
stoppedAt: finishedActiveExecution.data.stoppedAt,
},
...this.activeExecutions.slice(activeExecutionIndex + 1),
];
Vue.set(activeExecution, 'finished', finishedActiveExecution.data.finished);
Vue.set(activeExecution, 'stoppedAt', finishedActiveExecution.data.stoppedAt);
if (finishedActiveExecution.data && (finishedActiveExecution.data as IRun).data) {
this.setWorkflowExecutionRunData((finishedActiveExecution.data as IRun).data);
}
},
setActiveExecutions(newActiveExecutions: IExecutionsCurrentSummaryExtended[]): void {
Vue.set(this, 'activeExecutions', newActiveExecutions);
this.activeExecutions = newActiveExecutions;
},
async retryExecution(id: string, loadWorkflow?: boolean): Promise<boolean> {
@ -1247,7 +1352,13 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
},
setNodePristine(nodeName: string, isPristine: boolean): void {
Vue.set(this.nodeMetadata[nodeName], 'pristine', isPristine);
this.nodeMetadata = {
...this.nodeMetadata,
[nodeName]: {
...this.nodeMetadata[nodeName],
pristine: isPristine,
},
};
},
},
});