Repository: elastic/synthetics
Pull request #785 (2023-06-15 10:32:12)

feat: support tls alert key for project monitors
Support tls alert key

```
heartbeat.monitors:
- type: http
  name: Todos Lightweight
  id: todos-lightweight
  enabled: true
  urls: "https://elastic.github.io/synthetics-demo/"
  schedule: '@every 3m'
  timeout: 16s
  alert.status.enabled: true
  alert.tls.enabled: true
```



Patch:
diff --git a/package-lock.json b/package-lock.json
index d8699d5..2ced233 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@elastic/synthetics",
-  "version": "1.1.0",
+  "version": "1.2.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "@elastic/synthetics",
-      "version": "1.1.0",
+      "version": "1.2.0",
       "license": "MIT",
       "dependencies": {
         "archiver": "^5.3.1",
@@ -21,7 +21,7 @@
         "pirates": "^4.0.5",
         "playwright-chromium": "=1.35.0",
         "playwright-core": "=1.35.0",
-        "semver": "^7.5.0",
+        "semver": "^7.5.2",
         "sharp": "^0.32.0",
         "snakecase-keys": "^4.0.1",
         "sonic-boom": "^3.3.0",
@@ -5615,9 +5615,9 @@
       "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
     },
     "node_modules/semver": {
-      "version": "7.5.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
-      "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
+      "version": "7.5.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
+      "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
       "dependencies": {
         "lru-cache": "^6.0.0"
       },
@@ -10717,9 +10717,9 @@
       "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
     },
     "semver": {
-      "version": "7.5.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
-      "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
+      "version": "7.5.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
+      "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
       "requires": {
         "lru-cache": "^6.0.0"
       }
diff --git a/package.json b/package.json
index 1529bd9..c3749e8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@elastic/synthetics",
-  "version": "1.1.0",
+  "version": "1.2.0",
   "description": "Elastic synthetic monitoring agent",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",
@@ -52,7 +52,7 @@
     "pirates": "^4.0.5",
     "playwright-chromium": "=1.35.0",
     "playwright-core": "=1.35.0",
-    "semver": "^7.5.0",
+    "semver": "^7.5.2",
     "sharp": "^0.32.0",
     "snakecase-keys": "^4.0.1",
     "sonic-boom": "^3.3.0",
diff --git a/src/common_types.ts b/src/common_types.ts
index 17cad07..1ac5e1a 100644
--- a/src/common_types.ts
+++ b/src/common_types.ts
@@ -34,7 +34,7 @@ import {
 } from 'playwright-chromium';
 import { Step } from './dsl';
 import { BuiltInReporterName, ReporterInstance } from './reporters';
-import { MonitorConfig } from './dsl/monitor';
+import { AlertConfig, MonitorConfig } from './dsl/monitor';
 
 export type VoidCallback = () => void;
 export type Location = {
@@ -212,6 +212,7 @@ type BaseArgs = {
   schedule?: MonitorConfig['schedule'];
   locations?: MonitorConfig['locations'];
   privateLocations?: MonitorConfig['privateLocations'];
+  alert?: AlertConfig;
 };
 
 export type CliArgs = BaseArgs & {
@@ -248,6 +249,7 @@ export type PushOptions = Partial<ProjectSettings> & {
   pattern?: string;
   match?: string;
   tags?: Array<string>;
+  alert?: AlertConfig;
 };
 
 export type ProjectSettings = {
diff --git a/src/dsl/monitor.ts b/src/dsl/monitor.ts
index 7cad26d..0c664b3 100644
--- a/src/dsl/monitor.ts
+++ b/src/dsl/monitor.ts
@@ -44,6 +44,15 @@ export const ALLOWED_SCHEDULES = [
   1, 3, 5, 10, 15, 20, 30, 60, 120, 240,
 ] as const;
 
+export interface AlertConfig {
+  status?: {
+    enabled: boolean;
+  };
+  tls?: {
+    enabled: boolean;
+  };
+}
+
 export type MonitorConfig = {
   id?: string;
   name?: string;
@@ -62,11 +71,7 @@ export type MonitorConfig = {
   screenshot?: ScreenshotOptions;
   params?: Params;
   playwrightOptions?: PlaywrightOptions;
-  alert?: {
-    status: {
-      enabled: boolean;
-    };
-  };
+  alert?: AlertConfig;
 };
 
 type MonitorFilter = {
diff --git a/src/options.ts b/src/options.ts
index bcb9ca4..afb28ad 100644
--- a/src/options.ts
+++ b/src/options.ts
@@ -130,9 +130,9 @@ export function normalizeOptions(
       options.locations = options.locations ?? monitor?.locations;
       options.privateLocations =
         options.privateLocations ?? monitor?.privateLocations;
+      options.alert = options.alert ?? monitor?.alert;
       break;
   }
-
   return options;
 }
 
diff --git a/src/push/monitor.ts b/src/push/monitor.ts
index ca3a8e4..e36f770 100644
--- a/src/push/monitor.ts
+++ b/src/push/monitor.ts
@@ -30,7 +30,12 @@ import { bold, red } from 'kleur/colors';
 import { Bundler } from './bundler';
 import { SYNTHETICS_PATH, totalist, indent, warn, isMatch } from '../helpers';
 import { LocationsMap } from '../locations/public-locations';
-import { ALLOWED_SCHEDULES, Monitor, MonitorConfig } from '../dsl/monitor';
+import {
+  AlertConfig,
+  ALLOWED_SCHEDULES,
+  Monitor,
+  MonitorConfig,
+} from '../dsl/monitor';
 import { PushOptions } from '../common_types';
 import { isParamOptionSupported } from './utils';
 
@@ -234,8 +239,7 @@ export function buildMonitorFromYaml(
   const privateLocations =
     config['private_locations'] || options.privateLocations;
   delete config['private_locations'];
-
-  const alertConfig = parseAlertConfig(config);
+  const alertConfig = parseAlertConfig(config, options.alert);
   const mon = new Monitor({
     locations: options.locations,
     ...config,
@@ -254,17 +258,43 @@ export function buildMonitorFromYaml(
   return mon;
 }
 
-export const parseAlertConfig = (config: MonitorConfig) => {
+export const parseAlertConfig = (
+  config: MonitorConfig,
+  globalAlertConfig?: AlertConfig
+) => {
+  const alertConfig: AlertConfig = {};
+
   if (config['alert.status.enabled'] !== undefined) {
     const value = config['alert.status.enabled'];
     delete config['alert.status.enabled'];
-    return {
-      status: {
-        enabled: value,
-      },
+    alertConfig['status'] = {
+      enabled: value,
+    };
+  }
+  if (config['alert.tls.enabled'] !== undefined) {
+    const value = config['alert.tls.enabled'];
+    delete config['alert.tls.enabled'];
+    alertConfig['tls'] = {
+      enabled: value,
     };
   }
-  return config.alert;
+  if (config?.alert?.status?.enabled !== undefined) {
+    alertConfig.status = {
+      enabled: config.alert.status.enabled,
+    };
+  }
+  if (config?.alert?.tls?.enabled !== undefined) {
+    alertConfig.tls = {
+      enabled: config.alert.tls.enabled,
+    };
+  }
+
+  // If the user has provided a global alert config, merge it with the monitor alert config
+  const result = {
+    ...(globalAlertConfig ?? {}),
+    ...alertConfig,
+  };
+  return Object.keys(result).length > 0 ? result : undefined;
 };
 
 export function parseSchedule(schedule: string) {


Test patch:
diff --git a/__tests__/fixtures/synthetics.config.ts b/__tests__/fixtures/synthetics.config.ts
index 2a3320b..843220b 100644
--- a/__tests__/fixtures/synthetics.config.ts
+++ b/__tests__/fixtures/synthetics.config.ts
@@ -39,6 +39,14 @@ module.exports = env => {
       schedule: 10,
       locations: ['us_east'],
       privateLocations: ['test-location'],
+      alert: {
+        status: {
+          enabled: true,
+        },
+        tls: {
+          enabled: false,
+        },
+      },
     },
   };
   if (env !== 'development' && config.params) {
diff --git a/__tests__/options.test.ts b/__tests__/options.test.ts
index 71d49d8..8a251ef 100644
--- a/__tests__/options.test.ts
+++ b/__tests__/options.test.ts
@@ -83,6 +83,14 @@ describe('options', () => {
       schedule: 10,
       privateLocations: ['test-location'],
       locations: ['us_east'],
+      alert: {
+        status: {
+          enabled: true,
+        },
+        tls: {
+          enabled: false,
+        },
+      },
     });
 
     expect(
@@ -101,6 +109,14 @@ describe('options', () => {
       schedule: 3,
       privateLocations: ['test'],
       locations: ['australia_east'],
+      alert: {
+        status: {
+          enabled: true,
+        },
+        tls: {
+          enabled: false,
+        },
+      },
     });
   });
 });
diff --git a/__tests__/push/monitor.test.ts b/__tests__/push/monitor.test.ts
index 5c8bfa6..c5bfb01 100644
--- a/__tests__/push/monitor.test.ts
+++ b/__tests__/push/monitor.test.ts
@@ -140,6 +140,48 @@ describe('Monitors', () => {
     expect(parseAlertConfig({ alert: { status: { enabled: true } } })).toEqual({
       status: { enabled: true },
     });
+
+    expect(
+      parseAlertConfig(
+        { alert: { status: { enabled: true } } },
+        {
+          status: { enabled: false },
+          tls: { enabled: true },
+        }
+      )
+    ).toEqual({
+      status: { enabled: true },
+      tls: {
+        enabled: true,
+      },
+    });
+  });
+
+  it('parse tls alert config option', async () => {
+    expect(parseAlertConfig({})).toBe(undefined);
+    expect(
+      parseAlertConfig({
+        'alert.status.enabled': true,
+        'alert.tls.enabled': true,
+      } as any)
+    ).toEqual({
+      status: { enabled: true },
+      tls: { enabled: true },
+    });
+    expect(parseAlertConfig({ 'alert.tls.enabled': true } as any)).toEqual({
+      tls: { enabled: true },
+    });
+    expect(
+      parseAlertConfig(
+        { alert: { tls: { enabled: true } } },
+        {
+          status: { enabled: false },
+        }
+      )
+    ).toEqual({
+      tls: { enabled: true },
+      status: { enabled: false },
+    });
   });
 
   describe('Lightweight monitors', () => {