Repository: elastic/synthetics
Pull request #795 (2023-06-28 09:35:39)

fix: handle nested alert keys
Handle alert config in following format as well

```
heartbeat.monitors:
- type: http
  name: Todos Lightweight
  id: todos-lightweight
  enabled: true
  urls: "${devUrl}"
  schedule: '@every 3m'
  timeout: 16s
  alert:
    status.enabled: false
    tls.enabled: false
```

Edit: 

Continuation of https://github.com/elastic/synthetics/pull/785 which is related to [elastic/kibana#159697](https://github.com/elastic/kibana/pull/159697)

devcorpio: Should we support the following format, too?

```
  alert:
    status
      enabled: false
    tls
       enabled: false
```

shahzad31: > 

i think yes, let me add a use case for it. It's getting so ugly :D 

I wish yaml parser could handle it 

Patch:
diff --git a/.github/workflows/dist-tag.yml b/.github/workflows/dist-tag.yml
index 51327fc..d364632 100644
--- a/.github/workflows/dist-tag.yml
+++ b/.github/workflows/dist-tag.yml
@@ -14,21 +14,44 @@ on:
 
 jobs:
   dist-tag:
+    env:
+      VERSION: ${{ github.event.inputs.version }}
+      DIST_TAG: ${{ github.event.inputs.dist-tag }}
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v3
+
       - uses: elastic/apm-pipeline-library/.github/actions/setup-npmrc@current
         with:
           secret: secret/jenkins-ci/npmjs/elasticmachine
           vault-url: ${{ secrets.VAULT_ADDR }}
           vault-role-id: ${{ secrets.VAULT_ROLE_ID }}
           vault-secret-id: ${{ secrets.VAULT_SECRET_ID }}
+
       - uses: ./.github/actions/setup-totp
         with:
           vault-url: ${{ secrets.VAULT_ADDR }}
           vault-role-id: ${{ secrets.VAULT_ROLE_ID }}
           vault-secret-id: ${{ secrets.VAULT_SECRET_ID }}
+
+      - name: Validate version input is a valid semver
+        run: |
+            if ! [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9.]+)?(\+[a-z0-9.]+)?$ ]]; then
+                echo "Version input '${VERSION}' is not a valid semver"
+                exit 1
+            fi
+
+      - name: Validate dist-tag input is a valid dist-tag
+        run: |
+          case "$DIST_TAG" in
+            alpha|beta|latest|stack_release)
+            exit 0 ;;
+            *)
+            echo "Dist tag input '${DIST_TAG}' is not a valid dist-tag"
+            exit 1 ;;
+          esac
+
       - name: NPM dist-tag
         run: |
           NPM_PACKAGE=$(jq '.name' package.json)
