Skip to content

Commit

Permalink
feat: add error handling to callout (#288)
Browse files Browse the repository at this point in the history
* Update sample data

* Fix: first child error

* Feat: Exract header into its own component

* Feat: Add a callout error component

* Only render if callout and callout is active

* Consider active if no active until

* refactor: move styles inside component

* fix: move types to prevent dependency cycle

* Feat: add links to targeting tool if error

* refactor: make time human readable
  • Loading branch information
abeddow91 authored Jan 3, 2023
1 parent 4090818 commit a390a34
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 53 deletions.
33 changes: 25 additions & 8 deletions demo/sampleElements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,8 @@ export const sampleCampaignList = [
matchAllTags: false,
},
],
activeFrom: 1645488000000,
activeUntil: 1645488000001,
activeFrom: new Date("2021-01-29T00:00:00.000Z").getTime(),
activeUntil: new Date("2026-01-29T00:00:00.000Z").getTime(),
priority: 0,
displayOnSensitive: false,
fields: {
Expand All @@ -427,11 +427,29 @@ export const sampleCampaignList = [
},
{
id: "1235",
name: "Expired callout",
rules: [],
priority: 0,
activeFrom: new Date("2021-01-29T00:00:00.000Z").getTime(),
activeUntil: new Date("2021-02-29T00:00:00.000Z").getTime(),
displayOnSensitive: false,
fields: {
callout: "callout-expired",
formId: 11121,
tagName: "callout-callout",
description: "this is an expired callout",
formFields: [],
formUrl: "formstack.co.uk",
_type: "callout",
},
},
{
id: "1236",
name: "Broken callout",
rules: [],
priority: 0,
activeFrom: 1645488000000,
activeUntil: 1645488000001,
activeFrom: new Date("2021-01-29T00:00:00.000Z").getTime(),
activeUntil: new Date("2026-01-29T00:00:00.000Z").getTime(),
displayOnSensitive: false,
fields: {
callout: "callout-2",
Expand All @@ -443,14 +461,13 @@ export const sampleCampaignList = [
_type: "callout",
},
},

{
id: "1236",
id: "1237",
name: "empty tag name callout",
rules: [],
priority: 0,
activeFrom: 1645488000000,
activeUntil: 1645488000001,
activeFrom: new Date("2021-01-29T00:00:00.000Z").getTime(),
activeUntil: new Date("2026-01-29T00:00:00.000Z").getTime(),
displayOnSensitive: false,
fields: {
callout: "callout-2",
Expand Down
90 changes: 58 additions & 32 deletions src/elements/callout/Callout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { css } from "@emotion/react";
import { neutral, space, text } from "@guardian/src-foundations";
import { textSans } from "@guardian/src-foundations/typography";
import React, { useEffect, useState } from "react";
import {
createCustomDropdownField,
Expand All @@ -8,34 +11,9 @@ import type { FieldNameToValueMap } from "../../plugin/helpers/fieldView";
import { dropDownRequired } from "../../plugin/helpers/validation";
import { createReactElementSpec } from "../../renderers/react/createReactElementSpec";
import { CustomDropdownView } from "../../renderers/react/customFieldViewComponents/CustomDropdownView";
import { CalloutError, calloutStyles } from "../embed/Callout";
import { CalloutError } from "./CalloutError";
import { CalloutTable } from "./CalloutTable";

export type Fields = {
callout: string;
formId: number;
tagName: string;
description?: string;
formUrl?: string;
_type: string;
};

export type Rules = {
requiredTags: string[];
lackingTags: string[];
matchAllTags: boolean;
};

export type Campaign = {
id: string;
name: string;
fields: Fields;
rules: Rules[];
priority: number;
displayOnSensitive: boolean;
activeFrom?: number;
activeUntil?: number;
};
import type { Campaign } from "./CalloutTypes";

const getDropdownOptionsFromCampaignList = (campaignList: Campaign[]) => {
const campaigns = campaignList.map((campaign) => {
Expand All @@ -53,11 +31,55 @@ export const calloutFields = {
campaignId: createCustomDropdownField(
undefinedDropdownValue,
[],
[dropDownRequired(undefined, "WARN")]
[
dropDownRequired(
"A current campaign must be selected for the callout to appear",
"WARN"
),
]
),
isNonCollapsible: createCustomField(false, true),
};

const calloutStyles = css`
${textSans.small({ fontWeight: "regular", lineHeight: "loose" })}
font-family: "Guardian Agate Sans";
a {
color: ${text.anchorPrimary};
}
code {
font-family: monospace;
background-color: ${neutral[86]};
border-radius: ${space[1]}px;
padding: 1px 4px;
}
table {
width: 100%;
border-collapse: collapse;
border: 1px solid ${neutral[86]};
font-size: 14px;
}
th,
tr,
td {
border: 1px solid ${neutral[86]};
padding: ${space[1]}px;
line-height: 14px;
vertical-align: top;
}
th {
text-align: left;
}
p,
p:first-child {
margin-top: 0px;
margin-bottom: 0px;
}
ul {
margin-bottom: 0px;
}
`;

type Props = {
fetchCampaignList: () => Promise<Campaign[]>;
targetingUrl: string;
Expand All @@ -76,6 +98,7 @@ export const createCalloutElement = ({
({ fields }) => {
const campaignId = fields.campaignId.value;
const [campaignList, setCampaignList] = useState<Campaign[]>([]);

useEffect(() => {
void fetchCampaignList().then((campaignList) => {
setCampaignList(campaignList);
Expand All @@ -96,25 +119,28 @@ export const createCalloutElement = ({
const campaign = campaignList.find((campaign) => campaign.id === id);
return campaign?.fields.tagName ?? "";
};

const dropdownOptions = getDropdownOptionsFromCampaignList(campaignList);
const callout = campaignList.find(
(campaign) => campaign.id === campaignId
);

const isActiveCallout =
!callout?.activeUntil || callout.activeUntil >= Date.now();
const trimmedTargetingUrl = targetingUrl.replace(/\/$/, "");

return campaignId && campaignId != "none-selected" ? (
<div css={calloutStyles}>
{callout ? (
{callout && isActiveCallout ? (
<CalloutTable
calloutData={callout}
targetingUrl={trimmedTargetingUrl}
isNonCollapsible={fields.isNonCollapsible}
/>
) : (
<CalloutError
tag={getTag(campaignId)}
isExpired={!isActiveCallout}
targetingUrl={trimmedTargetingUrl}
callout={callout}
calloutId={campaignId}
/>
)}
</div>
Expand Down
74 changes: 74 additions & 0 deletions src/elements/callout/CalloutError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { css } from "@emotion/react";
import { space, text } from "@guardian/src-foundations";
import { SvgAlertTriangle } from "@guardian/src-icons";
import { Error } from "../../editorial-source-components/Error";
import { CalloutTableHeader } from "./CalloutTable";
import type { Campaign } from "./CalloutTypes";

const marginBottom = css`
margin-bottom: ${space[2]}px !important;
`;

const error = css`
display: flex;
align-items: center;
svg {
height: 20px;
fill: ${text.error};
margin-right: ${space[1]}px;
}
a {
margin-left: auto;
}
`;

export const CalloutError = ({
callout,
targetingUrl,
calloutId,
isExpired,
}: {
callout: Campaign | undefined;
targetingUrl: string;
calloutId: string;
isExpired: boolean;
}) => {
const edToolsEmail = "[email protected]";
const centralProdEmail = "[email protected]";

return callout && isExpired ? (
<>
<Error css={[error, marginBottom]}>
<SvgAlertTriangle />
<p>This callout has expired and will not appear in the article.</p>
</Error>
<CalloutTableHeader
title={callout.fields.callout}
tagName={callout.fields.tagName}
targetingUrl={targetingUrl}
calloutId={calloutId}
formUrl={callout.fields.formUrl ?? ""}
/>
</>
) : (
<div>
<Error css={[error, marginBottom]}>
<SvgAlertTriangle />
Composer was unable to find this callout.
<a href={`${targetingUrl}`}>Open in targeting tool</a>
</Error>
<p css={marginBottom}>
It is likely that the callout has been deleted. Please check in
the&nbsp;
<a href={`${targetingUrl}`}>targeting tool</a> to check if this callout
is available.
</p>
<p>
If the problem persists, you may wish to contact Central Production (
<a href={`mailto:${centralProdEmail}`}>{centralProdEmail}</a>) and the
Editorial Tools team (
<a href={`mailto:${edToolsEmail}`}>{edToolsEmail}</a>) for assistance.
</p>
</div>
);
};
47 changes: 36 additions & 11 deletions src/elements/callout/CalloutTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { neutral, space } from "@guardian/src-foundations";
import { Label } from "../../editorial-source-components/Label";
import type { CustomField } from "../../plugin/types/Element";
import { CustomCheckboxView } from "../../renderers/react/customFieldViewComponents/CustomCheckboxView";
import type { Campaign } from "./Callout";
import type { Campaign } from "./CalloutTypes";

const containerStyle = css`
display: flex;
Expand All @@ -26,7 +26,7 @@ const cellStyle = css`
border-right: 1px solid ${neutral[100]};
padding: 3px 5px;
&:first-child {
&:first-of-type {
padding-left: ${space[3]}px;
}
Expand Down Expand Up @@ -58,26 +58,29 @@ const strongStyle = css`
font-weight: 700;
`;

export const CalloutTable = ({
calloutData,
export const CalloutTableHeader = ({
title,
tagName,
targetingUrl,
isNonCollapsible,
calloutId,
formUrl,
}: {
calloutData: Campaign;
title: string;
tagName: string;
targetingUrl: string;
isNonCollapsible: CustomField<boolean, boolean>;
calloutId: string;
formUrl: string;
}) => {
const { tagName, callout, description, formUrl } = calloutData.fields;
return (
<div css={containerStyle}>
<>
<div css={headerStyle}>
<Label>CALLOUT: {tagName}</Label>
<Label>CALLOUT: {title}</Label>
<span>
<a
css={css`
margin-right: ${space[4]}px;
`}
href={`${targetingUrl}/campaigns/${calloutData.id}`}
href={`${targetingUrl}/campaigns/${calloutId}`}
>
Open in targeting tool
</a>
Expand All @@ -91,6 +94,28 @@ export const CalloutTable = ({
</span>
<span css={[cellStyle, tagNameStyle]}>{tagName}</span>
</div>
</>
);
};
export const CalloutTable = ({
calloutData,
targetingUrl,
isNonCollapsible,
}: {
calloutData: Campaign;
targetingUrl: string;
isNonCollapsible: CustomField<boolean, boolean>;
}) => {
const { tagName, callout, description, formUrl } = calloutData.fields;
return (
<div css={containerStyle}>
<CalloutTableHeader
title={callout}
tagName={tagName}
formUrl={formUrl ?? ""}
targetingUrl={targetingUrl}
calloutId={calloutData.id}
/>
<div css={bodyStyle}>
<span>
<span css={strongStyle}>Callout title: </span>
Expand Down
25 changes: 25 additions & 0 deletions src/elements/callout/CalloutTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export type Fields = {
callout: string;
formId: number;
tagName: string;
description?: string;
formUrl?: string;
_type: string;
};

export type Rules = {
requiredTags: string[];
lackingTags: string[];
matchAllTags: boolean;
};

export type Campaign = {
id: string;
name: string;
fields: Fields;
rules: Rules[];
priority: number;
displayOnSensitive: boolean;
activeFrom?: number;
activeUntil?: number;
};
Loading

0 comments on commit a390a34

Please sign in to comment.