feat: Add workflow sharing telemetry (#4906)

* feat: Add workflow sharing telemetry

* chore: fix linting issue

* fix: fix telemetry typo
This commit is contained in:
Alex Grozav 2022-12-15 10:05:54 +02:00 committed by GitHub
parent 9956547504
commit ac066fc9f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 84 additions and 6 deletions

View file

@ -953,6 +953,8 @@ export interface IUsedCredential {
name: string;
credentialType: string;
currentUserHasAccess: boolean;
ownedBy: Partial<IUser>;
sharedWith: Array<Partial<IUser>>;
}
export interface WorkflowsState {

View file

@ -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) {

View file

@ -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;

View file

@ -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,

View file

@ -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', {

View file

@ -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) {

View file

@ -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', {