mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat: Add workflow sharing telemetry (#4906)
* feat: Add workflow sharing telemetry * chore: fix linting issue * fix: fix telemetry typo
This commit is contained in:
@@ -953,6 +953,8 @@ export interface IUsedCredential {
|
|||||||
name: string;
|
name: string;
|
||||||
credentialType: string;
|
credentialType: string;
|
||||||
currentUserHasAccess: boolean;
|
currentUserHasAccess: boolean;
|
||||||
|
ownedBy: Partial<IUser>;
|
||||||
|
sharedWith: Array<Partial<IUser>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkflowsState {
|
export interface WorkflowsState {
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ import { mapStores } from 'pinia';
|
|||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows';
|
import { useWorkflowsStore } from '@/stores/workflows';
|
||||||
import { IWorkflowDataUpdate } from '@/Interface';
|
import { IWorkflowDataUpdate } from '@/Interface';
|
||||||
|
import { getWorkflowPermissions, IPermissions } from '@/permissions';
|
||||||
|
import { useUsersStore } from '@/stores/users';
|
||||||
|
|
||||||
export default mixins(showMessage, workflowHelpers, restApi).extend({
|
export default mixins(showMessage, workflowHelpers, restApi).extend({
|
||||||
components: { TagsDropdown, Modal },
|
components: { TagsDropdown, Modal },
|
||||||
@@ -85,7 +87,13 @@ export default mixins(showMessage, workflowHelpers, restApi).extend({
|
|||||||
this.$nextTick(() => this.focusOnNameInput());
|
this.$nextTick(() => this.focusOnNameInput());
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapStores(useSettingsStore, useWorkflowsStore),
|
...mapStores(useUsersStore, useSettingsStore, useWorkflowsStore),
|
||||||
|
workflowPermissions(): IPermissions {
|
||||||
|
return getWorkflowPermissions(
|
||||||
|
this.usersStore.currentUser,
|
||||||
|
this.workflowsStore.getWorkflowById(this.data.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
isActive(active) {
|
isActive(active) {
|
||||||
@@ -157,6 +165,7 @@ export default mixins(showMessage, workflowHelpers, restApi).extend({
|
|||||||
this.$telemetry.track('User duplicated workflow', {
|
this.$telemetry.track('User duplicated workflow', {
|
||||||
old_workflow_id: currentWorkflowId,
|
old_workflow_id: currentWorkflowId,
|
||||||
workflow_id: this.data.id,
|
workflow_id: this.data.id,
|
||||||
|
sharing_role: this.workflowPermissions.isOwner ? 'owner' : 'sharee',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -139,7 +139,13 @@ import SaveButton from '@/components/SaveButton.vue';
|
|||||||
import TagsDropdown from '@/components/TagsDropdown.vue';
|
import TagsDropdown from '@/components/TagsDropdown.vue';
|
||||||
import InlineTextEdit from '@/components/InlineTextEdit.vue';
|
import InlineTextEdit from '@/components/InlineTextEdit.vue';
|
||||||
import BreakpointsObserver from '@/components/BreakpointsObserver.vue';
|
import BreakpointsObserver from '@/components/BreakpointsObserver.vue';
|
||||||
import { IWorkflowDataUpdate, IWorkflowDb, IWorkflowToShare, NestedRecord } from '@/Interface';
|
import {
|
||||||
|
IUser,
|
||||||
|
IWorkflowDataUpdate,
|
||||||
|
IWorkflowDb,
|
||||||
|
IWorkflowToShare,
|
||||||
|
NestedRecord,
|
||||||
|
} from '@/Interface';
|
||||||
|
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
import { titleChange } from '@/mixins/titleChange';
|
import { titleChange } from '@/mixins/titleChange';
|
||||||
@@ -194,6 +200,9 @@ export default mixins(workflowHelpers, titleChange).extend({
|
|||||||
useWorkflowsStore,
|
useWorkflowsStore,
|
||||||
useUsersStore,
|
useUsersStore,
|
||||||
),
|
),
|
||||||
|
currentUser(): IUser | null {
|
||||||
|
return this.usersStore.currentUser;
|
||||||
|
},
|
||||||
dynamicTranslations(): NestedRecord<string> {
|
dynamicTranslations(): NestedRecord<string> {
|
||||||
return this.uiStore.dynamicTranslations;
|
return this.uiStore.dynamicTranslations;
|
||||||
},
|
},
|
||||||
@@ -302,6 +311,12 @@ export default mixins(workflowHelpers, titleChange).extend({
|
|||||||
name: WORKFLOW_SHARE_MODAL_KEY,
|
name: WORKFLOW_SHARE_MODAL_KEY,
|
||||||
data: { id: this.currentWorkflowId },
|
data: { id: this.currentWorkflowId },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$telemetry.track('User opened sharing modal', {
|
||||||
|
workflow_id: this.currentWorkflowId,
|
||||||
|
user_id_sharer: this.currentUser?.id,
|
||||||
|
sub_view: this.$route.name === VIEWS.WORKFLOWS ? 'Workflows listing' : 'Workflow editor',
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onTagsEditEnable() {
|
onTagsEditEnable() {
|
||||||
this.$data.appliedTagIds = this.currentWorkflowTagIds;
|
this.$data.appliedTagIds = this.currentWorkflowTagIds;
|
||||||
|
|||||||
@@ -421,6 +421,7 @@ export default mixins(
|
|||||||
node_type: this.activeNodeType ? this.activeNodeType.name : '',
|
node_type: this.activeNodeType ? this.activeNodeType.name : '',
|
||||||
workflow_id: this.workflowsStore.workflowId,
|
workflow_id: this.workflowsStore.workflowId,
|
||||||
session_id: this.sessionId,
|
session_id: this.sessionId,
|
||||||
|
is_editable: !this.hasForeignCredential,
|
||||||
parameters_pane_position: this.mainPanelPosition,
|
parameters_pane_position: this.mainPanelPosition,
|
||||||
input_first_connector_runs: this.maxInputRun,
|
input_first_connector_runs: this.maxInputRun,
|
||||||
output_first_connector_runs: this.maxOutputRun,
|
output_first_connector_runs: this.maxOutputRun,
|
||||||
|
|||||||
@@ -206,6 +206,12 @@ export default mixins(showMessage, restApi).extend({
|
|||||||
name: WORKFLOW_SHARE_MODAL_KEY,
|
name: WORKFLOW_SHARE_MODAL_KEY,
|
||||||
data: { id: this.data.id },
|
data: { id: this.data.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$telemetry.track('User opened sharing modal', {
|
||||||
|
workflow_id: this.data.id,
|
||||||
|
user_id_sharer: this.currentUser.id,
|
||||||
|
sub_view: this.$route.name === VIEWS.WORKFLOWS ? 'Workflows listing' : 'Workflow editor',
|
||||||
|
});
|
||||||
} else if (action === WORKFLOW_LIST_ITEM_ACTIONS.DELETE) {
|
} else if (action === WORKFLOW_LIST_ITEM_ACTIONS.DELETE) {
|
||||||
const deleteConfirmed = await this.confirmMessage(
|
const deleteConfirmed = await this.confirmMessage(
|
||||||
this.$locale.baseText('mainSidebar.confirmMessage.workflowDelete.message', {
|
this.$locale.baseText('mainSidebar.confirmMessage.workflowDelete.message', {
|
||||||
|
|||||||
@@ -132,7 +132,8 @@ import { useSettingsStore } from '@/stores/settings';
|
|||||||
import { useUIStore } from '@/stores/ui';
|
import { useUIStore } from '@/stores/ui';
|
||||||
import { useUsersStore } from '@/stores/users';
|
import { useUsersStore } from '@/stores/users';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows';
|
import { useWorkflowsStore } from '@/stores/workflows';
|
||||||
import useWorkflowsEEStore from '@/stores/workflows.ee';
|
import { useWorkflowsEEStore } from '@/stores/workflows.ee';
|
||||||
|
import { ITelemetryTrackProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
export default mixins(showMessage).extend({
|
export default mixins(showMessage).extend({
|
||||||
name: 'workflow-share-modal',
|
name: 'workflow-share-modal',
|
||||||
@@ -248,12 +249,26 @@ export default mixins(showMessage).extend({
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const shareesAdded = this.sharedWith.filter(
|
||||||
|
(sharee) =>
|
||||||
|
!this.workflow.sharedWith?.find((previousSharee) => sharee.id === previousSharee.id),
|
||||||
|
);
|
||||||
|
const shareesRemoved =
|
||||||
|
this.workflow.sharedWith?.filter(
|
||||||
|
(previousSharee) => !this.sharedWith.find((sharee) => sharee.id === previousSharee.id),
|
||||||
|
) || [];
|
||||||
|
|
||||||
const workflowId = await saveWorkflowPromise();
|
const workflowId = await saveWorkflowPromise();
|
||||||
await this.workflowsEEStore.saveWorkflowSharedWith({
|
await this.workflowsEEStore.saveWorkflowSharedWith({
|
||||||
workflowId,
|
workflowId,
|
||||||
sharedWith: this.sharedWith,
|
sharedWith: this.sharedWith,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.trackTelemetry({
|
||||||
|
user_ids_sharees_added: shareesAdded.map((sharee) => sharee.id),
|
||||||
|
sharees_removed: shareesRemoved.length,
|
||||||
|
});
|
||||||
|
|
||||||
this.$showMessage({
|
this.$showMessage({
|
||||||
title: this.$locale.baseText('workflows.shareModal.onSave.success.title'),
|
title: this.$locale.baseText('workflows.shareModal.onSave.success.title'),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@@ -270,6 +285,10 @@ export default mixins(showMessage).extend({
|
|||||||
const sharee = { id, firstName, lastName, email };
|
const sharee = { id, firstName, lastName, email };
|
||||||
|
|
||||||
this.sharedWith = this.sharedWith.concat(sharee);
|
this.sharedWith = this.sharedWith.concat(sharee);
|
||||||
|
|
||||||
|
this.trackTelemetry({
|
||||||
|
user_id_sharee: userId,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
async onRemoveSharee(userId: string) {
|
async onRemoveSharee(userId: string) {
|
||||||
const user = this.usersStore.getUserById(userId)!;
|
const user = this.usersStore.getUserById(userId)!;
|
||||||
@@ -348,6 +367,11 @@ export default mixins(showMessage).extend({
|
|||||||
this.sharedWith = this.sharedWith.filter((sharee: Partial<IUser>) => {
|
this.sharedWith = this.sharedWith.filter((sharee: Partial<IUser>) => {
|
||||||
return sharee.id !== user.id;
|
return sharee.id !== user.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.trackTelemetry({
|
||||||
|
user_id_sharee: userId,
|
||||||
|
warning_orphan_credentials: isLastUserWithAccessToCredentials,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRoleAction(user: IUser, action: string) {
|
onRoleAction(user: IUser, action: string) {
|
||||||
@@ -379,6 +403,14 @@ export default mixins(showMessage).extend({
|
|||||||
this.$router.push({ name: VIEWS.USERS_SETTINGS });
|
this.$router.push({ name: VIEWS.USERS_SETTINGS });
|
||||||
this.modalBus.$emit('close');
|
this.modalBus.$emit('close');
|
||||||
},
|
},
|
||||||
|
trackTelemetry(data: ITelemetryTrackProperties) {
|
||||||
|
this.$telemetry.track('User selected sharee to remove', {
|
||||||
|
workflow_id: this.workflow.id,
|
||||||
|
user_id_sharer: this.currentUser?.id,
|
||||||
|
sub_view: this.$route.name === VIEWS.WORKFLOWS ? 'Workflows listing' : 'Workflow editor',
|
||||||
|
...data,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.isSharingAvailable) {
|
if (this.isSharingAvailable) {
|
||||||
|
|||||||
@@ -66,8 +66,10 @@ import { IWorkflowSettings } from 'n8n-workflow';
|
|||||||
import { useNDVStore } from '@/stores/ndv';
|
import { useNDVStore } from '@/stores/ndv';
|
||||||
import { useTemplatesStore } from '@/stores/templates';
|
import { useTemplatesStore } from '@/stores/templates';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes';
|
import { useNodeTypesStore } from '@/stores/nodeTypes';
|
||||||
import { useWorkflowsEEStore } from "@/stores/workflows.ee";
|
import { useUsersStore } from '@/stores/users';
|
||||||
import { useUsersStore } from "@/stores/users";
|
import { useWorkflowsEEStore } from '@/stores/workflows.ee';
|
||||||
|
import { ICredentialMap, ICredentialsResponse, IUsedCredential } from '@/Interface';
|
||||||
|
import { getWorkflowPermissions, IPermissions } from '@/permissions';
|
||||||
import { ICredentialsResponse } from '@/Interface';
|
import { ICredentialsResponse } from '@/Interface';
|
||||||
|
|
||||||
let cachedWorkflowKey: string | null = '';
|
let cachedWorkflowKey: string | null = '';
|
||||||
@@ -85,6 +87,9 @@ export const workflowHelpers = mixins(externalHooks, nodeHelpers, restApi, showM
|
|||||||
useUsersStore,
|
useUsersStore,
|
||||||
useUIStore,
|
useUIStore,
|
||||||
),
|
),
|
||||||
|
workflowPermissions(): IPermissions {
|
||||||
|
return getWorkflowPermissions(this.usersStore.currentUser, this.workflowsStore.workflow);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
executeData(
|
executeData(
|
||||||
@@ -827,7 +832,15 @@ export const workflowHelpers = mixins(externalHooks, nodeHelpers, restApi, showM
|
|||||||
this.uiStore.removeActiveAction('workflowSaving');
|
this.uiStore.removeActiveAction('workflowSaving');
|
||||||
|
|
||||||
if (error.errorCode === 100) {
|
if (error.errorCode === 100) {
|
||||||
const url = this.$router.resolve({ name: VIEWS.WORKFLOW, params: { name: currentWorkflow }}).href;
|
this.$telemetry.track('User attempted to save locked workflow', {
|
||||||
|
workflowId: currentWorkflow,
|
||||||
|
sharing_role: this.workflowPermissions.isOwner ? 'owner' : 'sharee',
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = this.$router.resolve({
|
||||||
|
name: VIEWS.WORKFLOW,
|
||||||
|
params: { name: currentWorkflow },
|
||||||
|
}).href;
|
||||||
|
|
||||||
const overwrite = await this.confirmMessage(
|
const overwrite = await this.confirmMessage(
|
||||||
this.$locale.baseText('workflows.concurrentChanges.confirmMessage.message', {
|
this.$locale.baseText('workflows.concurrentChanges.confirmMessage.message', {
|
||||||
|
|||||||
Reference in New Issue
Block a user