mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08: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:
parent
9956547504
commit
ac066fc9f3
|
@ -953,6 +953,8 @@ export interface IUsedCredential {
|
|||
name: string;
|
||||
credentialType: string;
|
||||
currentUserHasAccess: boolean;
|
||||
ownedBy: Partial<IUser>;
|
||||
sharedWith: Array<Partial<IUser>>;
|
||||
}
|
||||
|
||||
export interface WorkflowsState {
|
||||
|
|
|
@ -62,6 +62,8 @@ import { mapStores } from 'pinia';
|
|||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { useWorkflowsStore } from '@/stores/workflows';
|
||||
import { IWorkflowDataUpdate } from '@/Interface';
|
||||
import { getWorkflowPermissions, IPermissions } from '@/permissions';
|
||||
import { useUsersStore } from '@/stores/users';
|
||||
|
||||
export default mixins(showMessage, workflowHelpers, restApi).extend({
|
||||
components: { TagsDropdown, Modal },
|
||||
|
@ -85,7 +87,13 @@ export default mixins(showMessage, workflowHelpers, restApi).extend({
|
|||
this.$nextTick(() => this.focusOnNameInput());
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useSettingsStore, useWorkflowsStore),
|
||||
...mapStores(useUsersStore, useSettingsStore, useWorkflowsStore),
|
||||
workflowPermissions(): IPermissions {
|
||||
return getWorkflowPermissions(
|
||||
this.usersStore.currentUser,
|
||||
this.workflowsStore.getWorkflowById(this.data.id),
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isActive(active) {
|
||||
|
@ -157,6 +165,7 @@ export default mixins(showMessage, workflowHelpers, restApi).extend({
|
|||
this.$telemetry.track('User duplicated workflow', {
|
||||
old_workflow_id: currentWorkflowId,
|
||||
workflow_id: this.data.id,
|
||||
sharing_role: this.workflowPermissions.isOwner ? 'owner' : 'sharee',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
@ -139,7 +139,13 @@ import SaveButton from '@/components/SaveButton.vue';
|
|||
import TagsDropdown from '@/components/TagsDropdown.vue';
|
||||
import InlineTextEdit from '@/components/InlineTextEdit.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 { titleChange } from '@/mixins/titleChange';
|
||||
|
@ -194,6 +200,9 @@ export default mixins(workflowHelpers, titleChange).extend({
|
|||
useWorkflowsStore,
|
||||
useUsersStore,
|
||||
),
|
||||
currentUser(): IUser | null {
|
||||
return this.usersStore.currentUser;
|
||||
},
|
||||
dynamicTranslations(): NestedRecord<string> {
|
||||
return this.uiStore.dynamicTranslations;
|
||||
},
|
||||
|
@ -302,6 +311,12 @@ export default mixins(workflowHelpers, titleChange).extend({
|
|||
name: WORKFLOW_SHARE_MODAL_KEY,
|
||||
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() {
|
||||
this.$data.appliedTagIds = this.currentWorkflowTagIds;
|
||||
|
|
|
@ -421,6 +421,7 @@ export default mixins(
|
|||
node_type: this.activeNodeType ? this.activeNodeType.name : '',
|
||||
workflow_id: this.workflowsStore.workflowId,
|
||||
session_id: this.sessionId,
|
||||
is_editable: !this.hasForeignCredential,
|
||||
parameters_pane_position: this.mainPanelPosition,
|
||||
input_first_connector_runs: this.maxInputRun,
|
||||
output_first_connector_runs: this.maxOutputRun,
|
||||
|
|
|
@ -206,6 +206,12 @@ export default mixins(showMessage, restApi).extend({
|
|||
name: WORKFLOW_SHARE_MODAL_KEY,
|
||||
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) {
|
||||
const deleteConfirmed = await this.confirmMessage(
|
||||
this.$locale.baseText('mainSidebar.confirmMessage.workflowDelete.message', {
|
||||
|
|
|
@ -132,7 +132,8 @@ import { useSettingsStore } from '@/stores/settings';
|
|||
import { useUIStore } from '@/stores/ui';
|
||||
import { useUsersStore } from '@/stores/users';
|
||||
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({
|
||||
name: 'workflow-share-modal',
|
||||
|
@ -248,12 +249,26 @@ export default mixins(showMessage).extend({
|
|||
};
|
||||
|
||||
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();
|
||||
await this.workflowsEEStore.saveWorkflowSharedWith({
|
||||
workflowId,
|
||||
sharedWith: this.sharedWith,
|
||||
});
|
||||
|
||||
this.trackTelemetry({
|
||||
user_ids_sharees_added: shareesAdded.map((sharee) => sharee.id),
|
||||
sharees_removed: shareesRemoved.length,
|
||||
});
|
||||
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText('workflows.shareModal.onSave.success.title'),
|
||||
type: 'success',
|
||||
|
@ -270,6 +285,10 @@ export default mixins(showMessage).extend({
|
|||
const sharee = { id, firstName, lastName, email };
|
||||
|
||||
this.sharedWith = this.sharedWith.concat(sharee);
|
||||
|
||||
this.trackTelemetry({
|
||||
user_id_sharee: userId,
|
||||
});
|
||||
},
|
||||
async onRemoveSharee(userId: string) {
|
||||
const user = this.usersStore.getUserById(userId)!;
|
||||
|
@ -348,6 +367,11 @@ export default mixins(showMessage).extend({
|
|||
this.sharedWith = this.sharedWith.filter((sharee: Partial<IUser>) => {
|
||||
return sharee.id !== user.id;
|
||||
});
|
||||
|
||||
this.trackTelemetry({
|
||||
user_id_sharee: userId,
|
||||
warning_orphan_credentials: isLastUserWithAccessToCredentials,
|
||||
});
|
||||
}
|
||||
},
|
||||
onRoleAction(user: IUser, action: string) {
|
||||
|
@ -379,6 +403,14 @@ export default mixins(showMessage).extend({
|
|||
this.$router.push({ name: VIEWS.USERS_SETTINGS });
|
||||
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() {
|
||||
if (this.isSharingAvailable) {
|
||||
|
|
|
@ -66,8 +66,10 @@ import { IWorkflowSettings } from 'n8n-workflow';
|
|||
import { useNDVStore } from '@/stores/ndv';
|
||||
import { useTemplatesStore } from '@/stores/templates';
|
||||
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';
|
||||
|
||||
let cachedWorkflowKey: string | null = '';
|
||||
|
@ -85,6 +87,9 @@ export const workflowHelpers = mixins(externalHooks, nodeHelpers, restApi, showM
|
|||
useUsersStore,
|
||||
useUIStore,
|
||||
),
|
||||
workflowPermissions(): IPermissions {
|
||||
return getWorkflowPermissions(this.usersStore.currentUser, this.workflowsStore.workflow);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
executeData(
|
||||
|
@ -827,7 +832,15 @@ export const workflowHelpers = mixins(externalHooks, nodeHelpers, restApi, showM
|
|||
this.uiStore.removeActiveAction('workflowSaving');
|
||||
|
||||
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(
|
||||
this.$locale.baseText('workflows.concurrentChanges.confirmMessage.message', {
|
||||
|
|
Loading…
Reference in a new issue