-          npm dist-tag --otp=${TOTP_CODE} add ${NPM_PACKAGE}@${{ github.event.inputs.version }} ${{ github.event.inputs.dist-tag }}
+          npm dist-tag --otp=${TOTP_CODE} add "${NPM_PACKAGE}@${VERSION}" "${DIST_TAG}"
diff --git a/src/push/monitor.ts b/src/push/monitor.ts
index e36f770..7a500f5 100644
--- a/src/push/monitor.ts
+++ b/src/push/monitor.ts
@@ -260,42 +260,54 @@ export function buildMonitorFromYaml(
 
 export const parseAlertConfig = (
   config: MonitorConfig,
-  globalAlertConfig?: AlertConfig
+  gConfig?: AlertConfig
 ) => {
-  const alertConfig: AlertConfig = {};
-
-  if (config['alert.status.enabled'] !== undefined) {
-    const value = config['alert.status.enabled'];
-    delete config['alert.status.enabled'];
-    alertConfig['status'] = {
-      enabled: value,
-    };
+  // If the user has provided a global alert config, merge it with the monitor alert config
+  const status = getAlertKeyValue('status', config, gConfig);
+  const tls = getAlertKeyValue('tls', config, gConfig);
+  const result = {};
+  if (status) {
+    result['status'] = status;
+  }
+  if (tls) {
+    result['tls'] = tls;
   }
-  if (config['alert.tls.enabled'] !== undefined) {
-    const value = config['alert.tls.enabled'];
-    delete config['alert.tls.enabled'];
-    alertConfig['tls'] = {
-      enabled: value,
+  return Object.keys(result).length > 0 ? result : undefined;
+};
+
+export function getAlertKeyValue(
+  key: 'status' | 'tls',
+  config: MonitorConfig,
+  alertConfig?: AlertConfig
+): { enabled: boolean } {
+  const value = config.alert;
+  if (value?.[key]?.enabled !== undefined) {
+    return {
+      enabled: value[key].enabled,
     };
   }
-  if (config?.alert?.status?.enabled !== undefined) {
-    alertConfig.status = {
-      enabled: config.alert.status.enabled,
+
+  if (value?.[`${key}.enabled`] !== undefined) {
+    const val = value?.[`${key}.enabled`];
+    delete value?.[`${key}.enabled`];
+    if (Object.keys(value).length === 0) {
+      delete config.alert;
+    }
+    return {
+      enabled: val,
     };
   }
-  if (config?.alert?.tls?.enabled !== undefined) {
-    alertConfig.tls = {
-      enabled: config.alert.tls.enabled,
+  const rootKey = `alert.${key}.enabled`;
+  if (config[rootKey] !== undefined) {
+    const enabled = config[rootKey];
+    delete config[rootKey];
+    return {
+      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;
-};
+  return alertConfig?.[key];
+}
 
 export function parseSchedule(schedule: string) {
   const EVERY_SYNTAX = '@every';


Test patch:
diff --git a/__tests__/push/monitor.test.ts b/__tests__/push/monitor.test.ts
index c5bfb01..6b7c0a3 100644
--- a/__tests__/push/monitor.test.ts
+++ b/__tests__/push/monitor.test.ts
@@ -132,58 +132,6 @@ describe('Monitors', () => {
     expect(parseSchedule('@every 1d')).toBe(240);
   });
 
-  it('parse alert config option', async () => {
-    expect(parseAlertConfig({})).toBe(undefined);
-    expect(parseAlertConfig({ 'alert.status.enabled': true } as any)).toEqual({
-      status: { enabled: true },
-    });
-    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', () => {
     const PROJECT_DIR = generateTempPath();
     const HB_SOURCE = join(PROJECT_DIR, 'heartbeat.yml');
@@ -406,4 +354,102 @@ heartbeat.monitors:
       });
     });
   });
+
+  describe('parseAlertConfig', () => {
+    it('parse alert config option', async () => {
+      expect(parseAlertConfig({})).toBe(undefined);
+      expect(parseAlertConfig({ 'alert.status.enabled': true } as any)).toEqual(
+        {
+          status: { enabled: true },
+        }
+      );
+      expect(
+        parseAlertConfig({ alert: { status: { enabled: true } } })
+      ).toEqual({
+        status: { enabled: true },
+      });
+    });
+
+    it('parse alert config option when global config is also provided', async () => {
+      expect(
+        parseAlertConfig(
+          { alert: { 'status.enabled': false, 'tls.enabled': true } } as any,
+          {
+            status: { enabled: false },
+            tls: { enabled: false },
+          }
+        )
+      ).toEqual({
+        status: { enabled: false },
+        tls: {
+          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 },
+      });
+    });
+
+    it('deletes parsed keys from config', async () => {
+      let config: any = {
+        'alert.status.enabled': true,
+        'alert.tls.enabled': true,
+      };
+      expect(parseAlertConfig(config)).toEqual({
+        status: { enabled: true },
+        tls: { enabled: true },
+      });
+      expect(config).toEqual({});
+
+      config = {
+        alert: {
+          'status.enabled': true,
+          'tls.enabled': true,
+        },
+      };
+      expect(parseAlertConfig(config)).toEqual({
+        status: { enabled: true },
+        tls: { enabled: true },
+      });
+      expect(config).toEqual({});
+    });
+  });
 });