fix(editor): Adjust Ask AI tracking events & pass NDV session id (no-changelog) (#7027)

Github issue / Community forum post (link here to close automatically):

---------

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
OlegIvaniv
2023-08-29 12:53:29 +02:00
committed by GitHub
parent a4578d68a5
commit 4600bb3317
7 changed files with 48 additions and 37 deletions

View File

@@ -110,23 +110,17 @@ describe('Code node', () => {
statusCode: 200, statusCode: 200,
body: { body: {
data: { data: {
code: 'console.log("Hello World")', code: 'console.log("Hello World")'
usage: {
prompt_tokens: 15,
completion_tokens: 15,
total_tokens: 30
}
}, },
} }
}).as('ask-ai'); }).as('ask-ai');
cy.getByTestId('ask-ai-cta').click(); cy.getByTestId('ask-ai-cta').click();
cy.wait('@ask-ai') const askAiReq = cy.wait('@ask-ai')
.its('request.body')
.should('deep.include', { askAiReq.its('request.body').should('have.keys', ['question', 'model', 'context', 'n8nVersion']);
question: prompt,
model: "gpt-3.5-turbo-16k", askAiReq.its('context').should('have.keys', ['schema', 'ndvSessionId', 'sessionId']);
context: { schema: [] }
});
cy.contains('Code generation completed').should('be.visible') cy.contains('Code generation completed').should('be.visible')
cy.getByTestId('code-node-tab-code').should('contain.text', 'console.log("Hello World")'); cy.getByTestId('code-node-tab-code').should('contain.text', 'console.log("Hello World")');

View File

