[8.16] fix(slo): Override transform _id to ensure uniqness (#198610) (#198699)

# Backport

This will backport the following commits from `main` to `8.16`:
- [fix(slo): Override transform _id to ensure uniqness
(#198610)](https://github.com/elastic/kibana/pull/198610)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Kevin
Delemme","email":"kevin.delemme@elastic.co"},"sourceCommit":{"committedDate":"2024-11-01T15:36:41Z","message":"fix(slo):
Override transform _id to ensure uniqness
(#198610)","sha":"98db4d6f75f05eba1682e0e903bf2d9c909b37f2","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-management","v8.16.0"],"title":"fix(slo):
Override transform _id to ensure
uniqness","number":198610,"url":"https://github.com/elastic/kibana/pull/198610","mergeCommit":{"message":"fix(slo):
Override transform _id to ensure uniqness
(#198610)","sha":"98db4d6f75f05eba1682e0e903bf2d9c909b37f2"}},"sourceBranch":"main","suggestedTargetBranches":["8.16"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/198610","number":198610,"mergeCommit":{"message":"fix(slo):
Override transform _id to ensure uniqness
(#198610)","sha":"98db4d6f75f05eba1682e0e903bf2d9c909b37f2"}},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Kevin Delemme <kevin.delemme@elastic.co>
This commit is contained in:
Kibana Machine 2024-11-02 04:18:49 +11:00 committed by GitHub
parent 4cf7f33e7c
commit ebe9f29b59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 248 additions and 166 deletions

View file

@ -6,6 +6,7 @@
*/
import * as t from 'io-ts';
import { Either } from 'fp-ts/Either';
import { allOrAnyStringOrArray, dateType } from './common';
import { durationType } from './duration';
import { indicatorSchema } from './indicators';
@ -36,7 +37,35 @@ const groupBySchema = allOrAnyStringOrArray;
const optionalSettingsSchema = t.partial({ ...settingsSchema.props });
const tagsSchema = t.array(t.string);
const sloIdSchema = t.string;
// id cannot contain special characters and spaces
const sloIdSchema = new t.Type<string, string, unknown>(
'sloIdSchema',
t.string.is,
(input, context): Either<t.Errors, string> => {
if (typeof input === 'string') {
const valid = isValidId(input);
if (!valid) {
return t.failure(
input,
context,
'Invalid slo id, must be between 8 and 48 characters and contain only letters, numbers, hyphens, and underscores'
);
}
return t.success(input);
} else {
return t.failure(input, context);
}
},
t.identity
);
function isValidId(id: string): boolean {
const MIN_ID_LENGTH = 8;
const MAX_ID_LENGTH = 48;
const validLength = MIN_ID_LENGTH <= id.length && id.length <= MAX_ID_LENGTH;
return validLength && /^[a-z0-9-_]+$/.test(id);
}
const sloDefinitionSchema = t.type({
id: sloIdSchema,

View file

@ -12,7 +12,7 @@
],
"kbn_references": [
"@kbn/std",
"@kbn/io-ts-utils"
"@kbn/io-ts-utils",
],
"exclude": [
"target/**/*",

View file

@ -17,6 +17,12 @@ export const getSLOPipelineTemplate = (slo: SLODefinition) => ({
id: getSLOPipelineId(slo.id, slo.revision),
description: `Ingest pipeline for SLO rollup data [id: ${slo.id}, revision: ${slo.revision}]`,
processors: [
{
set: {
field: '_id',
value: `{{{_id}}}-${slo.id}-${slo.revision}`,
},
},
{
set: {
field: 'event.ingested',

View file

@ -12,6 +12,12 @@ Array [
"description": "Ingest pipeline for SLO rollup data [id: unique-id, revision: 1]",
"id": ".slo-observability.sli.pipeline-unique-id-1",
"processors": Array [
Object {
"set": Object {
"field": "_id",
"value": "{{{_id}}}-unique-id-1",
},
},
Object {
"set": Object {
"field": "event.ingested",

View file

@ -208,6 +208,12 @@ exports[`ResetSLO resets all associated resources 8`] = `
"description": "Ingest pipeline for SLO rollup data [id: irrelevant, revision: 1]",
"id": ".slo-observability.sli.pipeline-irrelevant-1",
"processors": Array [
Object {
"set": Object {
"field": "_id",
"value": "{{{_id}}}-irrelevant-1",
},
},
Object {
"set": Object {
"field": "event.ingested",

View file

@ -0,0 +1,117 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Synthetics Availability Transform Generator returns the expected transform params 1`] = `
Object {
"_meta": Object {
"managed": true,
"managed_by": "observability",
"version": 3.3,
},
"defer_validation": true,
"description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]",
"dest": Object {
"index": ".slo-observability.sli-v3.3",
"pipeline": ".slo-observability.sli.pipeline-irrelevant-1",
},
"frequency": "1m",
"pivot": Object {
"aggregations": Object {
"slo.denominator": Object {
"filter": Object {
"term": Object {
"summary.final_attempt": true,
},
},
},
"slo.numerator": Object {
"filter": Object {
"term": Object {
"monitor.status": "up",
},
},
},
},
"group_by": Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
"monitor.config_id": Object {
"terms": Object {
"field": "config_id",
},
},
"monitor.name": Object {
"terms": Object {
"field": "monitor.name",
},
},
"observer.geo.name": Object {
"terms": Object {
"field": "observer.geo.name",
},
},
"observer.name": Object {
"terms": Object {
"field": "observer.name",
},
},
"slo.groupings.monitor.id": Object {
"terms": Object {
"field": "monitor.id",
},
},
"slo.groupings.monitor.name": Object {
"terms": Object {
"field": "monitor.name",
},
},
"slo.groupings.observer.geo.name": Object {
"terms": Object {
"field": "observer.geo.name",
},
},
},
},
"settings": Object {
"deduce_mappings": false,
"unattended": true,
},
"source": Object {
"index": "synthetics-*",
"query": Object {
"bool": Object {
"filter": Array [
Object {
"term": Object {
"summary.final_attempt": true,
},
},
Object {
"term": Object {
"meta.space_id": "custom-space",
},
},
Object {
"range": Object {
"@timestamp": Object {
"gte": "now-7d/d",
},
},
},
],
},
},
"runtime_mappings": Object {},
},
"sync": Object {
"time": Object {
"delay": "1m",
"field": "event.ingested",
},
},
"transform_id": "slo-irrelevant-1",
}
`;

View file

@ -0,0 +1,73 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Transform Generator builds common runtime mappings and group by with single group by 1`] = `Object {}`;
exports[`Transform Generator builds common runtime mappings and group by with single group by 2`] = `
Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
"slo.groupings.example": Object {
"terms": Object {
"field": "example",
},
},
}
`;
exports[`Transform Generator builds common runtime mappings and group by with single group by 3`] = `Object {}`;
exports[`Transform Generator builds common runtime mappings and group by with single group by 4`] = `
Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
"slo.groupings.example": Object {
"terms": Object {
"field": "example",
},
},
}
`;
exports[`Transform Generator builds common runtime mappings without multi group by 1`] = `Object {}`;
exports[`Transform Generator builds common runtime mappings without multi group by 2`] = `
Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
"slo.groupings.example1": Object {
"terms": Object {
"field": "example1",
},
},
"slo.groupings.example2": Object {
"terms": Object {
"field": "example2",
},
},
}
`;
exports[`Transform Generator builds empty runtime mappings without group by 1`] = `Object {}`;
exports[`Transform Generator builds empty runtime mappings without group by 2`] = `
Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
}
`;

View file

@ -5,13 +5,12 @@
* 2.0.
*/
import { ALL_VALUE } from '@kbn/slo-schema';
import { dataViewsService } from '@kbn/data-views-plugin/server/mocks';
import { ALL_VALUE } from '@kbn/slo-schema';
import { SLODefinition } from '../../domain/models';
import { twoMinute } from '../fixtures/duration';
import { createSLO, createSyntheticsAvailabilityIndicator } from '../fixtures/slo';
import { SyntheticsAvailabilityTransformGenerator } from './synthetics_availability';
import { SYNTHETICS_INDEX_PATTERN } from '../../../common/constants';
import { twoMinute } from '../fixtures/duration';
const generator = new SyntheticsAvailabilityTransformGenerator();
@ -22,119 +21,7 @@ describe('Synthetics Availability Transform Generator', () => {
const slo = createSLO({ id: 'irrelevant', indicator: createSyntheticsAvailabilityIndicator() });
const transform = await generator.getTransformParams(slo, spaceId, dataViewsService);
expect(transform).toEqual({
_meta: {
managed: true,
managed_by: 'observability',
version: 3.3,
},
defer_validation: true,
description: 'Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]',
dest: {
index: '.slo-observability.sli-v3.3',
pipeline: '.slo-observability.sli.pipeline-irrelevant-1',
},
frequency: '1m',
pivot: {
aggregations: {
'slo.denominator': {
filter: {
term: {
'summary.final_attempt': true,
},
},
},
'slo.numerator': {
filter: {
term: {
'monitor.status': 'up',
},
},
},
},
group_by: {
'@timestamp': {
date_histogram: {
field: '@timestamp',
fixed_interval: '1m',
},
},
'monitor.config_id': {
terms: {
field: 'config_id',
},
},
'monitor.name': {
terms: {
field: 'monitor.name',
},
},
'observer.name': {
terms: {
field: 'observer.name',
},
},
'observer.geo.name': {
terms: {
field: 'observer.geo.name',
},
},
'slo.groupings.monitor.name': {
terms: {
field: 'monitor.name',
},
},
'slo.groupings.observer.geo.name': {
terms: {
field: 'observer.geo.name',
},
},
'slo.groupings.monitor.id': {
terms: {
field: 'monitor.id',
},
},
},
},
settings: {
deduce_mappings: false,
unattended: true,
},
source: {
index: SYNTHETICS_INDEX_PATTERN,
query: {
bool: {
filter: [
{
term: {
'summary.final_attempt': true,
},
},
{
term: {
'meta.space_id': 'custom-space',
},
},
{
range: {
'@timestamp': {
gte: 'now-7d/d',
},
},
},
],
},
},
runtime_mappings: {},
},
sync: {
time: {
delay: '1m',
field: 'event.ingested',
},
},
transform_id: 'slo-irrelevant-1',
});
expect(transform).toMatchSnapshot();
expect(transform.source.query?.bool?.filter).toContainEqual({
term: {
'summary.final_attempt': true,

View file

@ -17,19 +17,10 @@ describe('Transform Generator', () => {
indicator: createAPMTransactionErrorRateIndicator(),
});
const commonRuntime = generator.buildCommonRuntimeMappings(slo);
expect(commonRuntime).toEqual({});
expect(commonRuntime).toMatchSnapshot();
const commonGroupBy = generator.buildCommonGroupBy(slo);
expect(commonGroupBy).toEqual({
'@timestamp': {
date_histogram: {
field: '@timestamp',
fixed_interval: '1m',
},
},
});
expect(commonGroupBy).toMatchSnapshot();
});
it.each(['example', ['example']])(
@ -42,24 +33,10 @@ describe('Transform Generator', () => {
indicator,
});
const commonRuntime = generator.buildCommonRuntimeMappings(slo);
expect(commonRuntime).toEqual({});
expect(commonRuntime).toMatchSnapshot();
const commonGroupBy = generator.buildCommonGroupBy(slo);
expect(commonGroupBy).toEqual({
'@timestamp': {
date_histogram: {
field: '@timestamp',
fixed_interval: '1m',
},
},
'slo.groupings.example': {
terms: {
field: 'example',
},
},
});
expect(commonGroupBy).toMatchSnapshot();
}
);
@ -71,28 +48,9 @@ describe('Transform Generator', () => {
indicator,
});
const commonRuntime = generator.buildCommonRuntimeMappings(slo);
expect(commonRuntime).toEqual({});
expect(commonRuntime).toMatchSnapshot();
const commonGroupBy = generator.buildCommonGroupBy(slo);
expect(commonGroupBy).toEqual({
'@timestamp': {
date_histogram: {
field: '@timestamp',
fixed_interval: '1m',
},
},
'slo.groupings.example1': {
terms: {
field: 'example1',
},
},
'slo.groupings.example2': {
terms: {
field: 'example2',
},
},
});
expect(commonGroupBy).toMatchSnapshot();
});
});