Repository: elastic/synthetics (ts)

Issue:
`push` does not send params for lightweight monitors
When pushing lightweight project monitors, it does not include the params defined in either `synthetics.config.ts` or from `--params`.  With the implementation added in https://github.com/elastic/kibana/issues/147467, that means the behaviour is different between browser monitors and lightweight monitors.

i.e. The `push` command should also include the params defined as they can be for browser monitors (config file, `--params`) when sending to Kibana.  The priority of the solution should be the same as for browser monitors (see https://www.elastic.co/guide/en/observability/master/synthetics-params-secrets.html#synthetics-basic-params), i.e.

1. Global params in Kibana
2. Then `synthetics.config.ts`
3. Then `--params` defined in `push`

Ensure the order remains consistent and as defined above with both browser and lightweight monitors after this change.

Pull request description:
feat: support params for lw monitors with push
+ fix #725 
+ Add support for pushing params for Lightweight monitors when used in
    - Synthetics config file 
    - CLI flags `--params {"foo": "bar"}`
 + Added a backwards compatibility check to drop `params` config if the push is performed on stack `<8.7.2`. 
+ Check the changes with Kibana PR - https://github.com/elastic/kibana/pull/157226

Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4c36557..cd2eb7c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -27,9 +27,9 @@ jobs:
           vault-role-id: ${{ secrets.VAULT_ROLE_ID }}
           vault-secret-id: ${{ secrets.VAULT_SECRET_ID }}
       - run: npm ci # runs npm prepublish
-      - name: Bump the beta version
+      - name: Bump the version
         run: |
-          echo "VERSION=$(npm version prerelease --preid=beta)" >> $GITHUB_ENV
+          echo "VERSION=$(npm version)" >> $GITHUB_ENV
           echo "NPM_PACKAGE=$(jq '.name' package.json) >> $GITHUB_ENV
       - run: git push --tags
       - uses: ./.github/actions/setup-totp
@@ -38,7 +38,7 @@ jobs:
           vault-role-id: ${{ secrets.VAULT_ROLE_ID }}
           vault-secret-id: ${{ secrets.VAULT_SECRET_ID }}
       - name: NPM Publish
-        run: npm publish --otp=${TOTP_CODE} --tag beta
+        run: npm publish --otp=${TOTP_CODE}
       - name: Create 'latest' dist-tag
         run: npm dist-tag --otp=${TOTP_CODE} add "${NPM_PACKAGE}@${VERSION}" latest
       - name: Create 'stack_release' dist-tag
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9a043d8..f8f0be4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -140,8 +140,6 @@ We run end to end tests for the Synthetics agent itself, as well as the Elastic
 
 ### Releasing
 
-**NOTE: This project is currently in Beta phase**
-
 #### CI based
 
 The release process is also automated in the way any specific commit from the main branch can be potentially released, for such it's required the below steps:
diff --git a/README.md b/README.md
index 8ac68c1..4eb1f1d 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,9 @@
 [![ci](https://github.com/elastic/synthetics/actions/workflows/ci.yml/badge.svg)](https://github.com/elastic/synthetics/actions/workflows/ci.yml)
 [![e2e](https://github.com/elastic/synthetics/actions/workflows/e2e.yml/badge.svg)](https://github.com/elastic/synthetics/actions/workflows/e2e.yml)
 
-# Experimental Synthetics Agent
+# Synthetics Agent
 
 Synthetic Monitoring with Real Browsers.
-**Please note this is project is in beta status. Expect things to break and change!**
 
 ## Usage
 
diff --git a/package-lock.json b/package-lock.json
index d0c16e4..c5f2945 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@elastic/synthetics",
-  "version": "1.0.0-beta.46",
+  "version": "1.0.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "@elastic/synthetics",
-      "version": "1.0.0-beta.46",
+      "version": "1.0.0",
       "license": "MIT",
       "dependencies": {
         "archiver": "^5.3.1",
diff --git a/package.json b/package.json
index 4d1c681..ef2525c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@elastic/synthetics",
-  "version": "1.0.0-beta.46",
+  "version": "1.0.0",
   "description": "Elastic synthetic monitoring agent",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",
diff --git a/src/cli.ts b/src/cli.ts
index be5bbbd..ce5ea1b 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -56,6 +56,7 @@ import { Generator } from './generator';
 import { error } from './helpers';
 import { LocationsMap } from './locations/public-locations';
 import { createLightweightMonitors } from './push/monitor';
+import { getVersion } from './push/kibana_api';
 
 /* eslint-disable-next-line @typescript-eslint/no-var-requires */
 const { name, version } = require('../package.json');
@@ -214,6 +215,7 @@ program
       if ((options as CliArgs).throttling == null) {
         warnIfThrottled(monitors);
       }
+      options.kibanaVersion = await getVersion(options);
       monitors.push(...(await createLightweightMonitors(workDir, options)));
       await push(monitors, options);
     } catch (e) {
diff --git a/src/common_types.ts b/src/common_types.ts
index ad5e54f..8b5b540 100644
--- a/src/common_types.ts
+++ b/src/common_types.ts
@@ -244,6 +244,8 @@ export type PushOptions = Partial<ProjectSettings> & {
   schedule?: MonitorConfig['schedule'];
   locations?: MonitorConfig['locations'];
   privateLocations?: MonitorConfig['privateLocations'];
+  params?: MonitorConfig['params'];
+  kibanaVersion?: number;
   yes?: boolean;
   pattern?: string;
   match?: string;
diff --git a/src/helpers.ts b/src/helpers.ts
index 4c55423..2e6b6ae 100644
--- a/src/helpers.ts
+++ b/src/helpers.ts
@@ -389,7 +389,7 @@ export function warn(message: string) {
   write(bold(yellow(`${symbols['warning']} ${message}`)));
 }
 
-export function removeTrailingSlash(url: string) {
+export function removeTrailingSlash(url = '') {
   return url.replace(/\/+$/, '');
 }
 
diff --git a/src/index.ts b/src/index.ts
index 2fd5232..56bed84 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -66,6 +66,7 @@ export type { SyntheticsConfig } from './common_types';
 export type {
   Action,
   ActionInContext,
+  FrameDescription,
   Signal,
   Step,
   Steps,
diff --git a/src/push/index.ts b/src/push/index.ts
index f6ffb07..2c6f228 100644
--- a/src/push/index.ts
+++ b/src/push/index.ts
@@ -22,7 +22,6 @@
  * THE SOFTWARE.
  *
  */
-import semver from 'semver';
 import { readFile, writeFile } from 'fs/promises';
 import { prompt } from 'enquirer';
 import { bold, grey } from 'kleur/colors';
@@ -50,11 +49,15 @@ import {
   bulkDeleteMonitors,
   bulkGetMonitors,
   bulkPutMonitors,
-  getVersion,
   createMonitorsLegacy,
   CHUNK_SIZE,
 } from './kibana_api';
-import { getChunks, logDiff } from './utils';
+import {
+  getChunks,
+  isBulkAPISupported,
+  isLightweightMonitorSupported,
+  logDiff,
+} from './utils';
 
 export async function push(monitors: Monitor[], options: PushOptions) {
   const duplicates = trackDuplicates(monitors);
@@ -63,10 +66,11 @@ export async function push(monitors: Monitor[], options: PushOptions) {
   }
   progress(`Pushing monitors for project: ${options.id}`);
 
-  const stackVersion = await getVersion(options);
-  const isV2 = semver.satisfies(stackVersion, '>=8.6.0');
-  if (!isV2) {
-    return await pushLegacy(monitors, options, stackVersion);
+  /**
+   * Legacy API for kibana which does not support bulk operations
+   */
+  if (!isBulkAPISupported(options.kibanaVersion)) {
+    return await pushLegacy(monitors, options);
   }
 
   const local = getLocalMonitors(monitors);
@@ -245,18 +249,10 @@ export async function catchIncorrectSettings(
   }
 }
 
-export async function pushLegacy(
-  monitors: Monitor[],
-  options: PushOptions,
-  version: number
-) {
-  const noLightWeightSupport = semver.satisfies(version, '<8.5.0');
-  if (
-    noLightWeightSupport &&
-    monitors.some(monitor => monitor.type !== 'browser')
-  ) {
+export async function pushLegacy(monitors: Monitor[], options: PushOptions) {
+  if (isLightweightMonitorSupported(monitors, options)) {
     throw error(
-      `Aborted: Lightweight monitors are not supported in ${version}. Please upgrade to 8.5.0 or above.`
+      `Aborted: Lightweight monitors are not supported in ${options.kibanaVersion}. Please upgrade to 8.5.0 or above.`
     );
   }
 
diff --git a/src/push/monitor.ts b/src/push/monitor.ts
index a3549a2..ca3a8e4 100644
--- a/src/push/monitor.ts
+++ b/src/push/monitor.ts
@@ -32,6 +32,7 @@ import { SYNTHETICS_PATH, totalist, indent, warn, isMatch } from '../helpers';
 import { LocationsMap } from '../locations/public-locations';
 import { ALLOWED_SCHEDULES, Monitor, MonitorConfig } from '../dsl/monitor';
 import { PushOptions } from '../common_types';
+import { isParamOptionSupported } from './utils';
 
 // Allowed extensions for lightweight monitor files
 const ALLOWED_LW_EXTENSIONS = ['.yml', '.yaml'];
@@ -235,14 +236,22 @@ export function buildMonitorFromYaml(
   delete config['private_locations'];
 
   const alertConfig = parseAlertConfig(config);
-
-  return new Monitor({
+  const mon = new Monitor({
     locations: options.locations,
     ...config,
     privateLocations,
     schedule: schedule || options.schedule,
     alert: alertConfig,
   });
+
+  /**
+   * Params support is only available for lighweight monitors
+   * post 8.7.2 stack
+   */
+  if (isParamOptionSupported(options.kibanaVersion)) {
+    mon.config.params = options.params;
+  }
+  return mon;
 }
 
 export const parseAlertConfig = (config: MonitorConfig) => {
diff --git a/src/push/utils.ts b/src/push/utils.ts
index a9d4f59..bff826d 100644
--- a/src/push/utils.ts
+++ b/src/push/utils.ts
@@ -23,9 +23,11 @@
  *
  */
 
+import semver from 'semver';
 import { progress, removeTrailingSlash } from '../helpers';
 import { green, red, grey, yellow } from 'kleur/colors';
 import { PushOptions } from '../common_types';
+import { Monitor } from '../dsl/monitor';
 
 export function logDiff<T extends Set<string>>(
   newIDs: T,
@@ -75,3 +77,32 @@ export function generateURL(options: PushOptions, operation: Operation) {
       throw new Error('Invalid operation');
   }
 }
+
+/**
+ * Bulk API is supported for push monitors only from 8.6.0 and above
+ */
+export function isBulkAPISupported(version: number) {
+  return semver.satisfies(version, '>=8.6.0');
+}
+
+/**
+ * Lightweight monitors are supported only from 8.5.0 and above
+ */
+export function isLightweightMonitorSupported(
+  monitors: Monitor[],
+  options: PushOptions
+) {
+  const version = options.kibanaVersion;
+  return (
+    semver.satisfies(version, '<8.5.0') &&
+    monitors.some(monitor => monitor.type !== 'browser')
+  );
+}
+
+/**
+ * Lighweight monitor param options are supported only from
+ * 8.7.2 and above
+ */
+export function isParamOptionSupported(version: number) {
+  return semver.satisfies(version, '>=8.7.2');
+}


Test patch:
diff --git a/__tests__/push/index.test.ts b/__tests__/push/index.test.ts
index 2083178..a7a52f3 100644
--- a/__tests__/push/index.test.ts
+++ b/__tests__/push/index.test.ts
@@ -34,6 +34,7 @@ import { CLIMock } from '../utils/test-config';
 import { THROTTLING_WARNING_MSG } from '../../src/helpers';
 
 describe('Push', () => {
+  let server: Server;
   const PROJECT_DIR = join(__dirname, 'new-project');
   const DEFAULT_ARGS = ['--auth', 'foo'];
 
@@ -55,10 +56,12 @@ describe('Push', () => {
   }
 
   beforeAll(async () => {
+    server = await createKibanaTestServer('8.6.0');
     await mkdir(PROJECT_DIR, { recursive: true });
   });
   afterAll(async () => {
     process.env.NO_COLOR = '';
+    await server.close();
     await rm(PROJECT_DIR, { recursive: true, force: true });
   });
 
@@ -134,7 +137,7 @@ journey('journey 1', () => monitor.use({ id: 'j1', schedule: 8 }));`
 
   it('errors on duplicate browser monitors', async () => {
     await fakeProjectSetup(
-      { id: 'test-project' },
+      { id: 'test-project', space: 'dummy', url: server.PREFIX },
       { locations: ['test-loc'], schedule: 3 }
     );
 
@@ -173,7 +176,7 @@ journey('duplicate name', () => monitor.use({ schedule: 15 }));`
 
   it('errors on duplicate lightweight monitors', async () => {
     await fakeProjectSetup(
-      { id: 'test-project' },
+      { id: 'test-project', space: 'dummy', url: server.PREFIX },
       { locations: ['test-loc'], schedule: 3 }
     );
 
diff --git a/__tests__/push/monitor.test.ts b/__tests__/push/monitor.test.ts
index add64b9..5c8bfa6 100644
--- a/__tests__/push/monitor.test.ts
+++ b/__tests__/push/monitor.test.ts
@@ -261,6 +261,35 @@ heartbeat.monitors:
       });
     });
 
+    it('use params from config', async () => {
+      await writeHBFile(`
+heartbeat.monitors:
+- type: icmp
+  id: foo
+  name: without schedule
+      `);
+      const [monitor1] = await createLightweightMonitors(
+        PROJECT_DIR,
+        {} as PushOptions
+      );
+      expect(monitor1.config).toMatchObject({
+        id: 'foo',
+        name: 'without schedule',
+        type: 'icmp',
+      });
+
+      const [monitor2] = await createLightweightMonitors(PROJECT_DIR, {
+        params: { foo: 'bar' },
+        kibanaVersion: '8.8.0',
+      } as any);
+      expect(monitor2.config).toEqual({
+        id: 'foo',
+        name: 'without schedule',
+        type: 'icmp',
+        params: { foo: 'bar' },
+      });
+    });
+
     it('parses monitor config correctly', async () => {
       await writeHBFile(`
 heartbeat.monitors:
diff --git a/__tests__/utils/kibana-test-server.ts b/__tests__/utils/kibana-test-server.ts
index cb32340..09a0d71 100644
--- a/__tests__/utils/kibana-test-server.ts
+++ b/__tests__/utils/kibana-test-server.ts
@@ -30,7 +30,7 @@ import {
 } from '../../src/push/kibana_api';
 
 export const createKibanaTestServer = async (kibanaVersion: string) => {
-  const server = await Server.create({ port: 54455 });
+  const server = await Server.create({ port: 0 });
   server.route('/s/dummy/api/status', (req, res) =>
     res.end(JSON.stringify({ version: { number: kibanaVersion } }))
   );