@@ -2,12 +2,6 @@ import type { IRestApiContext, Schema } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils'; import { makeRestApiRequest } from '@/utils/apiUtils';
import type { IDataObject } from 'n8n-workflow'; import type { IDataObject } from 'n8n-workflow';
type Usage = {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
export async function generateCodeForPrompt( export async function generateCodeForPrompt(
ctx: IRestApiContext, ctx: IRestApiContext,
{ {
@@ -20,11 +14,13 @@ export async function generateCodeForPrompt(
context: { context: {
schema: Array<{ nodeName: string; schema: Schema }>; schema: Array<{ nodeName: string; schema: Schema }>;
inputSchema: { nodeName: string; schema: Schema }; inputSchema: { nodeName: string; schema: Schema };
sessionId: string;
ndvSessionId: string;
}; };
model: string; model: string;
n8nVersion: string; n8nVersion: string;
}, },
): Promise<{ code: string; usage: Usage }> { ): Promise<{ code: string }> {
return makeRestApiRequest(ctx, 'POST', '/ask-ai', { return makeRestApiRequest(ctx, 'POST', '/ask-ai', {
question, question,
context, context,

View File

@@ -9,7 +9,7 @@ import type { CodeExecutionMode, INodeExecutionData } from 'n8n-workflow';
import type { BaseTextKey } from '@/plugins/i18n'; import type { BaseTextKey } from '@/plugins/i18n';
import type { INodeUi, Schema } from '@/Interface'; import type { INodeUi, Schema } from '@/Interface';
import { generateCodeForPrompt } from '@/api/ai'; import { generateCodeForPrompt } from '@/api/ai';
import { useDataSchema, useI18n, useMessage, useToast, useTelemetry } from '@/composables'; import { useDataSchema, useI18n, useMessage, useTelemetry, useToast } from '@/composables';
import { useNDVStore, usePostHog, useRootStore, useWorkflowsStore } from '@/stores'; import { useNDVStore, usePostHog, useRootStore, useWorkflowsStore } from '@/stores';
import { executionDataToJson } from '@/utils'; import { executionDataToJson } from '@/utils';
import { import {
@@ -131,10 +131,6 @@ async function onSubmit() {
if (!activeNode) return; if (!activeNode) return;
const schemas = getSchemas(); const schemas = getSchemas();
useTelemetry().trackAskAI('ask.generationClicked', {
prompt: prompt.value,
});
if (props.hasChanges) { if (props.hasChanges) {
const confirmModal = await alert(i18n.baseText('codeNodeEditor.askAi.areYouSureToReplace'), { const confirmModal = await alert(i18n.baseText('codeNodeEditor.askAi.areYouSureToReplace'), {
title: i18n.baseText('codeNodeEditor.askAi.replaceCurrentCode'), title: i18n.baseText('codeNodeEditor.askAi.replaceCurrentCode'),
@@ -157,9 +153,14 @@ async function onSubmit() {
? 'gpt-4' ? 'gpt-4'
: 'gpt-3.5-turbo-16k'; : 'gpt-3.5-turbo-16k';
const { code, usage } = await generateCodeForPrompt(getRestApiContext, { const { code } = await generateCodeForPrompt(getRestApiContext, {
question: prompt.value, question: prompt.value,
context: { schema: schemas.parentNodesSchemas, inputSchema: schemas.inputSchema! }, context: {
schema: schemas.parentNodesSchemas,
inputSchema: schemas.inputSchema!,
ndvSessionId: useNDVStore().sessionId,
sessionId: useRootStore().sessionId,
},
model, model,
n8nVersion: version, n8nVersion: version,
}); });
@@ -170,6 +171,10 @@ async function onSubmit() {
type: 'success', type: 'success',
title: i18n.baseText('codeNodeEditor.askAi.generationCompleted'), title: i18n.baseText('codeNodeEditor.askAi.generationCompleted'),
}); });
useTelemetry().trackAskAI('askAi.generationFinished', {
prompt: prompt.value,
code,
});
} catch (error) { } catch (error) {
showMessage({ showMessage({
type: 'error', type: 'error',
@@ -177,6 +182,11 @@ async function onSubmit() {
message: getErrorMessageByStatusCode(error.httpStatusCode || error?.response.status), message: getErrorMessageByStatusCode(error.httpStatusCode || error?.response.status),
}); });
stopLoading(); stopLoading();
useTelemetry().trackAskAI('askAi.generationFinished', {
prompt: prompt.value,
code: '',
hasError: true,
});
} }
} }
function triggerLoadingChange() { function triggerLoadingChange() {

View File

@@ -99,10 +99,10 @@ export default defineComponent({
return Boolean(this.nodeType && this.nodeType.name === MANUAL_TRIGGER_NODE_TYPE); return Boolean(this.nodeType && this.nodeType.name === MANUAL_TRIGGER_NODE_TYPE);
}, },
isPollingTypeNode(): boolean { isPollingTypeNode(): boolean {
return !!(this.nodeType && this.nodeType.polling); return !!this.nodeType?.polling;
}, },
isScheduleTrigger(): boolean { isScheduleTrigger(): boolean {
return !!(this.nodeType && this.nodeType.group.includes('schedule')); return !!this.nodeType?.group.includes('schedule');
}, },
isWebhookNode(): boolean { isWebhookNode(): boolean {
return Boolean(this.nodeType && this.nodeType.name === WEBHOOK_NODE_TYPE); return Boolean(this.nodeType && this.nodeType.name === WEBHOOK_NODE_TYPE);
@@ -129,9 +129,7 @@ export default defineComponent({
}, },
hasIssues(): boolean { hasIssues(): boolean {
return Boolean( return Boolean(
this.node && this.node?.issues && (this.node.issues.parameters || this.node.issues.credentials),
this.node.issues &&
(this.node.issues.parameters || this.node.issues.credentials),
); );
}, },
disabledHint(): string { disabledHint(): string {
@@ -171,7 +169,7 @@ export default defineComponent({
return this.$locale.baseText('ndv.execute.listenForTestEvent'); return this.$locale.baseText('ndv.execute.listenForTestEvent');
} }
if (this.isPollingTypeNode || (this.nodeType && this.nodeType.mockManualExecution)) { if (this.isPollingTypeNode || this.nodeType?.mockManualExecution) {
return this.$locale.baseText('ndv.execute.fetchEvent'); return this.$locale.baseText('ndv.execute.fetchEvent');
} }
@@ -221,6 +219,7 @@ export default defineComponent({
node_type: this.nodeType ? this.nodeType.name : null, node_type: this.nodeType ? this.nodeType.name : null,
workflow_id: this.workflowsStore.workflowId, workflow_id: this.workflowsStore.workflowId,
source: this.telemetrySource, source: this.telemetrySource,
session_id: this.ndvStore.sessionId,
}; };
this.$telemetry.track('User clicked execute node button', telemetryPayload); this.$telemetry.track('User clicked execute node button', telemetryPayload);
await this.$externalHooks().run('nodeExecuteButton.onClick', telemetryPayload); await this.$externalHooks().run('nodeExecuteButton.onClick', telemetryPayload);

View File

@@ -9,6 +9,7 @@ import { useRootStore } from '@/stores/n8nRoot.store';
import { useTelemetryStore } from '@/stores/telemetry.store'; import { useTelemetryStore } from '@/stores/telemetry.store';
import { SLACK_NODE_TYPE } from '@/constants'; import { SLACK_NODE_TYPE } from '@/constants';
import { usePostHog } from '@/stores/posthog.store'; import { usePostHog } from '@/stores/posthog.store';
import { useNDVStore } from '@/stores';
export class Telemetry { export class Telemetry {
private pageEventQueue: Array<{ route: RouteLocation }>; private pageEventQueue: Array<{ route: RouteLocation }>;
@@ -134,9 +135,11 @@ export class Telemetry {
trackAskAI(event: string, properties: IDataObject = {}) { trackAskAI(event: string, properties: IDataObject = {}) {
if (this.rudderStack) { if (this.rudderStack) {
properties.session_id = useRootStore().sessionId; properties.session_id = useRootStore().sessionId;
properties.ndv_session_id = useNDVStore().sessionId;
switch (event) { switch (event) {
case 'ask.generationClicked': case 'askAi.generationFinished':
this.track('User clicked on generate code button', properties, { withPostHog: true }); this.track('Ai code generation finished', properties, { withPostHog: true });
default: default:
break; break;
} }
@@ -222,7 +225,14 @@ export class Telemetry {
switch (nodeType) { switch (nodeType) {
case SLACK_NODE_TYPE: case SLACK_NODE_TYPE:
if (change.name === 'parameters.otherOptions.includeLinkToWorkflow') { if (change.name === 'parameters.otherOptions.includeLinkToWorkflow') {
this.track('User toggled n8n reference option'); this.track(
'User toggled n8n reference option',
{
node: nodeType,
toValue: change.value,
},
{ withPostHog: true },
);
} }
break; break;

View File

@@ -9,6 +9,7 @@ import type {
} from '@/Interface'; } from '@/Interface';
import type { INodeIssues, IRunData } from 'n8n-workflow'; import type { INodeIssues, IRunData } from 'n8n-workflow';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { v4 as uuid } from 'uuid';
import { useWorkflowsStore } from './workflows.store'; import { useWorkflowsStore } from './workflows.store';
export const useNDVStore = defineStore(STORES.NDV, { export const useNDVStore = defineStore(STORES.NDV, {
@@ -163,7 +164,7 @@ export const useNDVStore = defineStore(STORES.NDV, {
}; };
}, },
setNDVSessionId(): void { setNDVSessionId(): void {
this.sessionId = `ndv-${Math.random().toString(36).slice(-8)}`; this.sessionId = `ndv-${uuid()}`;
}, },
resetNDVSessionId(): void { resetNDVSessionId(): void {
this.sessionId = ''; this.sessionId = '';

View File

@@ -680,6 +680,7 @@ export default defineComponent({
node_type: node ? node.type : null, node_type: node ? node.type : null,
workflow_id: this.workflowsStore.workflowId, workflow_id: this.workflowsStore.workflowId,
source: 'canvas', source: 'canvas',
session_id: this.ndvStore.sessionId,
}; };
this.$telemetry.track('User clicked execute node button', telemetryPayload); this.$telemetry.track('User clicked execute node button', telemetryPayload);
void this.$externalHooks().run('nodeView.onRunNode', telemetryPayload); void this.$externalHooks().run('nodeView.onRunNode', telemetryPayload);