feat: Add workflow sharing functionality and permissions (#4370)

* feat(editor): extract credentials view into reusable layout components for workflows view

* feat(editor): add workflow card and start work on empty state

* feat: add hoverable card and finish workflows empty state

* fix: undo workflows response interface changes

* chore: fix linting issues.

* fix: remove enterprise sharing env schema

* fix(editor): fix workflows resource view when sharing is enabled

* fix: change owner tag design and order

* feat: add personalization survey on workflows page

* fix: update component snapshots

* feat: refactored workflow card to use workflow-activator properly

* fix: fix workflow activator and proptypes

* fix: hide owner tag for workflow card until sharing is available

* fix: fixed ownedBy and sharedWith appearing for workflows list

* feat: update tags component design

* refactor: change resource filter select to n8n-user-select

* fix: made telemetry messages reusable

* chore: remove unused import

* refactor: fix component name casing

* refactor: use Vue.set to make workflow property reactive

* feat: add support for clicking on tags for filtering

* chore: fix tags linting issues

* fix: fix resources list layout when title words are very long

* refactor: add active and inactive status text to workflow activator

* fix: fix credentials and workflows sorting when name contains leading whitespace

* fix: remove wrongfully added style tag

* feat: add translations and storybook examples for truncated tags

* fix: remove enterprise sharing env from schema

* refactor: fix workflows module and workflows field store naming conflict

* feat: add workflow share button and open dummy modal

* feat: add workflow sharing modal (in progress)

* feat: add message when sharing disabled

* feat: add sharing messages based on flags

* feat: add workflow sharing api integration and readonly state handling

* fix: change how foreign credentials are handled

* refactor: migrate newly added workflow sharing store methods to pinia

* fix: update foreign credentials handler and add executable prop to node-settings

* fix: fix credentials display issue caused by addCredentials override

* fix: fix various issues when sharing from empty state

* fix: update node duplication credentials

* fix: revert defautl values for sharing env

* feat: hide share button behind feature flag

* chore: add env variable for sharing feature (testing only)

* fix: change enterprise-edition component casing
This commit is contained in:
Alex Grozav
2022-11-15 14:25:04 +02:00
committed by GitHub
parent d1ffc58aa4
commit 898c25fd7e
27 changed files with 567 additions and 97 deletions

View File

@@ -149,7 +149,7 @@ import {
STICKY_NODE_TYPE,
VIEWS,
WEBHOOK_NODE_TYPE,
TRIGGER_NODE_FILTER,
TRIGGER_NODE_FILTER, EnterpriseEditionFeature,
} from '@/constants';
import { copyPaste } from '@/components/mixins/copyPaste';
import { externalHooks } from '@/components/mixins/externalHooks';
@@ -221,6 +221,7 @@ import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users';
import { getNodeViewTab } from '@/components/helpers';
import { Route, RawLocation } from 'vue-router';
import { nodeViewEventBus } from '@/event-bus/node-view-event-bus';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv';
@@ -231,6 +232,7 @@ import { useTagsStore } from '@/stores/tags';
import { useNodeCreatorStore } from '@/stores/nodeCreator';
import { dataPinningEventBus } from '@/event-bus/data-pinning-event-bus';
import { useCanvasStore } from '@/stores/canvas';
import useWorkflowsEEStore from "@/stores/workflows.ee";
interface AddNodeOptions {
position?: XYPosition;
@@ -395,6 +397,7 @@ export default mixins(
useUIStore,
useUsersStore,
useWorkflowsStore,
useWorkflowsEEStore,
),
nativelyNumberSuffixedDefaults(): string[] {
return this.rootStore.nativelyNumberSuffixedDefaults;
@@ -830,6 +833,7 @@ export default mixins(
),
);
}
this.workflowsStore.setActive(data.active || false);
this.workflowsStore.setWorkflowId(workflowId);
this.workflowsStore.setWorkflowName({ newName: data.name, setStateDirty: false });
@@ -837,6 +841,32 @@ export default mixins(
this.workflowsStore.setWorkflowPinData(data.pinData || {});
this.workflowsStore.setWorkflowHash(data.hash);
// @TODO
this.workflowsStore.addWorkflow({
id: data.id,
name: data.name,
ownedBy: data.ownedBy,
sharedWith: data.sharedWith,
tags: data.tags || [],
active: data.active,
createdAt: data.createdAt,
updatedAt: data.updatedAt,
nodes: data.nodes,
connections: data.connections,
});
this.workflowsEEStore.setWorkflowOwnedBy({
workflowId: data.id,
ownedBy: data.ownedBy,
});
this.workflowsEEStore.setWorkflowSharedWith({
workflowId: data.id,
sharedWith: data.sharedWith,
});
if (data.usedCredentials) {
this.credentialsStore.addCredentials(data.usedCredentials);
}
const tags = (data.tags || []) as ITag[];
const tagIds = tags.map((tag) => tag.id);
this.workflowsStore.setWorkflowTagIds(tagIds || []);
@@ -2372,6 +2402,15 @@ export default mixins(
newNodeData.webhookId = uuid();
}
if (newNodeData.credentials && this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.WorkflowSharing)) {
const foreignCredentials = this.credentialsStore.foreignCredentialsById;
newNodeData.credentials = Object.fromEntries(
Object.entries(newNodeData.credentials).filter(([_, credential]) => {
return credential.id && (!foreignCredentials[credential.id] || foreignCredentials[credential.id]?.currentUserHasAccess);
}),
);
}
await this.addNodes([newNodeData]);
const pinData = this.workflowsStore.pinDataByNodeName(nodeName);
@@ -3065,6 +3104,7 @@ export default mixins(
this.workflowsStore.resetAllNodesIssues();
// vm.$forceUpdate();
this.workflowsStore.$patch({ workflow: {} });
this.workflowsStore.setActive(false);
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
this.workflowsStore.setWorkflowName({ newName: '', setStateDirty: false });
@@ -3094,7 +3134,6 @@ export default mixins(
},
async loadCredentials(): Promise<void> {
await this.credentialsStore.fetchAllCredentials();
await this.credentialsStore.fetchForeignCredentials();
},
async loadNodesProperties(nodeInfos: INodeTypeNameVersion[]): Promise<void> {
const allNodes: INodeTypeDescription[] = this.nodeTypesStore.allNodeTypes;
@@ -3217,6 +3256,10 @@ export default mixins(
onAddNode({ nodeTypeName, position }: { nodeTypeName: string; position?: [number, number] }) {
this.addNode(nodeTypeName, { position });
},
async saveCurrentWorkflowExternal(callback: () => void) {
await this.saveCurrentWorkflow();
callback?.();
},
},
async mounted() {
this.$titleReset();
@@ -3304,8 +3347,6 @@ export default mixins(
}, promptTimeout);
}
}
dataPinningEventBus.$on('pin-data', this.addPinDataConnections);
dataPinningEventBus.$on('unpin-data', this.removePinDataConnections);
},
activated() {
const openSideMenu = this.uiStore.addFirstStepOnLoad;
@@ -3317,23 +3358,29 @@ export default mixins(
document.addEventListener('keydown', this.keyDown);
document.addEventListener('keyup', this.keyUp);
window.addEventListener('message', this.onPostMessageReceived);
this.$root.$on('newWorkflow', this.newWorkflow);
this.$root.$on('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$on('importWorkflowUrl', this.onImportWorkflowUrlEvent);
dataPinningEventBus.$on('pin-data', this.addPinDataConnections);
dataPinningEventBus.$on('unpin-data', this.removePinDataConnections);
nodeViewEventBus.$on('saveWorkflow', this.saveCurrentWorkflowExternal);
this.canvasStore.isDemo = this.isDemo;
},
deactivated () {
document.removeEventListener('keydown', this.keyDown);
document.removeEventListener('keyup', this.keyUp);
window.removeEventListener('message', this.onPostMessageReceived);
this.$root.$off('newWorkflow', this.newWorkflow);
this.$root.$off('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
dataPinningEventBus.$off('pin-data', this.addPinDataConnections);
dataPinningEventBus.$off('unpin-data', this.removePinDataConnections);
nodeViewEventBus.$off('saveWorkflow', this.saveCurrentWorkflowExternal);
},
destroyed() {
this.resetWorkspace();
@@ -3342,9 +3389,6 @@ export default mixins(
this.$root.$off('newWorkflow', this.newWorkflow);
this.$root.$off('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
dataPinningEventBus.$off('pin-data', this.addPinDataConnections);
dataPinningEventBus.$off('unpin-data', this.removePinDataConnections);
},
});
</script>