mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-29 15:57:00 +02:00
chore(ai-builder): Rollout builder experiment, removing PostHog flags (#21079)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
4698b93a5a
commit
9fc867ce47
|
|
@ -49,28 +49,18 @@ export class AiWorkflowBuilderService {
|
|||
});
|
||||
}
|
||||
|
||||
private async getApiProxyAuthHeaders(user: IUser, useDeprecatedCredentials = false) {
|
||||
private async getApiProxyAuthHeaders(user: IUser) {
|
||||
assert(this.client);
|
||||
|
||||
let authHeaders: { Authorization: string };
|
||||
|
||||
if (useDeprecatedCredentials) {
|
||||
const authResponse = await this.client.generateApiProxyCredentials(user);
|
||||
authHeaders = { Authorization: authResponse.apiKey };
|
||||
} else {
|
||||
const authResponse = await this.client.getBuilderApiProxyToken(user);
|
||||
authHeaders = {
|
||||
Authorization: `${authResponse.tokenType} ${authResponse.accessToken}`,
|
||||
};
|
||||
}
|
||||
const authResponse = await this.client.getBuilderApiProxyToken(user);
|
||||
const authHeaders = {
|
||||
Authorization: `${authResponse.tokenType} ${authResponse.accessToken}`,
|
||||
};
|
||||
|
||||
return authHeaders;
|
||||
}
|
||||
|
||||
private async setupModels(
|
||||
user: IUser,
|
||||
useDeprecatedCredentials = false,
|
||||
): Promise<{
|
||||
private async setupModels(user: IUser): Promise<{
|
||||
anthropicClaude: ChatAnthropic;
|
||||
tracingClient?: TracingClient;
|
||||
authHeaders?: { Authorization: string };
|
||||
|
|
@ -78,7 +68,7 @@ export class AiWorkflowBuilderService {
|
|||
try {
|
||||
// If client is provided, use it for API proxy
|
||||
if (this.client) {
|
||||
const authHeaders = await this.getApiProxyAuthHeaders(user, useDeprecatedCredentials);
|
||||
const authHeaders = await this.getApiProxyAuthHeaders(user);
|
||||
|
||||
// Extract baseUrl from client configuration
|
||||
const baseUrl = this.client.getApiProxyBaseUrl();
|
||||
|
|
@ -154,11 +144,8 @@ export class AiWorkflowBuilderService {
|
|||
});
|
||||
}
|
||||
|
||||
private async getAgent(user: IUser, useDeprecatedCredentials = false) {
|
||||
const { anthropicClaude, tracingClient, authHeaders } = await this.setupModels(
|
||||
user,
|
||||
useDeprecatedCredentials,
|
||||
);
|
||||
private async getAgent(user: IUser) {
|
||||
const { anthropicClaude, tracingClient, authHeaders } = await this.setupModels(user);
|
||||
|
||||
const agent = new WorkflowBuilderAgent({
|
||||
parsedNodeTypes: this.parsedNodeTypes,
|
||||
|
|
@ -172,9 +159,7 @@ export class AiWorkflowBuilderService {
|
|||
: undefined,
|
||||
instanceUrl: this.instanceUrl,
|
||||
onGenerationSuccess: async () => {
|
||||
if (!useDeprecatedCredentials) {
|
||||
await this.onGenerationSuccess(user, authHeaders);
|
||||
}
|
||||
await this.onGenerationSuccess(user, authHeaders);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -204,7 +189,7 @@ export class AiWorkflowBuilderService {
|
|||
}
|
||||
|
||||
async *chat(payload: ChatPayload, user: IUser, abortSignal?: AbortSignal) {
|
||||
const agent = await this.getAgent(user, payload.useDeprecatedCredentials);
|
||||
const agent = await this.getAgent(user);
|
||||
|
||||
for await (const output of agent.chat(payload, user?.id?.toString(), abortSignal)) {
|
||||
yield output;
|
||||
|
|
|
|||
|
|
@ -278,7 +278,6 @@ describe('AiWorkflowBuilderService', () => {
|
|||
workflowContext: {
|
||||
currentWorkflow: { id: 'test-workflow' },
|
||||
},
|
||||
useDeprecatedCredentials: false,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -308,19 +307,6 @@ describe('AiWorkflowBuilderService', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should handle deprecated credentials', async () => {
|
||||
const payloadWithDeprecatedCredentials = {
|
||||
...mockPayload,
|
||||
useDeprecatedCredentials: true,
|
||||
};
|
||||
|
||||
const generator = service.chat(payloadWithDeprecatedCredentials, mockUser);
|
||||
await generator.next();
|
||||
|
||||
// Verify that the deprecated credentials flow was used
|
||||
expect(mockClient.generateApiProxyCredentials).toHaveBeenCalledWith(mockUser);
|
||||
});
|
||||
|
||||
it('should create WorkflowBuilderAgent with correct configuration when using client', async () => {
|
||||
const generator = service.chat(mockPayload, mockUser);
|
||||
await generator.next();
|
||||
|
|
@ -439,24 +425,6 @@ describe('AiWorkflowBuilderService', () => {
|
|||
// Verify callback was called with correct parameters
|
||||
expect(mockOnCreditsUpdated).toHaveBeenCalledWith('test-user-id', 10, 1);
|
||||
});
|
||||
|
||||
it('should not call markBuilderSuccess when using deprecated credentials', async () => {
|
||||
const payloadWithDeprecatedCredentials = {
|
||||
...mockPayload,
|
||||
useDeprecatedCredentials: true,
|
||||
};
|
||||
|
||||
const generator = service.chat(payloadWithDeprecatedCredentials, mockUser);
|
||||
await generator.next();
|
||||
|
||||
const config = MockedWorkflowBuilderAgent.mock.calls[0][0];
|
||||
|
||||
// Call the onGenerationSuccess callback
|
||||
await config.onGenerationSuccess!();
|
||||
|
||||
// Should not call markBuilderSuccess for deprecated credentials
|
||||
expect(mockClient.markBuilderSuccess).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSessions', () => {
|
||||
|
|
@ -580,7 +548,6 @@ describe('AiWorkflowBuilderService', () => {
|
|||
workflowContext: {
|
||||
currentWorkflow: { id: workflowId },
|
||||
},
|
||||
useDeprecatedCredentials: false,
|
||||
};
|
||||
|
||||
// First, simulate a chat session
|
||||
|
|
|
|||
|
|
@ -143,7 +143,6 @@ describe('WorkflowBuilderAgent', () => {
|
|||
workflowContext: {
|
||||
currentWorkflow: { id: 'workflow-123' },
|
||||
},
|
||||
useDeprecatedCredentials: false,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -151,7 +150,6 @@ describe('WorkflowBuilderAgent', () => {
|
|||
const longMessage = 'x'.repeat(MAX_AI_BUILDER_PROMPT_LENGTH + 1);
|
||||
const payload: ChatPayload = {
|
||||
message: longMessage,
|
||||
useDeprecatedCredentials: false,
|
||||
};
|
||||
|
||||
await expect(async () => {
|
||||
|
|
@ -169,7 +167,6 @@ describe('WorkflowBuilderAgent', () => {
|
|||
const validMessage = 'Create a simple workflow';
|
||||
const payload: ChatPayload = {
|
||||
message: validMessage,
|
||||
useDeprecatedCredentials: false,
|
||||
};
|
||||
|
||||
// Mock the stream processing to return a proper StreamOutput
|
||||
|
|
|
|||
|
|
@ -66,12 +66,6 @@ export interface ChatPayload {
|
|||
executionData?: IRunExecutionData['resultData'];
|
||||
expressionValues?: Record<string, ExpressionValue[]>;
|
||||
};
|
||||
/**
|
||||
* Calls AI Assistant Service using deprecated credentials and endpoints
|
||||
* These credentials/endpoints will soon be removed
|
||||
* As new implementation is rolled out and builder experiment is released
|
||||
*/
|
||||
useDeprecatedCredentials?: boolean;
|
||||
}
|
||||
|
||||
export class WorkflowBuilderAgent {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ describe('AiBuilderChatRequestDto', () => {
|
|||
connections: {},
|
||||
},
|
||||
},
|
||||
useDeprecatedCredentials: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -316,21 +315,5 @@ describe('AiBuilderChatRequestDto', () => {
|
|||
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('should validate when useDeprecatedCredentials is explicitly set', () => {
|
||||
const requestWithFlag = {
|
||||
payload: {
|
||||
...validBasePayload.payload,
|
||||
useDeprecatedCredentials: true,
|
||||
},
|
||||
};
|
||||
|
||||
const result = AiBuilderChatRequestDto.safeParse(requestWithFlag);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.payload.useDeprecatedCredentials).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -57,6 +57,5 @@ export class AiBuilderChatRequestDto extends Z.class({
|
|||
})
|
||||
.optional(),
|
||||
}),
|
||||
useDeprecatedCredentials: z.boolean().default(false),
|
||||
}),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,6 @@ describe('AiController', () => {
|
|||
workflowContext: {
|
||||
currentWorkflow: { id: 'workflow123' },
|
||||
},
|
||||
useDeprecatedCredentials: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -153,7 +152,6 @@ describe('AiController', () => {
|
|||
executionData: undefined,
|
||||
executionSchema: undefined,
|
||||
},
|
||||
useDeprecatedCredentials: false,
|
||||
},
|
||||
request.user,
|
||||
expect.any(AbortSignal),
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export class AiController {
|
|||
|
||||
res.on('close', handleClose);
|
||||
|
||||
const { text, workflowContext, useDeprecatedCredentials } = payload.payload;
|
||||
const { text, workflowContext } = payload.payload;
|
||||
const aiResponse = this.workflowBuilderService.chat(
|
||||
{
|
||||
message: text,
|
||||
|
|
@ -65,7 +65,6 @@ export class AiController {
|
|||
executionSchema: workflowContext.executionSchema,
|
||||
expressionValues: workflowContext.expressionValues,
|
||||
},
|
||||
useDeprecatedCredentials,
|
||||
},
|
||||
req.user,
|
||||
signal,
|
||||
|
|
|
|||
|
|
@ -50,13 +50,7 @@ describe('API: ai', () => {
|
|||
expect(streamRequestSpy).toHaveBeenCalledWith(
|
||||
mockContext,
|
||||
'/ai/build',
|
||||
{
|
||||
...payload,
|
||||
payload: {
|
||||
...payload.payload,
|
||||
useDeprecatedCredentials: false,
|
||||
},
|
||||
},
|
||||
payload,
|
||||
mockOnMessageUpdated,
|
||||
mockOnDone,
|
||||
mockOnError,
|
||||
|
|
@ -89,56 +83,12 @@ describe('API: ai', () => {
|
|||
expect(streamRequestSpy).toHaveBeenCalledWith(
|
||||
mockContext,
|
||||
'/ai/build',
|
||||
{
|
||||
...payload,
|
||||
payload: {
|
||||
...payload.payload,
|
||||
useDeprecatedCredentials: false,
|
||||
},
|
||||
},
|
||||
mockOnMessageUpdated,
|
||||
mockOnDone,
|
||||
mockOnError,
|
||||
undefined,
|
||||
abortSignal,
|
||||
);
|
||||
});
|
||||
|
||||
it('should use deprecated credentials when flag is true', () => {
|
||||
const payload: ChatRequest.RequestPayload = {
|
||||
payload: {
|
||||
role: 'user',
|
||||
type: 'message',
|
||||
text: 'Build me a workflow',
|
||||
},
|
||||
sessionId: 'session-456',
|
||||
};
|
||||
|
||||
chatWithBuilder(
|
||||
mockContext,
|
||||
payload,
|
||||
mockOnMessageUpdated,
|
||||
mockOnDone,
|
||||
mockOnError,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(streamRequestSpy).toHaveBeenCalledWith(
|
||||
mockContext,
|
||||
'/ai/build',
|
||||
{
|
||||
...payload,
|
||||
payload: {
|
||||
...payload.payload,
|
||||
useDeprecatedCredentials: true,
|
||||
},
|
||||
},
|
||||
mockOnMessageUpdated,
|
||||
mockOnDone,
|
||||
mockOnError,
|
||||
undefined,
|
||||
undefined,
|
||||
abortSignal,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -165,13 +115,7 @@ describe('API: ai', () => {
|
|||
expect(streamRequestSpy).toHaveBeenCalledWith(
|
||||
mockContext,
|
||||
'/ai/build',
|
||||
{
|
||||
...payload,
|
||||
payload: {
|
||||
...payload.payload,
|
||||
useDeprecatedCredentials: false,
|
||||
},
|
||||
},
|
||||
payload,
|
||||
mockOnMessageUpdated,
|
||||
mockOnDone,
|
||||
mockOnError,
|
||||
|
|
@ -195,13 +139,7 @@ describe('API: ai', () => {
|
|||
expect(streamRequestSpy).toHaveBeenCalledWith(
|
||||
mockContext,
|
||||
'/ai/build',
|
||||
{
|
||||
...payload,
|
||||
payload: {
|
||||
...payload.payload,
|
||||
useDeprecatedCredentials: false,
|
||||
},
|
||||
},
|
||||
payload,
|
||||
mockOnMessageUpdated,
|
||||
mockOnDone,
|
||||
mockOnError,
|
||||
|
|
@ -316,26 +254,12 @@ describe('API: ai', () => {
|
|||
sessionId: 'session-complex',
|
||||
};
|
||||
|
||||
chatWithBuilder(
|
||||
mockContext,
|
||||
payload,
|
||||
mockOnMessageUpdated,
|
||||
mockOnDone,
|
||||
mockOnError,
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
chatWithBuilder(mockContext, payload, mockOnMessageUpdated, mockOnDone, mockOnError);
|
||||
|
||||
expect(streamRequestSpy).toHaveBeenCalledWith(
|
||||
mockContext,
|
||||
'/ai/build',
|
||||
{
|
||||
...payload,
|
||||
payload: {
|
||||
...payload.payload,
|
||||
useDeprecatedCredentials: false,
|
||||
},
|
||||
},
|
||||
payload,
|
||||
mockOnMessageUpdated,
|
||||
mockOnDone,
|
||||
mockOnError,
|
||||
|
|
@ -358,13 +282,7 @@ describe('API: ai', () => {
|
|||
expect(streamRequestSpy).toHaveBeenCalledWith(
|
||||
mockContext,
|
||||
'/ai/build',
|
||||
{
|
||||
...payload,
|
||||
payload: {
|
||||
...payload.payload,
|
||||
useDeprecatedCredentials: false,
|
||||
},
|
||||
},
|
||||
payload,
|
||||
mockOnMessageUpdated,
|
||||
mockOnDone,
|
||||
mockOnError,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ export function chatWithBuilder(
|
|||
onDone: () => void,
|
||||
onError: (e: Error) => void,
|
||||
abortSignal?: AbortSignal,
|
||||
useDeprecatedCredentials = false,
|
||||
): void {
|
||||
void streamRequest<ChatRequest.ResponsePayload>(
|
||||
ctx,
|
||||
|
|
@ -27,7 +26,6 @@ export function chatWithBuilder(
|
|||
...payload,
|
||||
payload: {
|
||||
...payload.payload,
|
||||
useDeprecatedCredentials,
|
||||
},
|
||||
},
|
||||
onMessageUpdated,
|
||||
|
|
|
|||
|
|
@ -22,18 +22,6 @@ export const NDV_UI_OVERHAUL_EXPERIMENT = {
|
|||
variant: 'variant',
|
||||
};
|
||||
|
||||
export const WORKFLOW_BUILDER_RELEASE_EXPERIMENT = {
|
||||
name: '043_workflow_builder_release',
|
||||
control: 'control',
|
||||
variant: 'variant',
|
||||
};
|
||||
|
||||
export const WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT = {
|
||||
name: '036_workflow_builder_agent',
|
||||
control: 'control',
|
||||
variant: 'variant',
|
||||
};
|
||||
|
||||
export const EXTRA_TEMPLATE_LINKS_EXPERIMENT = {
|
||||
name: '034_extra_template_links',
|
||||
control: 'control',
|
||||
|
|
@ -88,8 +76,6 @@ export const PERSONALIZED_TEMPLATES_V3 = {
|
|||
};
|
||||
|
||||
export const EXPERIMENTS_TO_TRACK = [
|
||||
WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.name,
|
||||
WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name,
|
||||
EXTRA_TEMPLATE_LINKS_EXPERIMENT.name,
|
||||
TEMPLATE_ONBOARDING_EXPERIMENT.name,
|
||||
NDV_UI_OVERHAUL_EXPERIMENT.name,
|
||||
|
|
|
|||
|
|
@ -19,11 +19,7 @@ import { useSettingsStore } from '@/stores/settings.store';
|
|||
import { defaultSettings } from '@/__tests__/defaults';
|
||||
import merge from 'lodash/merge';
|
||||
import { DEFAULT_POSTHOG_SETTINGS } from '@/stores/posthog.store.test';
|
||||
import {
|
||||
WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT,
|
||||
WORKFLOW_BUILDER_RELEASE_EXPERIMENT,
|
||||
DEFAULT_NEW_WORKFLOW_NAME,
|
||||
} from '@/constants';
|
||||
import { DEFAULT_NEW_WORKFLOW_NAME } from '@/constants';
|
||||
import { reactive } from 'vue';
|
||||
import * as chatAPI from '@/api/ai';
|
||||
import * as telemetryModule from '@/composables/useTelemetry';
|
||||
|
|
@ -442,70 +438,16 @@ describe('AI Builder store', () => {
|
|||
const settingsStore = useSettingsStore();
|
||||
|
||||
vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(false);
|
||||
vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => {
|
||||
if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) {
|
||||
return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant;
|
||||
}
|
||||
return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control;
|
||||
});
|
||||
|
||||
expect(builderStore.isAIBuilderEnabled).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when license has aiBuilder and release experiment is set to variant', () => {
|
||||
it('should return true when license has aiBuilder feature', () => {
|
||||
const builderStore = useBuilderStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true);
|
||||
vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => {
|
||||
if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) {
|
||||
return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant;
|
||||
}
|
||||
return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control; // deprecated should be control
|
||||
});
|
||||
expect(builderStore.isAIBuilderEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when license has aiBuilder and release experiment is control but deprecated experiment is variant', () => {
|
||||
const builderStore = useBuilderStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true);
|
||||
vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => {
|
||||
if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) {
|
||||
return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.control;
|
||||
}
|
||||
return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.variant;
|
||||
});
|
||||
expect(builderStore.isAIBuilderEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when license has aiBuilder but both experiments are set to control', () => {
|
||||
const builderStore = useBuilderStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true);
|
||||
vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => {
|
||||
if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) {
|
||||
return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.control;
|
||||
}
|
||||
return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control;
|
||||
});
|
||||
expect(builderStore.isAIBuilderEnabled).toBe(false);
|
||||
});
|
||||
|
||||
it('should prioritize release experiment over deprecated experiment when license has aiBuilder', () => {
|
||||
const builderStore = useBuilderStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true);
|
||||
vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => {
|
||||
if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) {
|
||||
return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant;
|
||||
}
|
||||
// Even if deprecated is control, release variant should win
|
||||
return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control;
|
||||
});
|
||||
expect(builderStore.isAIBuilderEnabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -787,8 +729,6 @@ describe('AI Builder store', () => {
|
|||
// Verify the API was called with correct parameters
|
||||
expect(apiSpy).toHaveBeenCalled();
|
||||
const callArgs = apiSpy.mock.calls[0];
|
||||
expect(callArgs).toHaveLength(7); // Should have 7 arguments
|
||||
|
||||
const signal = callArgs[5]; // The 6th argument is the abort signal
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal).toBeInstanceOf(AbortSignal);
|
||||
|
|
@ -1229,92 +1169,6 @@ describe('AI Builder store', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('useDeprecatedCredentials logic in sendChatMessage', () => {
|
||||
it('should set useDeprecatedCredentials to true when release experiment is control and deprecated experiment is variant', () => {
|
||||
const builderStore = useBuilderStore();
|
||||
|
||||
// Mock posthog to return control for release and variant for deprecated
|
||||
vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => {
|
||||
if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) {
|
||||
return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.control;
|
||||
}
|
||||
return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.variant;
|
||||
});
|
||||
|
||||
// Mock the API to capture the arguments
|
||||
apiSpy.mockImplementationOnce(() => {});
|
||||
|
||||
builderStore.sendChatMessage({ text: 'test message' });
|
||||
|
||||
// Verify chatWithBuilder was called with useDeprecatedCredentials = true
|
||||
expect(apiSpy).toHaveBeenCalledWith(
|
||||
expect.anything(), // rootStore.restApiContext
|
||||
expect.anything(), // payload
|
||||
expect.anything(), // onMessage callback
|
||||
expect.anything(), // onDone callback
|
||||
expect.anything(), // onError callback
|
||||
expect.anything(), // abort signal
|
||||
true, // useDeprecatedCredentials
|
||||
);
|
||||
});
|
||||
|
||||
it('should set useDeprecatedCredentials to false when release experiment is variant', () => {
|
||||
const builderStore = useBuilderStore();
|
||||
|
||||
// Mock posthog to return variant for release (regardless of deprecated)
|
||||
vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => {
|
||||
if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) {
|
||||
return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant;
|
||||
}
|
||||
return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.variant;
|
||||
});
|
||||
|
||||
// Mock the API to capture the arguments
|
||||
apiSpy.mockImplementationOnce(() => {});
|
||||
|
||||
builderStore.sendChatMessage({ text: 'test message' });
|
||||
|
||||
// Verify chatWithBuilder was called with useDeprecatedCredentials = false
|
||||
expect(apiSpy).toHaveBeenCalledWith(
|
||||
expect.anything(), // rootStore.restApiContext
|
||||
expect.anything(), // payload
|
||||
expect.anything(), // onMessage callback
|
||||
expect.anything(), // onDone callback
|
||||
expect.anything(), // onError callback
|
||||
expect.anything(), // abort signal
|
||||
false, // useDeprecatedCredentials
|
||||
);
|
||||
});
|
||||
|
||||
it('should set useDeprecatedCredentials to false when both experiments are control', () => {
|
||||
const builderStore = useBuilderStore();
|
||||
|
||||
// Mock posthog to return control for both experiments
|
||||
vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => {
|
||||
if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) {
|
||||
return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.control;
|
||||
}
|
||||
return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control;
|
||||
});
|
||||
|
||||
// Mock the API to capture the arguments
|
||||
apiSpy.mockImplementationOnce(() => {});
|
||||
|
||||
builderStore.sendChatMessage({ text: 'test message' });
|
||||
|
||||
// Verify chatWithBuilder was called with useDeprecatedCredentials = false
|
||||
expect(apiSpy).toHaveBeenCalledWith(
|
||||
expect.anything(), // rootStore.restApiContext
|
||||
expect.anything(), // payload
|
||||
expect.anything(), // onMessage callback
|
||||
expect.anything(), // onDone callback
|
||||
expect.anything(), // onError callback
|
||||
expect.anything(), // abort signal
|
||||
false, // useDeprecatedCredentials
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Credits management', () => {
|
||||
it('should update builder credits correctly', () => {
|
||||
const builderStore = useBuilderStore();
|
||||
|
|
@ -1462,16 +1316,12 @@ describe('AI Builder store', () => {
|
|||
mockGetBuilderCredits.mockClear();
|
||||
});
|
||||
|
||||
it('should fetch and update credits when release experiment is variant', async () => {
|
||||
it('should fetch and update credits when AI builder is enabled', async () => {
|
||||
const builderStore = useBuilderStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
// Mock posthog to return variant for release experiment
|
||||
vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => {
|
||||
if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) {
|
||||
return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant;
|
||||
}
|
||||
return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control;
|
||||
});
|
||||
// Mock AI builder as enabled
|
||||
vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true);
|
||||
|
||||
// Mock API response
|
||||
mockGetBuilderCredits.mockResolvedValueOnce({
|
||||
|
|
@ -1486,16 +1336,12 @@ describe('AI Builder store', () => {
|
|||
expect(builderStore.creditsRemaining).toBe(150);
|
||||
});
|
||||
|
||||
it('should not fetch credits when release experiment is not variant', async () => {
|
||||
it('should not fetch credits when AI builder is not enabled', async () => {
|
||||
const builderStore = useBuilderStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
// Mock posthog to return control for release experiment
|
||||
vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => {
|
||||
if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) {
|
||||
return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.control;
|
||||
}
|
||||
return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.variant;
|
||||
});
|
||||
// Mock AI builder as disabled
|
||||
vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(false);
|
||||
|
||||
await builderStore.fetchBuilderCredits();
|
||||
|
||||
|
|
@ -1506,14 +1352,10 @@ describe('AI Builder store', () => {
|
|||
|
||||
it('should handle API errors gracefully', async () => {
|
||||
const builderStore = useBuilderStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
// Mock posthog to return variant for release experiment
|
||||
vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => {
|
||||
if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) {
|
||||
return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant;
|
||||
}
|
||||
return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control;
|
||||
});
|
||||
// Mock AI builder as enabled
|
||||
vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true);
|
||||
|
||||
// Mock API to throw error
|
||||
mockGetBuilderCredits.mockRejectedValueOnce(new Error('API error'));
|
||||
|
|
@ -1529,14 +1371,10 @@ describe('AI Builder store', () => {
|
|||
it('should call fetchBuilderCredits when opening chat', async () => {
|
||||
const builderStore = useBuilderStore();
|
||||
const chatPanelStore = useChatPanelStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
// Mock posthog to return variant for release experiment
|
||||
vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => {
|
||||
if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) {
|
||||
return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant;
|
||||
}
|
||||
return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control;
|
||||
});
|
||||
// Mock AI builder as enabled
|
||||
vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true);
|
||||
|
||||
// Mock API response
|
||||
mockGetBuilderCredits.mockResolvedValueOnce({
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
import type { VIEWS } from '@/constants';
|
||||
import {
|
||||
DEFAULT_NEW_WORKFLOW_NAME,
|
||||
WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT,
|
||||
WORKFLOW_BUILDER_RELEASE_EXPERIMENT,
|
||||
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
||||
} from '@/constants';
|
||||
import { DEFAULT_NEW_WORKFLOW_NAME, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
|
||||
import { BUILDER_ENABLED_VIEWS } from './constants';
|
||||
import { STORES } from '@n8n/stores';
|
||||
import type { ChatUI } from '@n8n/design-system/types/assistant';
|
||||
|
|
@ -16,7 +11,6 @@ import { useSettingsStore } from '@/stores/settings.store';
|
|||
import { assert } from '@n8n/utils/assert';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useBuilderMessages } from './composables/useBuilderMessages';
|
||||
import { chatWithBuilder, getAiSessions, getBuilderCredits, getSessionsMetadata } from '@/api/ai';
|
||||
|
|
@ -57,7 +51,6 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => {
|
|||
const route = useRoute();
|
||||
const locale = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
const posthogStore = usePostHog();
|
||||
|
||||
// Composables
|
||||
const {
|
||||
|
|
@ -81,23 +74,8 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => {
|
|||
return firstUserMessage?.content;
|
||||
});
|
||||
|
||||
const isAIBuilderEnabled = computed(() => {
|
||||
// Check license first
|
||||
if (!settings.isAiBuilderEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const releaseExperimentVariant = posthogStore.getVariant(
|
||||
WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name,
|
||||
);
|
||||
if (releaseExperimentVariant === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
posthogStore.getVariant(WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.name) ===
|
||||
WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.variant
|
||||
);
|
||||
const isAIBuilderEnabled = computed((): boolean => {
|
||||
return settings.isAiBuilderEnabled;
|
||||
});
|
||||
|
||||
const toolMessages = computed(() => chatMessages.value.filter(isToolMessage));
|
||||
|
|
@ -305,12 +283,6 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => {
|
|||
streamingAbortController.value.abort();
|
||||
}
|
||||
|
||||
const useDeprecatedCredentials =
|
||||
posthogStore.getVariant(WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) !==
|
||||
WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant &&
|
||||
posthogStore.getVariant(WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.name) ===
|
||||
WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.variant;
|
||||
|
||||
streamingAbortController.value = new AbortController();
|
||||
try {
|
||||
chatWithBuilder(
|
||||
|
|
@ -335,7 +307,6 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => {
|
|||
() => stopStreaming(),
|
||||
(e) => handleServiceError(e, messageId, retry),
|
||||
streamingAbortController.value?.signal,
|
||||
useDeprecatedCredentials,
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
handleServiceError(e, messageId, retry);
|
||||
|
|
@ -508,10 +479,7 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => {
|
|||
}
|
||||
|
||||
async function fetchBuilderCredits() {
|
||||
const releaseExperimentVariant = posthogStore.getVariant(
|
||||
WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name,
|
||||
);
|
||||
if (releaseExperimentVariant !== WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant) {
|
||||
if (!isAIBuilderEnabled.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user