Skip to content

Commit 9f7f6fc

Browse files
authored
feat: support multi space monitors !! (#1052)
* handle multi spaces * Update src/push/kibana_api.ts * PR feedback * update tests * update to emit empty spaces * update tests * add unit tests
1 parent 0ee5e94 commit 9f7f6fc

File tree

8 files changed

+151
-2
lines changed

8 files changed

+151
-2
lines changed

__tests__/core/runner.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,7 @@ describe('runner', () => {
767767
locations: ['united_kingdom'],
768768
schedule: 3,
769769
throttling: { download: 5, latency: 20, upload: 3 },
770+
spaces: [],
770771
});
771772
expect(monitors[1].config).toMatchObject({
772773
throttling: { latency: 1000 },
@@ -824,6 +825,7 @@ describe('runner', () => {
824825
alert: { tls: { enabled: true } },
825826
retestOnFailure: true,
826827
fields: { area: 'website' },
828+
spaces: [],
827829
});
828830
expect(monitors[1].config).toMatchObject({
829831
locations: ['us_east'],

__tests__/push/monitor.test.ts

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
parseAlertConfig,
3434
parseFields,
3535
parseSchedule,
36+
parseSpaces,
3637
} from '../../src/push/monitor';
3738
import { Server } from '../utils/server';
3839
import { createTestMonitor } from '../utils/test-config';
@@ -85,6 +86,7 @@ describe('Monitors', () => {
8586
locations: ['europe-west2-a', 'australia-southeast1-a'],
8687
privateLocations: ['germany'],
8788
fields: { area: 'website' },
89+
spaces: ['test'],
8890
});
8991
});
9092

@@ -105,8 +107,12 @@ describe('Monitors', () => {
105107
match: 'test',
106108
},
107109
fields: { area: 'website' },
110+
spaces: ['test'],
111+
});
112+
monitor.update({
113+
locations: ['brazil'],
114+
fields: { env: 'dev' },
108115
});
109-
monitor.update({ locations: ['brazil'], fields: { env: 'dev' } });
110116
const { schemas: schemas1 } = await buildMonitorSchema([monitor], true);
111117
expect(schemas1[0].hash).not.toEqual(schemas[0].hash);
112118
expect(schemas1[0].fields).toEqual({
@@ -497,6 +503,57 @@ heartbeat.monitors:
497503
`);
498504

499505
const [mon] = await createLightweightMonitors(PROJECT_DIR, {
506+
space: 'default',
507+
auth: 'foo',
508+
params: { foo: 'bar' },
509+
kibanaVersion: '8.8.0',
510+
locations: ['australia_east'],
511+
tags: ['gtag1', 'gtag2'],
512+
privateLocations: ['gbaz'],
513+
schedule: 10,
514+
retestOnFailure: false,
515+
spaces: ['default'],
516+
});
517+
518+
expect(mon.config).toEqual({
519+
id: 'test-icmp',
520+
name: 'test-icmp',
521+
locations: ['australia_east'],
522+
privateLocations: ['baz'],
523+
type: 'icmp',
524+
params: { foo: 'bar' },
525+
schedule: 5,
526+
tags: ['ltag1', 'ltag2'],
527+
retestOnFailure: false,
528+
fields: {
529+
baz: 'qux',
530+
foo: 'bar',
531+
},
532+
spaces: ['default'],
533+
});
534+
});
535+
536+
it('supports spaces in config', async () => {
537+
await writeHBFile(`
538+
heartbeat.monitors:
539+
- type: icmp
540+
schedule: @every 5m
541+
id: "test-icmp"
542+
name: "test-icmp"
543+
privateLocations:
544+
- baz
545+
tags:
546+
- ltag1
547+
- ltag2
548+
fields.foo: bar
549+
fields.baz: qux
550+
spaces:
551+
- space1
552+
- space2
553+
`);
554+
555+
const [mon] = await createLightweightMonitors(PROJECT_DIR, {
556+
space: 'default',
500557
auth: 'foo',
501558
params: { foo: 'bar' },
502559
kibanaVersion: '8.8.0',
@@ -521,6 +578,7 @@ heartbeat.monitors:
521578
baz: 'qux',
522579
foo: 'bar',
523580
},
581+
spaces: ['default', 'space1', 'space2'],
524582
});
525583
});
526584
});
@@ -677,4 +735,58 @@ heartbeat.monitors:
677735
expect(result).toBeUndefined();
678736
});
679737
});
738+
739+
describe('parseSpaces', () => {
740+
it('returns empty object if no spaces are defined', () => {
741+
expect(parseSpaces({}, {} as any)).toEqual({});
742+
expect(parseSpaces({ spaces: undefined }, {} as any)).toEqual({});
743+
expect(parseSpaces({}, { spaces: undefined } as any)).toEqual({});
744+
});
745+
746+
it('merges config and options spaces, including global space', () => {
747+
const config = { spaces: ['space1'] };
748+
const options = { spaces: ['space2'], space: 'global' };
749+
expect(parseSpaces(config, options as any)).toEqual({
750+
spaces: ['global', 'space1', 'space2'],
751+
});
752+
});
753+
754+
it('handles only config spaces', () => {
755+
expect(parseSpaces({ spaces: ['foo'] }, {} as any)).toEqual({
756+
spaces: ['foo'],
757+
});
758+
});
759+
760+
it('handles only options spaces', () => {
761+
expect(parseSpaces({}, { spaces: ['bar'] } as any)).toEqual({
762+
spaces: ['bar'],
763+
});
764+
});
765+
766+
it('handles global space only', () => {
767+
expect(parseSpaces({}, { space: 'default' } as any)).toEqual({});
768+
});
769+
770+
it('deduplicates spaces', () => {
771+
const config = { spaces: ['space1', 'space2'] };
772+
const options = { spaces: ['space2', 'space3'], space: 'space1' };
773+
expect(parseSpaces(config, options as any)).toEqual({
774+
spaces: ['space1', 'space2', 'space3'],
775+
});
776+
});
777+
778+
it('returns wildcard if present', () => {
779+
expect(
780+
parseSpaces({ spaces: ['*'] }, {
781+
space: 'default',
782+
spaces: ['foo'],
783+
} as any)
784+
).toEqual({
785+
spaces: ['*'],
786+
});
787+
expect(parseSpaces({}, { spaces: ['*'] } as any)).toEqual({
788+
spaces: ['*'],
789+
});
790+
});
791+
});
680792
});

__tests__/utils/test-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function createTestMonitor(filename: string, type = 'browser') {
4343
locations: ['united_kingdom', 'australia_east'],
4444
privateLocations: ['germany'],
4545
fields: { area: 'website' },
46+
spaces: ['test'],
4647
});
4748
monitor.setSource({
4849
file: join(FIXTURES_DIR, filename),

src/common_types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ type BaseArgs = {
224224
locations?: MonitorConfig['locations'];
225225
privateLocations?: MonitorConfig['privateLocations'];
226226
fields?: MonitorConfig['fields'];
227+
spaces?: MonitorConfig['spaces'];
227228
};
228229

229230
export type CliArgs = BaseArgs & {
@@ -263,6 +264,7 @@ export type PushOptions = Partial<ProjectSettings> &
263264
retestOnFailure?: MonitorConfig['retestOnFailure'];
264265
enabled?: boolean;
265266
grepOpts?: GrepOptions;
267+
spaces?: MonitorConfig['spaces'];
266268
};
267269

268270
export type ProjectSettings = {

src/core/runner.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { PerformanceManager, filterBrowserMessages } from '../plugins';
4949
import { Gatherer } from './gatherer';
5050
import { log } from './logger';
5151
import { Monitor, MonitorConfig } from '../dsl/monitor';
52+
import { parseSpaces } from '../push/monitor';
5253

5354
type HookType = 'beforeAll' | 'afterAll';
5455
export type SuiteHooks = Record<HookType, Array<HooksCallback>>;
@@ -443,6 +444,7 @@ export default class Runner implements RunnerInfo {
443444
retestOnFailure: options.retestOnFailure,
444445
enabled: options.enabled,
445446
fields: options.fields,
447+
spaces: Array.from(new Set([...(options.spaces ?? []), options.space])),
446448
});
447449

448450
const monitors: Monitor[] = [];
@@ -465,7 +467,10 @@ export default class Runner implements RunnerInfo {
465467
// TODO: Fix backwards compatibility with 1.14 and prior
466468
(journey.cb ?? journey.callback)({ params: options.params } as any);
467469
const monitor = journey.monitor ?? journey?._getMonitor();
468-
monitor.update(this.#monitor?.config);
470+
monitor.update({
471+
...this.#monitor?.config,
472+
...parseSpaces(monitor.config, options),
473+
});
469474
if (!monitor.isMatch(options.grepOpts?.match, options.grepOpts?.tags)) {
470475
continue;
471476
}

src/dsl/monitor.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ export type MonitorConfig = {
7777
* By default, the monitor will be retested on failure
7878
*/
7979
retestOnFailure?: boolean;
80+
/**
81+
* The kibana spaces where the monitor will be visible.
82+
*/
83+
spaces?: string[];
8084
};
8185

8286
type MonitorFilter = {

src/push/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export async function push(monitors: Monitor[], options: PushOptions) {
7575
if (duplicates.size > 0) {
7676
throw error(formatDuplicateError(duplicates));
7777
}
78+
7879
progress(
7980
`Pushing monitors for '${options.id}' project in kibana '${options.space}' space`
8081
);

src/push/monitor.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ export function buildMonitorFromYaml(
293293
tags: options.tags,
294294
fields: parseFields(config, options.fields),
295295
...normalizeConfig(config),
296+
...parseSpaces(config, options),
296297
retestOnFailure,
297298
privateLocations,
298299
schedule:
@@ -335,6 +336,27 @@ export const parseAlertConfig = (
335336
return Object.keys(result).length > 0 ? result : undefined;
336337
};
337338

339+
export const parseSpaces = (config: MonitorConfig, options: PushOptions) => {
340+
const baseSpaces = [...(config.spaces ?? []), ...(options.spaces ?? [])];
341+
if (!baseSpaces.length) {
342+
// If no spaces are defined, we return an empty object
343+
return {};
344+
}
345+
346+
// If the user has provided a global space, merge it with the monitor spaces
347+
const spaces = Array.from(
348+
options.space
349+
? new Set([options.space, ...baseSpaces])
350+
: new Set(baseSpaces)
351+
);
352+
353+
if (spaces.includes('*')) {
354+
// If the user has provided a wildcard space, we should not override it with the global space
355+
return { spaces: ['*'] };
356+
}
357+
return { spaces };
358+
};
359+
338360
export const parseFields = (
339361
config: MonitorConfig,
340362
gFields?: Record<string, string>

0 commit comments

Comments
 (0)