Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/models/EpicTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ case class EpicTest(
consentStatus: Option[ConsentStatus] = Some(ConsentStatus.All),
methodologies: List[Methodology] = defaultMethodologies,
mParticleAudience: Option[Int] = None,
scheduler: Option[Scheduler] = None
scheduler: Option[Scheduler] = None,
mParticleTemplates: Option[List[String]],
) extends ChannelTest[EpicTest] {

override def withChannel(channel: Channel): EpicTest = this.copy(channel = Some(channel))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ const VariantContentEditor: React.FC<VariantContentEditorProps> = ({
enableArticleCountTemplate: true,
enableDateTemplate: true,
enableDayTemplate: true,
enablePriceTemplates: true,
enableLink: true,
}}
/>
Expand Down Expand Up @@ -348,6 +349,7 @@ const VariantContentEditor: React.FC<VariantContentEditorProps> = ({
enableArticleCountTemplate: true,
enableDateTemplate: true,
enableDayTemplate: true,
enableMParticleTemplates: true,
enableLink: true,
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { ValidatedTestEditor, ValidatedTestEditorProps } from '../validatedTestE
import MaxViewsEditor from './maxViewsEditor';
import { EpicTestPreviewButton } from './testPreview';
import { getDefaultVariant } from './utils/defaults';
import { findMParticleTemplates } from './utils/findMParticleTemplates';
import VariantEditor from './variantEditor';
import VariantPreview from './variantPreview';

Expand Down Expand Up @@ -89,6 +90,7 @@ export const getEpicTestEditor = (
...updatedTest,
// To save dotcom from having to work this out
hasCountryName: copyHasTemplate(updatedTest, COUNTRY_NAME_TEMPLATE),
mParticleTemplates: findMParticleTemplates(updatedTest),
articlesViewedSettings: userExplicitlyDisabledArticleCount
? undefined
: getArticlesViewedSettings(updatedTest),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const DEFAULT_REGION_TARGETING: RegionTargeting = {
contributionsOnlyCountriesTargeting: 'Exclude',
};

const CODE_DEFAULT_TEST: EpicTest = {
export const CODE_DEFAULT_TEST: EpicTest = {
name: 'TEST',
nickname: 'TEST',
status: 'Draft',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { EpicTest } from '../../../../models/epic';

export const findMParticleTemplates = (test: EpicTest): string[] => {
const mParticleAttributeTemplates = new Set<string>();
test.variants.forEach((variant) => {
if (variant.heading) {
for (const templateMatch of variant.heading.matchAll(/%%mParticle_([^%]+)%%/g)) {
mParticleAttributeTemplates.add(templateMatch[1]);
}
}
variant.paragraphs.forEach((paragraph) => {
for (const templateMatch of paragraph.matchAll(/%%mParticle_([^%]+)%%/g)) {
mParticleAttributeTemplates.add(templateMatch[1]);
}
});
});
return [...mParticleAttributeTemplates];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { EpicTest } from '../../../../models/epic';
import { UserCohort } from '../../helpers/shared';
import { findMParticleTemplates } from './findMParticleTemplates';

const baseTest: EpicTest = {
name: 'test',
status: 'Live',
locations: [],
regionTargeting: { targetedCountryGroups: [] },
tagIds: [],
sections: [],
excludedTagIds: [],
excludedSections: [],
alwaysAsk: false,
userCohort: UserCohort.Everyone,
hasCountryName: false,
variants: [],
highPriority: false,
useLocalViewLog: false,
methodologies: [],
};

describe('findMParticleTemplates', () => {
it('returns an empty array when there are no variants', () => {
const result = findMParticleTemplates({ ...baseTest, variants: [] });
expect(result).toEqual([]);
});

it('returns an empty array when variants have no mParticle templates', () => {
const result = findMParticleTemplates({
...baseTest,
variants: [
{
name: 'control',
heading: 'Hello world',
paragraphs: ['No templates here.'],
showTicker: false,
},
],
});
expect(result).toEqual([]);
});

it('finds a template in a variant heading', () => {
const result = findMParticleTemplates({
...baseTest,
variants: [
{ name: 'control', heading: '%%mParticle_firstName%%', paragraphs: [], showTicker: false },
],
});
expect(result).toEqual(['firstName']);
});

it('finds a template in a variant paragraph', () => {
const result = findMParticleTemplates({
...baseTest,
variants: [{ name: 'control', paragraphs: ['Hello %%mParticle_city%%!'], showTicker: false }],
});
expect(result).toEqual(['city']);
});

it('finds multiple distinct templates across heading and paragraphs', () => {
const result = findMParticleTemplates({
...baseTest,
variants: [
{
name: 'control',
heading: '%%mParticle_firstName%%',
paragraphs: ['You live in %%mParticle_city%%.', 'Your tier is %%mParticle_tier%%.'],
showTicker: false,
},
],
});
expect(result).toEqual(['firstName', 'city', 'tier']);
});

it('deduplicates templates that appear more than once', () => {
const result = findMParticleTemplates({
...baseTest,
variants: [
{
name: 'control',
heading: '%%mParticle_firstName%%',
paragraphs: ['Hi %%mParticle_firstName%%, welcome to %%mParticle_city%%.'],
showTicker: false,
},
],
});
expect(result).toEqual(['firstName', 'city']);
});

it('collects templates across multiple variants', () => {
const result = findMParticleTemplates({
...baseTest,
variants: [
{ name: 'control', paragraphs: ['%%mParticle_firstName%%'], showTicker: false },
{ name: 'variant', paragraphs: ['%%mParticle_city%%'], showTicker: false },
],
});
expect(result).toEqual(['firstName', 'city']);
});

it('does not match patterns that are not mParticle templates', () => {
const result = findMParticleTemplates({
...baseTest,
variants: [
{ name: 'control', paragraphs: ['%%ARTICLE_COUNT%% articles'], showTicker: false },
],
});
expect(result).toEqual([]);
});

it('handles a variant with no heading', () => {
const result = findMParticleTemplates({
...baseTest,
variants: [{ name: 'control', paragraphs: ['%%mParticle_country%%'], showTicker: false }],
});
expect(result).toEqual(['country']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ const VariantEditor: React.FC<EpicTestVariantEditorProps> = ({
enableArticleCountTemplate,
enableDateTemplate,
enableDayTemplate,
enableMParticleTemplates: true,
}}
/>
);
Expand Down Expand Up @@ -339,6 +340,7 @@ const VariantEditor: React.FC<EpicTestVariantEditorProps> = ({
enableArticleCountTemplate,
enableDateTemplate,
enableDayTemplate,
enableMParticleTemplates: true,
}}
/>
);
Expand Down Expand Up @@ -381,6 +383,7 @@ const VariantEditor: React.FC<EpicTestVariantEditorProps> = ({
enableArticleCountTemplate,
enableDateTemplate,
enableDayTemplate,
enableMParticleTemplates: true,
}}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const PRICE_PRODUCT_WEEKLY = '%%PRICE_PRODUCT_WEEKLY%%';
export const DAY_OF_THE_WEEK = '%%DAY_OF_THE_WEEK%%';
export const DATE = '%%DATE%%';
export const CAMPAIGN_DEADLINE_TEMPLATE = '%%CAMPAIGN_DEADLINE%%';
export const MPARTICLE_FIRST_NAME_TEMPLATE = '%%mParticle_$FirstName%%';

export const VALID_TEMPLATES = {
APPLE_NEWS: [CURRENCY_TEMPLATE],
Expand All @@ -69,6 +70,7 @@ export const VALID_TEMPLATES = {
PRICE_PRODUCT_WEEKLY,
DAY_OF_THE_WEEK,
DATE,
MPARTICLE_FIRST_NAME_TEMPLATE,
],
SUPPORT: [CAMPAIGN_DEADLINE_TEMPLATE],
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Button, Menu, MenuItem } from '@mui/material';
import React from 'react';
import { MPARTICLE_FIRST_NAME_TEMPLATE } from '../helpers/validation';

interface Props {
insertTemplate: (template: string) => void;
}

export const MParticleTemplateMenu: React.FC<Props> = ({ insertTemplate }: Props) => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleButtonClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};

const handleTemplateClick = (template: string) => {
insertTemplate(template);
handleClose();
};

return (
<div>
<Button
variant="contained"
disableElevation
onClick={handleButtonClick}
//endIcon={<KeyboardArrowDownIcon />}
>
mParticle
</Button>
<Menu id="demo-customized-menu" anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem onClick={() => handleTemplateClick(MPARTICLE_FIRST_NAME_TEMPLATE)} disableRipple>
First Name
</MenuItem>
</Menu>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ import {
PRICE_GUARDIANWEEKLY_MONTHLY,
PRICE_PRODUCT_WEEKLY,
} from '../helpers/validation';
import './remirror-styles.css';
import { MParticleTemplateMenu } from './mParticleTemplateMenu';
import { useRTEStyles } from './richTextEditorStyles';
import './remirror-styles.css';

// Typescript
interface RichTextEditorProps<T> {
Expand Down Expand Up @@ -74,6 +75,7 @@ interface RteMenuConstraints {
enableCampaignDeadlineTemplate?: boolean;
enableLink?: boolean;
enableStrikethrough?: boolean;
enableMParticleTemplates?: boolean;
}

/**
Expand Down Expand Up @@ -227,6 +229,8 @@ const FloatingLinkToolbar = () => {
useFloatingLinkState();
const active = useActive();
const activeLink = active.link();
const chain = useChainedCommands();
const insertTemplate = (template: string): void => chain.insertText(template).focus().run();
return (
<>
<FloatingToolbar placement="top">
Expand All @@ -245,6 +249,7 @@ const FloatingLinkToolbar = () => {
Add link
</button>
)}
<MParticleTemplateMenu insertTemplate={insertTemplate} />
</CommandButtonGroup>
</FloatingToolbar>

Expand Down Expand Up @@ -296,6 +301,7 @@ const RichTextMenu: React.FC<RichTextMenuProps> = ({
enableDateTemplate,
enableDayTemplate,
enableCampaignDeadlineTemplate,
enableMParticleTemplates,
} = rteMenuConstraints;

const clickBold = () => {
Expand Down Expand Up @@ -393,6 +399,9 @@ const RichTextMenu: React.FC<RichTextMenuProps> = ({
Date
</button>
)}
{enableMParticleTemplates && (
<MParticleTemplateMenu insertTemplate={insertTemplate} />
)}
{enableProductWeeklyTemplate && (
<button
className="remirror-button"
Expand Down
1 change: 1 addition & 0 deletions public/src/models/epic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,5 @@ export interface EpicTest extends Test {
deviceType?: DeviceType;
campaignName?: string;
mParticleAudience?: number;
mParticleTemplates?: string[];
}
2 changes: 1 addition & 1 deletion test/services/DynamoChannelTestsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class DynamoChannelTestsSpec extends AsyncFlatSpec with Matchers with BeforeAndA
priority = priority,
maxViews = None,
variants = Nil,
regionTargeting = None
mParticleTemplates = None
)

val epicTests = List(
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"compilerOptions": {
"target": "es6",
"module": "esnext",
"lib": ["dom", "es2017", "es2019"],
"lib": ["dom", "es2017", "es2019", "es2020"],
"jsx": "react",
"allowJs": true,
"noImplicitAny": true,
Expand Down
Loading