Skip to content

Commit

Permalink
feat(editor): Show template credential setup based on feature flag (#…
Browse files Browse the repository at this point in the history
…7989)

Replace the local storage based feature flag with posthog feature flag.

Also:
- Fix bunch of eslint warnings in posthog store
  • Loading branch information
tomi authored Dec 11, 2023
1 parent c378f60 commit 08ee307
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 87 deletions.
16 changes: 8 additions & 8 deletions cypress/e2e/34-template-credentials-setup.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import {
visitTemplateCollectionPage,
testData,
} from '../pages/template-collection';
import { TemplateCredentialSetupPage } from '../pages/template-credential-setup';
import * as templateCredentialsSetupPage from '../pages/template-credential-setup';
import { TemplateWorkflowPage } from '../pages/template-workflow';
import { WorkflowPage } from '../pages/workflow';

const templateWorkflowPage = new TemplateWorkflowPage();
const templateCredentialsSetupPage = new TemplateCredentialSetupPage();
const credentialsModal = new CredentialsModal();
const messageBox = new MessageBox();
const workflowPage = new WorkflowPage();
Expand All @@ -25,7 +24,8 @@ describe('Template credentials setup', () => {

it('can be opened from template workflow page', () => {
templateWorkflowPage.actions.visit(testTemplate.id);
templateCredentialsSetupPage.actions.enableFeatureFlag();
templateWorkflowPage.getters.useTemplateButton().should('be.visible');
templateCredentialsSetupPage.enableTemplateCredentialSetupFeatureFlag();
templateWorkflowPage.actions.clickUseThisWorkflowButton();

templateCredentialsSetupPage.getters
Expand All @@ -35,7 +35,7 @@ describe('Template credentials setup', () => {

it('can be opened from template collection page', () => {
visitTemplateCollectionPage(testData.ecommerceStarterPack);
templateCredentialsSetupPage.actions.enableFeatureFlag();
templateCredentialsSetupPage.enableTemplateCredentialSetupFeatureFlag();
clickUseWorkflowButtonByTitle('Promote new Shopify products on Twitter and Telegram');

templateCredentialsSetupPage.getters
Expand All @@ -44,15 +44,15 @@ describe('Template credentials setup', () => {
});

it('can be opened with a direct url', () => {
templateCredentialsSetupPage.actions.visit(testTemplate.id);
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id);

templateCredentialsSetupPage.getters
.title(`Setup 'Promote new Shopify products on Twitter and Telegram' template`)
.should('be.visible');
});

it('has all the elements on page', () => {
templateCredentialsSetupPage.actions.visit(testTemplate.id);
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id);

templateCredentialsSetupPage.getters
.title(`Setup 'Promote new Shopify products on Twitter and Telegram' template`)
Expand Down Expand Up @@ -83,14 +83,14 @@ describe('Template credentials setup', () => {
});

it('can skip template creation', () => {
templateCredentialsSetupPage.actions.visit(testTemplate.id);
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id);

templateCredentialsSetupPage.getters.skipLink().click();
workflowPage.getters.canvasNodes().should('have.length', 3);
});

it('can create credentials and workflow from the template', () => {
templateCredentialsSetupPage.actions.visit(testTemplate.id);
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id);

// Continue button should be disabled if no credentials are created
templateCredentialsSetupPage.getters.continueButton().should('be.disabled');
Expand Down
61 changes: 28 additions & 33 deletions cypress/pages/template-credential-setup.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,35 @@
import { BasePage } from './base';

export type TemplateTestData = {
id: number;
fixture: string;
};

export class TemplateCredentialSetupPage extends BasePage {
testData = {
simpleTemplate: {
id: 1205,
fixture: 'Test_Template_1.json',
},
};
export const testData = {
simpleTemplate: {
id: 1205,
fixture: 'Test_Template_1.json',
},
};

export const getters = {
continueButton: () => cy.getByTestId('continue-button'),
skipLink: () => cy.get('a:contains("Skip")'),
title: (title: string) => cy.get(`h1:contains(${title})`),
infoCallout: () => cy.getByTestId('info-callout'),
createAppCredentialsButton: (appName: string) =>
cy.get(`button:contains("Create new ${appName} credential")`),
appCredentialSteps: () => cy.getByTestId('setup-credentials-form-step'),
stepHeading: ($el: JQuery<HTMLElement>) =>
cy.wrap($el).findChildByTestId('credential-step-heading'),
stepDescription: ($el: JQuery<HTMLElement>) =>
cy.wrap($el).findChildByTestId('credential-step-description'),
};

getters = {
continueButton: () => cy.getByTestId('continue-button'),
skipLink: () => cy.get('a:contains("Skip")'),
title: (title: string) => cy.get(`h1:contains(${title})`),
infoCallout: () => cy.getByTestId('info-callout'),
createAppCredentialsButton: (appName: string) =>
cy.get(`button:contains("Create new ${appName} credential")`),
appCredentialSteps: () => cy.getByTestId('setup-credentials-form-step'),
stepHeading: ($el: JQuery<HTMLElement>) =>
cy.wrap($el).findChildByTestId('credential-step-heading'),
stepDescription: ($el: JQuery<HTMLElement>) =>
cy.wrap($el).findChildByTestId('credential-step-description'),
};
export const visitTemplateCredentialSetupPage = (templateId: number) => {
cy.visit(`/templates/${templateId}/setup`);
};

actions = {
visit: (templateId: number) => {
cy.visit(`/templates/${templateId}/setup`);
},
enableFeatureFlag: () => {
cy.window().then((window) => {
window.localStorage.setItem('template-credentials-setup', 'true');
});
},
};
}
export const enableTemplateCredentialSetupFeatureFlag = () => {
cy.window().then((win) => {
win.featureFlags.override('016_template_credential_setup', true);
});
};
7 changes: 7 additions & 0 deletions cypress/support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ declare global {
draganddrop(draggableSelector: string, droppableSelector: string): void;
push(type: string, data: unknown): void;
shouldNotHaveConsoleErrors(): void;
window(): Chainable<
AUTWindow & {
featureFlags: {
override: (feature: string, value: any) => void;
};
}
>;
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/editor-ui/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,9 @@ export const ASK_AI_EXPERIMENT = {
gpt4: 'gpt4',
};

export const EXPERIMENTS_TO_TRACK = [ASK_AI_EXPERIMENT.name];
export const TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT = '016_template_credential_setup';

export const EXPERIMENTS_TO_TRACK = [ASK_AI_EXPERIMENT.name, TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT];

export const MFA_AUTHENTICATION_REQUIRED_ERROR_CODE = 998;

Expand Down
50 changes: 30 additions & 20 deletions packages/editor-ui/src/stores/posthog.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const EVENTS = {
IS_PART_OF_EXPERIMENT: 'User is part of experiment',
};

export type PosthogStore = ReturnType<typeof usePostHog>;

export const usePostHog = defineStore('posthog', () => {
const usersStore = useUsersStore();
const settingsStore = useSettingsStore();
Expand All @@ -39,6 +41,13 @@ export const usePostHog = defineStore('posthog', () => {
return getVariant(experiment) === variant;
};

/**
* Checks if the given feature flag is enabled. Should only be used for boolean flags
*/
const isFeatureEnabled = (experiment: keyof FeatureFlags) => {
return featureFlags.value?.[experiment] === true;
};

if (!window.featureFlags) {
// for testing
const cachedOverrides = useStorage(LOCAL_STORAGE_EXPERIMENT_OVERRIDES).value;
Expand All @@ -65,7 +74,7 @@ export const usePostHog = defineStore('posthog', () => {
},

getVariant,
getAll: () => featureFlags.value || {},
getAll: () => featureFlags.value ?? {},
};
}

Expand All @@ -90,6 +99,25 @@ export const usePostHog = defineStore('posthog', () => {
};
};

const trackExperiment = (featFlags: FeatureFlags, name: string) => {
const variant = featFlags[name];
if (!variant || trackedDemoExp.value[name] === variant) {
return;
}

telemetryStore.track(EVENTS.IS_PART_OF_EXPERIMENT, {
name,
variant,
});

trackedDemoExp.value[name] = variant;
};

const trackExperiments = (featFlags: FeatureFlags) => {
EXPERIMENTS_TO_TRACK.forEach((name) => trackExperiment(featFlags, name));
};
const trackExperimentsDebounced = debounce(trackExperiments, 2000);

const init = (evaluatedFeatureFlags?: FeatureFlags) => {
if (!window.posthog) {
return;
Expand Down Expand Up @@ -143,25 +171,6 @@ export const usePostHog = defineStore('posthog', () => {
}
};

const trackExperiments = (featureFlags: FeatureFlags) => {
EXPERIMENTS_TO_TRACK.forEach((name) => trackExperiment(featureFlags, name));
};
const trackExperimentsDebounced = debounce(trackExperiments, 2000);

const trackExperiment = (featureFlags: FeatureFlags, name: string) => {
const variant = featureFlags[name];
if (!variant || trackedDemoExp.value[name] === variant) {
return;
}

telemetryStore.track(EVENTS.IS_PART_OF_EXPERIMENT, {
name,
variant,
});

trackedDemoExp.value[name] = variant;
};

const capture = (event: string, properties: IDataObject) => {
if (typeof window.posthog?.capture === 'function') {
window.posthog.capture(event, properties);
Expand All @@ -181,6 +190,7 @@ export const usePostHog = defineStore('posthog', () => {

return {
init,
isFeatureEnabled,
isVariantEnabled,
getVariant,
reset,
Expand Down
16 changes: 0 additions & 16 deletions packages/editor-ui/src/utils/featureFlag.ts

This file was deleted.

11 changes: 7 additions & 4 deletions packages/editor-ui/src/utils/templates/templateActions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { INodeUi, IWorkflowData, IWorkflowTemplate } from '@/Interface';
import { getNewWorkflow } from '@/api/workflows';
import { VIEWS } from '@/constants';
import { TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT, VIEWS } from '@/constants';
import type { useRootStore } from '@/stores/n8nRoot.store';
import type { PosthogStore } from '@/stores/posthog.store';
import type { useWorkflowsStore } from '@/stores/workflows.store';
import { FeatureFlag, isFeatureFlagEnabled } from '@/utils/featureFlag';
import { getFixedNodesList } from '@/utils/nodeViewUtils';
import type { TemplateCredentialKey } from '@/utils/templates/templateTransforms';
import { replaceAllTemplateNodeCredentials } from '@/utils/templates/templateTransforms';
Expand Down Expand Up @@ -45,13 +45,16 @@ export async function createWorkflowFromTemplate(
* if the feature flag is disabled)
*/
export async function openTemplateCredentialSetup(opts: {
posthogStore: PosthogStore;
templateId: string;
router: Router;
inNewBrowserTab?: boolean;
}) {
const { router, templateId, inNewBrowserTab = false } = opts;
const { router, templateId, inNewBrowserTab = false, posthogStore } = opts;

const routeLocation: RouteLocationRaw = isFeatureFlagEnabled(FeatureFlag.templateCredentialsSetup)
const routeLocation: RouteLocationRaw = posthogStore.isFeatureEnabled(
TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT,
)
? {
name: VIEWS.TEMPLATE_SETUP,
params: { id: templateId },
Expand Down
6 changes: 3 additions & 3 deletions packages/editor-ui/src/views/TemplatesCollectionView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,9 @@ import type {
} from '@/Interface';
import { setPageTitle } from '@/utils/htmlUtils';
import { VIEWS } from '@/constants';
import { TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT, VIEWS } from '@/constants';
import { useTemplatesStore } from '@/stores/templates.store';
import { usePostHog } from '@/stores/posthog.store';
import { FeatureFlag, isFeatureFlagEnabled } from '@/utils/featureFlag';
import { openTemplateCredentialSetup } from '@/utils/templates/templateActions';
import { useExternalHooks } from '@/composables/useExternalHooks';
Expand Down Expand Up @@ -129,7 +128,7 @@ export default defineComponent({
this.navigateTo(event, VIEWS.TEMPLATE, id);
},
async onUseWorkflow({ event, id }: { event: MouseEvent; id: string }) {
if (!isFeatureFlagEnabled(FeatureFlag.templateCredentialsSetup)) {
if (this.posthogStore.isFeatureEnabled(TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT)) {
const telemetryPayload = {
template_id: id,
wf_template_repo_session_id: this.templatesStore.currentSessionId,
Expand All @@ -142,6 +141,7 @@ export default defineComponent({
}
await openTemplateCredentialSetup({
posthogStore: this.posthogStore,
router: this.$router,
templateId: id,
inNewBrowserTab: event.metaKey || event.ctrlKey,
Expand Down
5 changes: 3 additions & 2 deletions packages/editor-ui/src/views/TemplatesWorkflowView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ import { setPageTitle } from '@/utils/htmlUtils';
import { useTemplatesStore } from '@/stores/templates.store';
import { usePostHog } from '@/stores/posthog.store';
import { openTemplateCredentialSetup } from '@/utils/templates/templateActions';
import { FeatureFlag, isFeatureFlagEnabled } from '@/utils/featureFlag';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT } from '@/constants';
export default defineComponent({
name: 'TemplatesWorkflowView',
Expand Down Expand Up @@ -107,7 +107,7 @@ export default defineComponent({
},
methods: {
async openTemplateSetup(id: string, e: PointerEvent) {
if (!isFeatureFlagEnabled(FeatureFlag.templateCredentialsSetup)) {
if (!this.posthogStore.isFeatureEnabled(TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT)) {
const telemetryPayload = {
source: 'workflow',
template_id: id,
Expand All @@ -121,6 +121,7 @@ export default defineComponent({
}
await openTemplateCredentialSetup({
posthogStore: this.posthogStore,
router: this.$router,
templateId: id,
inNewBrowserTab: e.metaKey || e.ctrlKey,
Expand Down

0 comments on commit 08ee307

Please sign in to comment.