Repository: guardian/support-frontend
Pull request #6860: Add alarm for no successful Ad-Lite checkouts in 12 hours
Author: login: maloky
Labels: name: Seen-on-PROD
Created: 2025-03-19T11:33:59Z | Merged: 2025-03-19T15:23:47Z

<!-- all sections optional, delete any you don't need -->
## What are you doing in this PR?
Add alarm for no successful Ad-Lite checkouts in 12 hours
<!--
This pr adds a widget to the doogle so that we can buy a sub from any page in one click
For detailed changes see the inline self-review comments.
-->

[**Trello Card**](https://trello.com/c/t0bnXb5x/1488-guardian-ad-lite-alarm-setup)

How I tested:
1. Watched the Alarm for Ad-Lite without purchases on Cloudwatch.
2. Made a Ad-Lite purchase on site.
3. See the Alarm state dropping to `ok` on Cloudwatch.

### Screenshot
![Screenshot 2025-03-19 at 11 52 31](https://github.com/user-attachments/assets/31e65c12-8c28-4ce2-8589-c4c89607bc79)

## Why are you doing this?

<!--
Remember, PRs are documentation for future contributors.
-->

## How to test

<!-- Provide instructions to help others verify the change. This could take the form of "On PROD, do X and witness Y. On this branch, do X and witness Z. " -->
go to AWS cloud watch and check last 12h purchases for the Ad-Lite alarm.

## How can we measure success?

<!-- Do you expect errors to decrease? Do you expect user journeys to be simplified? What can be used to prove this? A filtered view of logs or analytics, etc? -->

## Have we considered potential risks?

<!-- What are the potential risks and how can they be mitigated? Does an error require an alarm? Should user help, infosec, or legal be informed of this change? Is private information guarded? Do we need to add anything in the backlog? -->

## Accessibility test checklist

-  [ ] [Tested with screen reader](https://github.com/guardian/accessibility/blob/main/people-and-technology/03-visual.md#screen-reader)
-  [ ] [Navigable with keyboard](https://github.com/guardian/accessibility/blob/main/people-and-technology/02-physical.md#keyboard)
-  [ ] [Colour contrast passed](https://github.com/guardian/accessibility/blob/main/people-and-technology/03-visual.md#contrast)
-  [ ] [The change doesn't use only colour to convey meaning](https://github.com/guardian/accessibility/blob/main/people-and-technology/03-visual.md#use-of-colour)

## Screenshots



Diff:
diff --git a/cdk/lib/__snapshots__/support-workers.test.ts.snap b/cdk/lib/__snapshots__/support-workers.test.ts.snap
index b963c4ae46..2ff0414043 100644
--- a/cdk/lib/__snapshots__/support-workers.test.ts.snap
+++ b/cdk/lib/__snapshots__/support-workers.test.ts.snap
@@ -24,6 +24,7 @@ exports[`The support-workers stack matches the snapshot 1`] = `
       "GuAlarm",
       "GuAlarm",
       "GuAlarm",
+      "GuAlarm",
     ],
     "gu:cdk:version": "TEST",
   },
@@ -1078,6 +1079,143 @@ exports[`The support-workers stack matches the snapshot 1`] = `
       },
       "Type": "AWS::IAM::Policy",
     },
+    "NoAdLiteAcquisitionInPeriodAlarm14B9FDD9": {
+      "DependsOn": [
+        "SupportWorkersPROD",
+        "SupportWorkersRoleDefaultPolicyCB757939",
+        "SupportWorkersRole33385BA0",
+      ],
+      "Properties": {
+        "ActionsEnabled": true,
+        "AlarmActions": [
+          {
+            "Fn::Join": [
+              "",
+              [
+                "arn:aws:sns:",
+                {
+                  "Ref": "AWS::Region",
+                },
+                ":",
+                {
+                  "Ref": "AWS::AccountId",
+                },
+                ":alarms-handler-topic-PROD",
+              ],
+            ],
+          },
+        ],
+        "AlarmName": "URGENT 9-5 - PROD support-workers No successful Ad-Lite checkouts in 12 hours.",
+        "ComparisonOperator": "LessThanOrEqualToThreshold",
+        "EvaluationPeriods": 144,
+        "Metrics": [
+          {
+            "Expression": "SUM([FILL(m1,0),FILL(m2,0),FILL(m3,0)])",
+            "Id": "expr_1",
+            "Label": "AllGuardianAdLiteConversions",
+          },
+          {
+            "Id": "m1",
+            "MetricStat": {
+              "Metric": {
+                "Dimensions": [
+                  {
+                    "Name": "PaymentProvider",
+                    "Value": "Stripe",
+                  },
+                  {
+                    "Name": "ProductType",
+                    "Value": "GuardianAdLite",
+                  },
+                  {
+                    "Name": "Stage",
+                    "Value": "PROD",
+                  },
+                ],
+                "MetricName": "PaymentSuccess",
+                "Namespace": "support-frontend",
+              },
+              "Period": 300,
+              "Stat": "Sum",
+            },
+            "ReturnData": false,
+          },
+          {
+            "Id": "m2",
+            "MetricStat": {
+              "Metric": {
+                "Dimensions": [
+                  {
+                    "Name": "PaymentProvider",
+                    "Value": "DirectDebit",
+                  },
+                  {
+                    "Name": "ProductType",
+                    "Value": "GuardianAdLite",
+                  },
+                  {
+                    "Name": "Stage",
+                    "Value": "PROD",
+                  },
+                ],
+                "MetricName": "PaymentSuccess",
+                "Namespace": "support-frontend",
+              },
+              "Period": 300,
+              "Stat": "Sum",
+            },
+            "ReturnData": false,
+          },
+          {
+            "Id": "m3",
+            "MetricStat": {
+              "Metric": {
+                "Dimensions": [
+                  {
+                    "Name": "PaymentProvider",
+                    "Value": "PayPal",
+                  },
+                  {
+                    "Name": "ProductType",
+                    "Value": "GuardianAdLite",
+                  },
+                  {
+                    "Name": "Stage",
+                    "Value": "PROD",
+                  },
+                ],
+                "MetricName": "PaymentSuccess",
+                "Namespace": "support-frontend",
+              },
+              "Period": 300,
+              "Stat": "Sum",
+            },
+            "ReturnData": false,
+          },
+        ],
+        "Tags": [
+          {
+            "Key": "gu:cdk:version",
+            "Value": "TEST",
+          },
+          {
+            "Key": "gu:repo",
+            "Value": "guardian/support-frontend",
+          },
+          {
+            "Key": "Stack",
+            "Value": "support",
+          },
+          {
+            "Key": "Stage",
+            "Value": "PROD",
+          },
+        ],
+        "Threshold": 0,
+        "TreatMissingData": "breaching",
+      },
+      "Type": "AWS::CloudWatch::Alarm",
+    },
     "NoGocardlessContributionsAlarmD9872C48": {
       "DependsOn": [
         "SupportWorkersPROD",
diff --git a/cdk/lib/support-workers.ts b/cdk/lib/support-workers.ts
index 11fc8e866a..59327666b0 100644
--- a/cdk/lib/support-workers.ts
+++ b/cdk/lib/support-workers.ts
@@ -28,12 +28,14 @@ import {
 import { LambdaInvoke } from "aws-cdk-lib/aws-stepfunctions-tasks";
 
 type PaymentProvider = "Stripe" | "DirectDebit" | "PayPal";
+
 type ProductType =
   | "Contribution"
   | "Paper"
   | "GuardianWeekly"
   | "SupporterPlus"
-  | "TierThree";
+  | "TierThree"
+  | "GuardianAdLite";
 
 interface SupportWorkersProps extends GuStackProps {
   promotionsDynamoTables: string[];
@@ -522,8 +524,51 @@ export class SupportWorkers extends GuStack {
       treatMissingData: TreatMissingData.BREACHING,
       threshold: 0,
     }).node.addDependency(stateMachine);
+
+    // begining of Ad lite
+
+    const adLiteMetricDuration = Duration.minutes(5);
+    const adLiteEvaluationPeriods = 144; // The number of 5 minute periods in 12 hours
+    const adLiteAlarmPeriod = Duration.minutes(
+      adLiteMetricDuration.toMinutes() * adLiteEvaluationPeriods
+    );
+
+    new GuAlarm(this, `NoAdLiteAcquisitionInPeriodAlarm`, {
+      app,
+      actionsEnabled: isProd,
+      snsTopicName: `alarms-handler-topic-${this.stage}`,
+      alarmName: `URGENT 9-5 - ${
+        this.stage
+      } support-workers No successful Ad-Lite checkouts in ${adLiteAlarmPeriod.toHumanString()}.`,
+      metric: new MathExpression({
+        label: "AllGuardianAdLiteConversions",
+        expression: "SUM([FILL(m1,0),FILL(m2,0),FILL(m3,0)])",
+        usingMetrics: {
+          m1: this.buildPaymentSuccessMetric(
+            "Stripe",
+            "GuardianAdLite",
+            adLiteMetricDuration
+          ),
+          m2: this.buildPaymentSuccessMetric(
+            "DirectDebit",
+            "GuardianAdLite",
+            adLiteMetricDuration
+          ),
+          m3: this.buildPaymentSuccessMetric(
+            "PayPal",
+            "GuardianAdLite",
+            adLiteMetricDuration
+          ),
+        },
+      }),
+      comparisonOperator: ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
+      evaluationPeriods: adLiteEvaluationPeriods,
+      treatMissingData: TreatMissingData.BREACHING,
+      threshold: 0,
+    }).node.addDependency(stateMachine);
   }
 
+  // End of an lite
   buildPaymentSuccessMetric = (
     paymentProvider: PaymentProvider,
     productType: ProductType,


Reviews:
user: login: tjmw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T12:52:56Z
user: login: maloky
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T14:39:17Z
user: login: tjmw
body: Nice! 👍 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-19T14:48:42Z
user: login: tjmw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T14:50:52Z

Review comments:
diff_hunk: @@ -522,8 +524,51 @@ export class SupportWorkers extends GuStack {
       treatMissingData: TreatMissingData.BREACHING,
       threshold: 0,
     }).node.addDependency(stateMachine);
+
+    // begining of Ad lite
+
+    const adLiteMetricDuration = Duration.minutes(5);
+    const adLiteEvaluationPeriods = 144; // The number of 5 minute periods in 12 hours
+    const adLiteAlarmPeriod = Duration.minutes(
+      adLiteMetricDuration.toMinutes() * adLiteEvaluationPeriods
+    );
+
+    new GuAlarm(this, `NoAdLiteAcquisitionInPeriodAlarm`, {
+      app,
+      actionsEnabled: isProd,
+      snsTopicName: `alarms-handler-topic-${this.stage}`,
+      alarmName: `URGENT 9-5 - ${
+        this.stage
+      } support-workers No successful Ad-Lite checkouts in ${adLiteAlarmPeriod.toHumanString()}.`,
+      metric: new MathExpression({
+        label: "AllTierThreeConversions",
path: cdk/lib/support-workers.ts
user: login: tjmw
body: A small naming thing I noticed:

```suggestion
        label: "AllGuardianAdLiteConversions",
```
created_at: 2025-03-19T12:52:55Z
author_association: MEMBER
diff_hunk: @@ -522,8 +524,51 @@ export class SupportWorkers extends GuStack {
       treatMissingData: TreatMissingData.BREACHING,
       threshold: 0,
     }).node.addDependency(stateMachine);
+
+    // begining of Ad lite
+
+    const adLiteMetricDuration = Duration.minutes(5);
+    const adLiteEvaluationPeriods = 144; // The number of 5 minute periods in 12 hours
+    const adLiteAlarmPeriod = Duration.minutes(
+      adLiteMetricDuration.toMinutes() * adLiteEvaluationPeriods
+    );
+
+    new GuAlarm(this, `NoAdLiteAcquisitionInPeriodAlarm`, {
+      app,
+      actionsEnabled: isProd,
+      snsTopicName: `alarms-handler-topic-${this.stage}`,
+      alarmName: `URGENT 9-5 - ${
+        this.stage
+      } support-workers No successful Ad-Lite checkouts in ${adLiteAlarmPeriod.toHumanString()}.`,
+      metric: new MathExpression({
+        label: "AllTierThreeConversions",
path: cdk/lib/support-workers.ts
user: login: maloky
body: nice catch!
created_at: 2025-03-19T14:39:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -522,8 +524,51 @@ export class SupportWorkers extends GuStack {
       treatMissingData: TreatMissingData.BREACHING,
       threshold: 0,
     }).node.addDependency(stateMachine);
+
+    // begining of Ad lite
+
+    const adLiteMetricDuration = Duration.minutes(5);
+    const adLiteEvaluationPeriods = 144; // The number of 5 minute periods in 12 hours
+    const adLiteAlarmPeriod = Duration.minutes(
+      adLiteMetricDuration.toMinutes() * adLiteEvaluationPeriods
+    );
+
+    new GuAlarm(this, `NoAdLiteAcquisitionInPeriodAlarm`, {
+      app,
+      actionsEnabled: isProd,
+      snsTopicName: `alarms-handler-topic-${this.stage}`,
+      alarmName: `URGENT 9-5 - ${
+        this.stage
+      } support-workers No successful Ad-Lite checkouts in ${adLiteAlarmPeriod.toHumanString()}.`,
+      metric: new MathExpression({
+        label: "AllGuardianAdLiteConversions",
+        expression: "SUM([FILL(m1,0),FILL(m2,0),FILL(m3,0)])",
+        usingMetrics: {
+          m1: this.buildPaymentSuccessMetric(
+            "Stripe",
+            "GuardianAdLite",
+            adLiteMetricDuration
+          ),
+          m2: this.buildPaymentSuccessMetric(
+            "DirectDebit",
+            "GuardianAdLite",
+            adLiteMetricDuration
+          ),
+          m3: this.buildPaymentSuccessMetric(
+            "PayPal",
+            "GuardianAdLite",
+            adLiteMetricDuration
+          ),
+        },
+      }),
+      comparisonOperator: ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
+      evaluationPeriods: adLiteEvaluationPeriods,
+      treatMissingData: TreatMissingData.BREACHING,
+      threshold: 0,
+    }).node.addDependency(stateMachine);
   }
 
+  // End of an lite
path: cdk/lib/support-workers.ts
user: login: tjmw
body: Maybe these beginning and end comments should be removed? What do you think?
created_at: 2025-03-19T14:48:30Z
author_association: MEMBER
diff_hunk: @@ -522,8 +524,51 @@ export class SupportWorkers extends GuStack {
       treatMissingData: TreatMissingData.BREACHING,
       threshold: 0,
     }).node.addDependency(stateMachine);
+
+    // begining of Ad lite
+
+    const adLiteMetricDuration = Duration.minutes(5);
+    const adLiteEvaluationPeriods = 144; // The number of 5 minute periods in 12 hours
+    const adLiteAlarmPeriod = Duration.minutes(
+      adLiteMetricDuration.toMinutes() * adLiteEvaluationPeriods
+    );
+
+    new GuAlarm(this, `NoAdLiteAcquisitionInPeriodAlarm`, {
+      app,
+      actionsEnabled: isProd,
+      snsTopicName: `alarms-handler-topic-${this.stage}`,
+      alarmName: `URGENT 9-5 - ${
+        this.stage
+      } support-workers No successful Ad-Lite checkouts in ${adLiteAlarmPeriod.toHumanString()}.`,
+      metric: new MathExpression({
+        label: "AllTierThreeConversions",
path: cdk/lib/support-workers.ts
user: login: tjmw
body: @maloky thanks for fixing this 🙌  I think the snapshot needs updating, are you happy to do that?
created_at: 2025-03-19T14:50:52Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-19T11:36:14Z
body: ### [Deploy build 13814 of `support::stripe-patrons-data` to CODE](https://riffraff.gutools.co.uk/deployment/deployAgain?project=support%3A%3Astripe-patrons-data&build=13814&stage=CODE&updateStrategy=MostlyHarmless&action=deploy)
<details>
<summary>All deployment options</summary>

- [Deploy build 13814 of `support::stripe-patrons-data` to CODE](https://riffraff.gutools.co.uk/deployment/deployAgain?project=support%3A%3Astripe-patrons-data&build=13814&stage=CODE&updateStrategy=MostlyHarmless&action=deploy)
- [Deploy parts of build 13814 to CODE by previewing it first](https://riffraff.gutools.co.uk/preview/yaml?project=support%3A%3Astripe-patrons-data&build=13814&stage=CODE&updateStrategy=MostlyHarmless)
- [What's on CODE right now?](https://riffraff.gutools.co.uk/deployment/history?projectName=support%3A%3Astripe-patrons-data&stage=CODE)
</details>

---
_From [guardian/actions-riff-raff](https://github.com/guardian/actions-riff-raff)._
<!-- guardian/actions-riff-raff for support::stripe-patrons-data -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-19T11:37:00Z
body: **Size Change:** 0 B 

**Total Size:** 1.95 MB



<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size |
| :--- | :---: |
| `./public/compiled-assets/javascripts/[countryGroupId]/events/router.js` | 92.6 kB |
| `./public/compiled-assets/javascripts/[countryGroupId]/router.js` | 98 kB |
| `./public/compiled-assets/javascripts/ausMomentMap.js` | 108 kB |
| `./public/compiled-assets/javascripts/contributionsRedirectStyles.js` | 20 B |
| `./public/compiled-assets/javascripts/digitalSubscriptionLandingPage.js` | 226 kB |
| `./public/compiled-assets/javascripts/downForMaintenancePage.js` | 69.6 kB |
| `./public/compiled-assets/javascripts/error404Page.js` | 69.5 kB |
| `./public/compiled-assets/javascripts/error500Page.js` | 69.5 kB |
| `./public/compiled-assets/javascripts/favicons.js` | 618 B |
| `./public/compiled-assets/javascripts/paperSubscriptionCheckoutPage.js` | 167 kB |
| `./public/compiled-assets/javascripts/paperSubscriptionLandingPage.js` | 89.9 kB |
| `./public/compiled-assets/javascripts/payPalErrorPage.js` | 68 kB |
| `./public/compiled-assets/javascripts/payPalErrorPageStyles.js` | 20 B |
| `./public/compiled-assets/javascripts/promotionTerms.js` | 76 kB |
| `./public/compiled-assets/javascripts/subscriptionsLandingPage.js` | 80.3 kB |
| `./public/compiled-assets/javascripts/subscriptionsRedemptionPage.js` | 121 kB |
| `./public/compiled-assets/javascripts/unsupportedBrowserStyles.js` | 20 B |
| `./public/compiled-assets/javascripts/weeklySubscriptionCheckoutPage.js` | 163 kB |
| `./public/compiled-assets/javascripts/weeklySubscriptionLandingPage.js` | 90.1 kB |
| `./public/compiled-assets/webpack/142.js` | 7.57 kB |
| `./public/compiled-assets/webpack/192.js` | 3.54 kB |
| `./public/compiled-assets/webpack/236.js` | 24.2 kB |
| `./public/compiled-assets/webpack/316.js` | 2.01 kB |
| `./public/compiled-assets/webpack/490.js` | 6.83 kB |
| `./public/compiled-assets/webpack/542.js` | 38.2 kB |
| `./public/compiled-assets/webpack/603.js` | 4.39 kB |
| `./public/compiled-assets/webpack/630.js` | 12.2 kB |
| `./public/compiled-assets/webpack/644.js` | 2.17 kB |
| `./public/compiled-assets/webpack/696.js` | 9.04 kB |
| `./public/compiled-assets/webpack/778.js` | 107 kB |
| `./public/compiled-assets/webpack/783.js` | 26 kB |
| `./public/compiled-assets/webpack/889.js` | 5.72 kB |
| `./public/compiled-assets/webpack/944.js` | 21.6 kB |
| `./public/compiled-assets/webpack/checkout.js` | 20 kB |
| `./public/compiled-assets/webpack/GuardianAdLiteLanding.js` | 8.15 kB |
| `./public/compiled-assets/webpack/LandingPage.js` | 15.3 kB |
| `./public/compiled-assets/webpack/oneTimeCheckout.js` | 6.16 kB |
| `./public/compiled-assets/webpack/ThankYou.js` | 44.8 kB |

</details>



<a href="https://github.com/preactjs/compressed-size-action"><sub>compressed-size-action</sub></a>
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-19T11:37:57Z
body: ### [Deploy build 23078 of `support:support-workers-mono` to CODE](https://riffraff.gutools.co.uk/deployment/deployAgain?project=support%3Asupport-workers-mono&build=23078&stage=CODE&updateStrategy=MostlyHarmless&action=deploy)
<details>
<summary>All deployment options</summary>

- [Deploy build 23078 of `support:support-workers-mono` to CODE](https://riffraff.gutools.co.uk/deployment/deployAgain?project=support%3Asupport-workers-mono&build=23078&stage=CODE&updateStrategy=MostlyHarmless&action=deploy)
- [Deploy parts of build 23078 to CODE by previewing it first](https://riffraff.gutools.co.uk/preview/yaml?project=support%3Asupport-workers-mono&build=23078&stage=CODE&updateStrategy=MostlyHarmless)
- [What's on CODE right now?](https://riffraff.gutools.co.uk/deployment/history?projectName=support%3Asupport-workers-mono&stage=CODE)
</details>

---
_From [guardian/actions-riff-raff](https://github.com/guardian/actions-riff-raff)._
<!-- guardian/actions-riff-raff for support:support-workers-mono -->
author_association: CONTRIBUTOR
user: login: prout-bot
created_at: 2025-03-19T15:33:55Z
body: Seen on [PROD](https://support.theguardian.com/uk/subscribe) (merged by @maloky 10 minutes and 8 seconds ago) 
- Keep an eye out for the post deployment Playwright smoke tests [github action](https://github.com/guardian/support-frontend/actions/workflows/playwright-smoke.yml)
- If the tests have failed, the [post deployment test runbook](https://github.com/guardian/support-frontend/wiki/Post-deployment-test-runbook) should help troubleshoot.


#### Sentry Release: [support-client-side](https://sentry.io/the-guardian/support-client-side/releases/84754349dbcdd7440e58f5429926d670edd40189/), [support](https://sentry.io/the-guardian/support/releases/84754349dbcdd7440e58f5429926d670edd40189/)
author_association: NONE