Introduce telemetry (#2099)

* introduce analytics

* add user survey backend

* add user survey backend

* set answers on survey submit

Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com>

* change name to personalization

* lint

Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com>

* N8n 2495 add personalization modal (#2280)

* update modals

* add onboarding modal

* implement questions

* introduce analytics

* simplify impl

* implement survey handling

* add personalized cateogry

* update modal behavior

* add thank you view

* handle empty cases

* rename modal

* standarize modal names

* update image, add tags to headings

* remove unused file

* remove unused interfaces

* clean up footer spacing

* introduce analytics

* refactor to fix bug

* update endpoint

* set min height

* update stories

* update naming from questions to survey

* remove spacing after core categories

* fix bug in logic

* sort nodes

* rename types

* merge with be

* rename userSurvey

* clean up rest api

* use constants for keys

* use survey keys

* clean up types

* move personalization to its own file

Co-authored-by: ahsan-virani <ahsan.virani@gmail.com>

* Survey new options (#2300)

* split up options

* fix quotes

* remove unused import

* add user created workflow event (#2301)

* simplify env vars

* fix versionCli on FE

* update personalization env

* fix event User opened Credentials panel

* fix select modal spacing

* fix nodes panel event

* fix workflow id in workflow execute event

* improve telemetry error logging

* fix config and stop process events

* add flush call on n8n stop

* ready for release

* improve telemetry process exit

* fix merge

* improve n8n stop events

Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com>
Co-authored-by: Mutasem <mutdmour@gmail.com>
Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
Ahsan Virani
2021-10-19 05:57:49 +02:00
committed by GitHub
parent 4b857b19ac
commit 421dd72224
100 changed files with 2223 additions and 550 deletions

View File

@@ -87,7 +87,6 @@ export default Vue.extend({
opacity: 1;
}
.subcategory + .category,
.node + .category {
margin-top: 15px;
}

View File

@@ -89,7 +89,6 @@ export default mixins(externalHooks).extend({
const nodeTypes: INodeCreateElement[] = this.searchItems;
const filter = this.searchFilter;
const returnData = nodeTypes.filter((el: INodeCreateElement) => {
const nodeType = (el.properties as INodeItemProps).nodeType;
return filter && matchesSelectType(el, this.selectedType) && matchesNodeType(el, filter);
});
@@ -152,12 +151,24 @@ export default mixins(externalHooks).extend({
selectedType: this.selectedType,
filteredNodes: this.filteredNodeTypes,
});
this.$telemetry.trackNodesPanel('nodeCreateList.nodeFilterChanged', {
oldValue,
newValue,
selectedType: this.selectedType,
filteredNodes: this.filteredNodeTypes,
workflow_id: this.$store.getters.workflowId,
});
},
selectedType(newValue, oldValue) {
this.$externalHooks().run('nodeCreateList.selectedTypeChanged', {
oldValue,
newValue,
});
this.$telemetry.trackNodesPanel('nodeCreateList.selectedTypeChanged', {
old_filter: oldValue,
new_filter: newValue,
workflow_id: this.$store.getters.workflowId,
});
},
},
methods: {
@@ -243,6 +254,7 @@ export default mixins(externalHooks).extend({
);
} else {
this.activeCategory = [...this.activeCategory, category];
this.$telemetry.trackNodesPanel('nodeCreateList.onCategoryExpanded', { category_name: category, workflow_id: this.$store.getters.workflowId });
}
this.activeIndex = this.categorized.findIndex(
@@ -252,6 +264,7 @@ export default mixins(externalHooks).extend({
onSubcategorySelected(selected: INodeCreateElement) {
this.activeSubcategoryIndex = 0;
this.activeSubcategory = selected;
this.$telemetry.trackNodesPanel('nodeCreateList.onSubcategorySelected', { selected, workflow_id: this.$store.getters.workflowId });
},
onSubcategoryClose() {
@@ -273,6 +286,7 @@ export default mixins(externalHooks).extend({
},
async destroyed() {
this.$externalHooks().run('nodeCreateList.destroyed');
this.$telemetry.trackNodesPanel('nodeCreateList.destroyed', { workflow_id: this.$store.getters.workflowId });
},
});
</script>

View File

@@ -35,7 +35,7 @@
<script lang="ts">
import { HTTP_REQUEST_NODE_NAME, REQUEST_NODE_FORM_URL, WEBHOOK_NODE_NAME } from '@/constants';
import { HTTP_REQUEST_NODE_TYPE, REQUEST_NODE_FORM_URL, WEBHOOK_NODE_TYPE } from '@/constants';
import Vue from 'vue';
import NoResultsIcon from './NoResultsIcon.vue';
@@ -57,11 +57,11 @@ export default Vue.extend({
},
methods: {
selectWebhook() {
this.$emit('nodeTypeSelected', WEBHOOK_NODE_NAME);
this.$emit('nodeTypeSelected', WEBHOOK_NODE_TYPE);
},
selectHttpRequest() {
this.$emit('nodeTypeSelected', HTTP_REQUEST_NODE_NAME);
this.$emit('nodeTypeSelected', HTTP_REQUEST_NODE_TYPE);
},
},
});

View File

@@ -19,6 +19,7 @@ import { HIDDEN_NODES } from '@/constants';
import MainPanel from './MainPanel.vue';
import { getCategoriesWithNodes, getCategorizedList } from './helpers';
import { mapGetters } from 'vuex';
export default Vue.extend({
name: 'NodeCreator',
@@ -35,6 +36,7 @@ export default Vue.extend({
};
},
computed: {
...mapGetters('settings', ['personalizedNodeTypes']),
nodeTypes(): INodeTypeDescription[] {
return this.$store.getters.allNodeTypes;
},
@@ -57,7 +59,7 @@ export default Vue.extend({
}, []);
},
categoriesWithNodes(): ICategoriesWithNodes {
return getCategoriesWithNodes(this.visibleNodeTypes);
return getCategoriesWithNodes(this.visibleNodeTypes, this.personalizedNodeTypes as string[]);
},
categorizedItems(): INodeCreateElement[] {
return getCategorizedList(this.categoriesWithNodes);

View File

@@ -1,25 +1,51 @@
import { CORE_NODES_CATEGORY, CUSTOM_NODES_CATEGORY, SUBCATEGORY_DESCRIPTIONS, UNCATEGORIZED_CATEGORY, UNCATEGORIZED_SUBCATEGORY, REGULAR_NODE_FILTER, TRIGGER_NODE_FILTER, ALL_NODE_FILTER } from '@/constants';
import { CORE_NODES_CATEGORY, CUSTOM_NODES_CATEGORY, SUBCATEGORY_DESCRIPTIONS, UNCATEGORIZED_CATEGORY, UNCATEGORIZED_SUBCATEGORY, REGULAR_NODE_FILTER, TRIGGER_NODE_FILTER, ALL_NODE_FILTER, PERSONALIZED_CATEGORY } from '@/constants';
import { INodeCreateElement, ICategoriesWithNodes, INodeItemProps } from '@/Interface';
import { INodeTypeDescription } from 'n8n-workflow';
const addNodeToCategory = (accu: ICategoriesWithNodes, nodeType: INodeTypeDescription, category: string, subcategory: string) => {
if (!accu[category]) {
accu[category] = {};
}
if (!accu[category][subcategory]) {
accu[category][subcategory] = {
triggerCount: 0,
regularCount: 0,
nodes: [],
};
}
const isTrigger = nodeType.group.includes('trigger');
if (isTrigger) {
accu[category][subcategory].triggerCount++;
}
if (!isTrigger) {
accu[category][subcategory].regularCount++;
}
accu[category][subcategory].nodes.push({
type: 'node',
key: `${category}_${nodeType.name}`,
category,
properties: {
nodeType,
subcategory,
},
includedByTrigger: isTrigger,
includedByRegular: !isTrigger,
});
};
export const getCategoriesWithNodes = (nodeTypes: INodeTypeDescription[]): ICategoriesWithNodes => {
return nodeTypes.reduce(
export const getCategoriesWithNodes = (nodeTypes: INodeTypeDescription[], personalizedNodeTypes: string[]): ICategoriesWithNodes => {
const sorted = [...nodeTypes].sort((a: INodeTypeDescription, b: INodeTypeDescription) => a.displayName > b.displayName? 1 : -1);
return sorted.reduce(
(accu: ICategoriesWithNodes, nodeType: INodeTypeDescription) => {
if (personalizedNodeTypes.includes(nodeType.name)) {
addNodeToCategory(accu, nodeType, PERSONALIZED_CATEGORY, UNCATEGORIZED_SUBCATEGORY);
}
if (!nodeType.codex || !nodeType.codex.categories) {
accu[UNCATEGORIZED_CATEGORY][UNCATEGORIZED_SUBCATEGORY].nodes.push({
type: 'node',
category: UNCATEGORIZED_CATEGORY,
key: `${UNCATEGORIZED_CATEGORY}_${nodeType.name}`,
properties: {
subcategory: UNCATEGORIZED_SUBCATEGORY,
nodeType,
},
includedByTrigger: nodeType.group.includes('trigger'),
includedByRegular: !nodeType.group.includes('trigger'),
});
addNodeToCategory(accu, nodeType, UNCATEGORIZED_CATEGORY, UNCATEGORIZED_SUBCATEGORY);
return accu;
}
nodeType.codex.categories.forEach((_category: string) => {
const category = _category.trim();
const subcategory =
@@ -28,58 +54,25 @@ export const getCategoriesWithNodes = (nodeTypes: INodeTypeDescription[]): ICate
nodeType.codex.subcategories[category]
? nodeType.codex.subcategories[category][0]
: UNCATEGORIZED_SUBCATEGORY;
if (!accu[category]) {
accu[category] = {};
}
if (!accu[category][subcategory]) {
accu[category][subcategory] = {
triggerCount: 0,
regularCount: 0,
nodes: [],
};
}
const isTrigger = nodeType.group.includes('trigger');
if (isTrigger) {
accu[category][subcategory].triggerCount++;
}
if (!isTrigger) {
accu[category][subcategory].regularCount++;
}
accu[category][subcategory].nodes.push({
type: 'node',
key: `${category}_${nodeType.name}`,
category,
properties: {
nodeType,
subcategory,
},
includedByTrigger: isTrigger,
includedByRegular: !isTrigger,
});
addNodeToCategory(accu, nodeType, category, subcategory);
});
return accu;
},
{
[UNCATEGORIZED_CATEGORY]: {
[UNCATEGORIZED_SUBCATEGORY]: {
triggerCount: 0,
regularCount: 0,
nodes: [],
},
},
},
{},
);
};
const getCategories = (categoriesWithNodes: ICategoriesWithNodes): string[] => {
const excludeFromSort = [CORE_NODES_CATEGORY, CUSTOM_NODES_CATEGORY, UNCATEGORIZED_CATEGORY, PERSONALIZED_CATEGORY];
const categories = Object.keys(categoriesWithNodes);
const sorted = categories.filter(
(category: string) =>
category !== CORE_NODES_CATEGORY && category !== CUSTOM_NODES_CATEGORY && category !== UNCATEGORIZED_CATEGORY,
!excludeFromSort.includes(category),
);
sorted.sort();
return [CORE_NODES_CATEGORY, CUSTOM_NODES_CATEGORY, ...sorted, UNCATEGORIZED_CATEGORY];
return [CORE_NODES_CATEGORY, CUSTOM_NODES_CATEGORY, PERSONALIZED_CATEGORY, ...sorted, UNCATEGORIZED_CATEGORY];
};
export const getCategorizedList = (categoriesWithNodes: ICategoriesWithNodes): INodeCreateElement[] => {
@@ -173,4 +166,4 @@ export const matchesNodeType = (el: INodeCreateElement, filter: string) => {
const nodeType = (el.properties as INodeItemProps).nodeType;
return nodeType.displayName.toLowerCase().indexOf(filter) !== -1 || matchesAlias(nodeType, filter);
};
};