/** * WordPress dependencies */ import apiFetch from '@safe-wordpress/api-fetch'; import { dispatch, select } from '@safe-wordpress/data'; /** * External dependencies */ import { v4 as uuid } from 'uuid'; import { isDefined } from '@nab/utils'; import type { Experiment, Goal, Maybe, Opportunity, OpportunityExperimentMeta, ScopeRule, } from '@nab/types'; /** * Internal dependencies */ import { store as NAB_DATA } from '../../../store'; import type { ControlAttributes as CptAttrs } from '../../../../experiment-library/custom-post-type/types'; import type { ControlAttributes as CssAttrs } from '../../../../experiment-library/css/types'; import type { ControlAttributes as HeadlineAttrs } from '../../../../experiment-library/headline/types'; import type { ControlAttributes as JavaScriptAttrs } from '../../../../experiment-library/javascript/types'; import type { ControlAttributes as PageAttrs } from '../../../../experiment-library/page/types'; import type { ControlAttributes as PhpAttrs } from '../../../../experiment-library/php/types'; import type { ControlAttributes as PostAttrs } from '../../../../experiment-library/post/types'; import type { ControlAttributes as UrlAttrs } from '../../../../experiment-library/url/types'; type ControlAttributesDict = { 'nab/page': PageAttrs; 'nab/post': PostAttrs; 'nab/custom-post-type': CptAttrs; 'nab/headline': HeadlineAttrs; 'nab/css': CssAttrs; 'nab/javascript': JavaScriptAttrs; 'nab/php': PhpAttrs; 'nab/url': UrlAttrs; }; export async function instantiateOpportunityAsExperiment( type: keyof ControlAttributesDict, opportunity: Opportunity ): Promise< void > { if ( select( NAB_DATA ).getOpportunityBeingInstantiated() ) { return; } await dispatch( NAB_DATA ).markOpportunityAsBeingInstantiated( opportunity ); try { const newExperiment = await apiFetch< Experiment< ControlAttributesDict[ typeof type ], Record< string, unknown > > >( { path: '/nab/v1/experiment', method: 'post', data: { type }, } ); const experiment = { ...newExperiment, alternatives: [ { id: 'control', attributes: getControlAttributes( type, opportunity ), }, ], goals: getGoals( opportunity ) || newExperiment.goals, scope: getScope( type, opportunity ) || newExperiment.scope, }; await apiFetch< Experiment< ControlAttributesDict[ typeof type ], Record< string, unknown > > >( { path: `/nab/v1/experiment/${ experiment.id }`, method: 'post', data: experiment, } ); window.location.href = experiment.links.edit; } catch ( _ ) { await dispatch( NAB_DATA ).markOpportunityAsBeingInstantiated( undefined ); } } // ======= // HELPERS // ======= function getControlAttributes( type: keyof ControlAttributesDict, opportunity: Opportunity ): ControlAttributesDict[ keyof ControlAttributesDict ] { switch ( type ) { case 'nab/page': return { postType: 'page', postId: 'post' === opportunity.target.type ? opportunity.target.postId : 0, } satisfies PageAttrs; case 'nab/post': return { postType: 'post', postId: 'post' === opportunity.target.type ? opportunity.target.postId : 0, } satisfies PostAttrs; case 'nab/custom-post-type': return { postType: 'post' === opportunity.target.type ? opportunity.target.postType : 'invalid-post-type', postId: 'post' === opportunity.target.type ? opportunity.target.postId : 0, } satisfies CptAttrs; case 'nab/headline': return { postType: 'post', postId: 'post' === opportunity.target.type ? opportunity.target.postId : 0, } satisfies PostAttrs; case 'nab/url': return { url: opportunity.target.url, } satisfies UrlAttrs; case 'nab/css': return {} satisfies CssAttrs; case 'nab/javascript': return {} satisfies JavaScriptAttrs; case 'nab/php': return {} satisfies PhpAttrs; } } function getGoals( opportunity: Opportunity ): Maybe< ReadonlyArray< Goal > > { const experiment = findExperiment( opportunity ); return experiment?.goals.map( ( g ) => ( { ...g, id: uuid(), conversionActions: g.conversionActions.map( ( a ) => ( { ...a, id: uuid(), } ) ), } ) ); } function getScope( type: keyof ControlAttributesDict, opportunity: Opportunity ): Maybe< ReadonlyArray< ScopeRule > > { switch ( type ) { case 'nab/page': case 'nab/post': case 'nab/custom-post-type': case 'nab/headline': case 'nab/url': return undefined; case 'nab/css': case 'nab/javascript': case 'nab/php': return [ { id: uuid(), attributes: { type: 'exact', value: opportunity.target.url, }, }, ]; } } function findExperiment( opportunity: Opportunity ): Maybe< OpportunityExperimentMeta[ 'experiment' ] > { switch ( opportunity.type ) { case 'multiple-reasons': return opportunity.meta.opportunities .map( findExperiment ) .filter( isDefined )[ 0 ]; case 'recently-finished-experiment': return opportunity.meta.experiment; case 'stale-experiment': return opportunity.meta.experiment; case 'key-page': case 'high-traffic': case 'recently-published': return undefined; } }