Skip to content

Commit 514a0f5

Browse files
authored
Merge branch 'main' into fix/enable-prerelease-suite
2 parents 8bac3f1 + c03e2fc commit 514a0f5

File tree

45 files changed

+2955
-782
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2955
-782
lines changed

docs/api/alerting/create_rule.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ The API returns the following:
224224
"execution_status": {
225225
"last_execution_date": "2022-06-08T17:20:31.632Z",
226226
"status": "pending"
227-
}
227+
},
228228
"actions": [
229229
{
230230
"group": "threshold met",

src/plugins/chart_expressions/expression_partition_vis/public/utils/filter_helpers.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
*/
88
import { Datatable, DatatableColumn } from '@kbn/expressions-plugin/public';
99
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
10-
import { getFilterClickData, getFilterEventData, getFilterPopoverTitle } from './filter_helpers';
10+
import {
11+
getFilterClickData,
12+
getFilterEventData,
13+
getFilterPopoverTitle,
14+
getAccessor,
15+
} from './filter_helpers';
1116
import { createMockBucketColumns, createMockVisData, createMockPieParams } from '../mocks';
1217
import { consolidateMetricColumns } from '../../common/utils';
1318
import { LayerValue } from '@elastic/charts';
@@ -267,6 +272,26 @@ describe('getFilterEventData', () => {
267272
});
268273
});
269274

275+
describe('getAccessor', () => {
276+
it('returns the correct accessor for ExpressionValueVisDimension', () => {
277+
const accessor = getAccessor(visParams.dimensions.buckets, 2);
278+
expect(accessor).toStrictEqual({
279+
accessor: 2,
280+
format: {
281+
id: 'terms',
282+
params: { id: 'boolean', missingBucketLabel: 'Missing', otherBucketLabel: 'Other' },
283+
},
284+
type: 'vis_dimension',
285+
});
286+
});
287+
288+
it('returns the correct accessor for strings', () => {
289+
const buckets = ['bucket1', 'bucket2'];
290+
const accessor = getAccessor(buckets, 0);
291+
expect(accessor).toStrictEqual('bucket1');
292+
});
293+
});
294+
270295
describe('getFilterPopoverTitle', () => {
271296
it('returns the series key if no buckets', () => {
272297
const series = {

src/plugins/chart_expressions/expression_partition_vis/public/utils/filter_helpers.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { DataPublicPluginStart } from '@kbn/data-plugin/public';
1212
import { ValueClickContext } from '@kbn/embeddable-plugin/public';
1313
import { getFormatByAccessor } from '@kbn/visualizations-plugin/common/utils';
1414
import type { FieldFormat, FormatFactory } from '@kbn/field-formats-plugin/common';
15-
import { BucketColumns, PartitionVisParams } from '../../common/types';
15+
import { BucketColumns, PartitionVisParams, Dimensions } from '../../common/types';
1616
import { FilterEvent } from '../types';
1717

1818
export const canFilter = async (
@@ -131,6 +131,13 @@ export const getSeriesValueColumnIndex = (value: string, visData: Datatable): nu
131131
return visData.columns.findIndex(({ id }) => !!visData.rows.find((r) => r[id] === value));
132132
};
133133

134+
export const getAccessor = (buckets: Dimensions['buckets'], index: number) => {
135+
const accessorForDimensionBuckets = buckets?.find((b) => {
136+
return typeof b !== 'string' && b.accessor === index;
137+
});
138+
return accessorForDimensionBuckets || buckets?.[index];
139+
};
140+
134141
export const getFilterPopoverTitle = (
135142
visParams: PartitionVisParams,
136143
visData: Datatable,
@@ -140,7 +147,7 @@ export const getFilterPopoverTitle = (
140147
) => {
141148
let formattedTitle = '';
142149
if (visParams.dimensions.buckets) {
143-
const accessor = visParams.dimensions.buckets[columnIndex];
150+
const accessor = getAccessor(visParams.dimensions.buckets, columnIndex);
144151
formattedTitle = accessor
145152
? formatter(getFormatByAccessor(accessor, visData.columns)).convert(seriesKey)
146153
: '';

x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -227,16 +227,18 @@ export const LinksMenuUI = (props: LinksMenuProps) => {
227227
// Start from the previous hour.
228228
earliestMoment.subtract(1, 'h');
229229
}
230-
let latestMoment = moment(record.timestamp).add(record.bucket_span, 's');
230+
231+
const latestMoment = moment(record.timestamp).add(record.bucket_span, 's');
231232
if (props.isAggregatedData === true) {
232-
latestMoment = moment(record.timestamp).endOf(interval);
233233
if (interval === 'hour') {
234234
// Show to the end of the next hour.
235-
latestMoment.add(1, 'h'); // e.g. 2016-02-08T18:59:59.999Z
235+
latestMoment.add(1, 'h');
236236
}
237+
latestMoment.subtract(1, 'ms').endOf(interval); // e.g. 2016-02-08T18:59:59.999Z
237238
}
239+
238240
const from = timeFormatter(earliestMoment.unix() * 1000); // e.g. 2016-02-08T16:00:00.000Z
239-
const to = timeFormatter(latestMoment.unix() * 1000);
241+
const to = timeFormatter(latestMoment.unix() * 1000); // e.g. 2016-02-08T18:59:59.000Z
240242

241243
let kqlQuery = '';
242244

@@ -315,16 +317,16 @@ export const LinksMenuUI = (props: LinksMenuProps) => {
315317
}
316318

317319
if (configuredUrlValue.includes('$latest$')) {
318-
let latestMoment = moment(timestamp).add(record.bucket_span, 's');
320+
const latestMoment = moment(timestamp).add(record.bucket_span, 's');
319321
if (timeRangeInterval !== null) {
320322
latestMoment.add(timeRangeInterval);
321323
} else {
322324
if (isAggregatedData === true) {
323-
latestMoment = moment(timestamp).endOf(interval);
324325
if (interval === 'hour') {
325326
// Show to the end of the next hour.
326327
latestMoment.add(1, 'h'); // e.g. 2016-02-08T18:59:59.999Z
327328
}
329+
latestMoment.subtract(1, 'ms').endOf(interval); // e.g. 2016-02-08T18:59:59.999Z
328330
}
329331
}
330332
record.latest = latestMoment.toISOString();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export const SLACK_CONNECTOR_ID = '.slack';
9+
export const SLACK_URL = 'https://slack.com/api/';
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { schema } from '@kbn/config-schema';
9+
10+
export const SlackConfigSchema = schema.object({
11+
type: schema.oneOf([schema.literal('webhook'), schema.literal('web_api')], {
12+
defaultValue: 'webhook',
13+
}),
14+
});
15+
16+
export const SlackWebhookSecretsSchema = schema.object({
17+
webhookUrl: schema.string({ minLength: 1 }),
18+
});
19+
export const SlackWebApiSecretsSchema = schema.object({
20+
token: schema.string({ minLength: 1 }),
21+
});
22+
23+
export const SlackSecretsSchema = schema.oneOf([
24+
SlackWebhookSecretsSchema,
25+
SlackWebApiSecretsSchema,
26+
]);
27+
28+
export const ExecutorGetChannelsParamsSchema = schema.object({
29+
subAction: schema.literal('getChannels'),
30+
});
31+
32+
export const PostMessageSubActionParamsSchema = schema.object({
33+
channels: schema.arrayOf(schema.string()),
34+
text: schema.string(),
35+
});
36+
export const ExecutorPostMessageParamsSchema = schema.object({
37+
subAction: schema.literal('postMessage'),
38+
subActionParams: PostMessageSubActionParamsSchema,
39+
});
40+
41+
export const WebhookParamsSchema = schema.object({
42+
message: schema.string({ minLength: 1 }),
43+
});
44+
export const WebApiParamsSchema = schema.oneOf([
45+
ExecutorGetChannelsParamsSchema,
46+
ExecutorPostMessageParamsSchema,
47+
]);
48+
49+
export const SlackParamsSchema = schema.oneOf([WebhookParamsSchema, WebApiParamsSchema]);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { TypeOf } from '@kbn/config-schema';
9+
import type { ActionTypeExecutorOptions as ConnectorTypeExecutorOptions } from '@kbn/actions-plugin/server/types';
10+
import type { ActionType as ConnectorType } from '@kbn/actions-plugin/server/types';
11+
import {
12+
ExecutorPostMessageParamsSchema,
13+
PostMessageSubActionParamsSchema,
14+
SlackConfigSchema,
15+
SlackSecretsSchema,
16+
SlackWebhookSecretsSchema,
17+
SlackWebApiSecretsSchema,
18+
WebhookParamsSchema,
19+
WebApiParamsSchema,
20+
} from './schema';
21+
22+
export type SlackConfig = TypeOf<typeof SlackConfigSchema>;
23+
export type SlackSecrets = TypeOf<typeof SlackSecretsSchema>;
24+
25+
export type PostMessageParams = TypeOf<typeof ExecutorPostMessageParamsSchema>;
26+
export type PostMessageSubActionParams = TypeOf<typeof PostMessageSubActionParamsSchema>;
27+
28+
export type SlackWebhookSecrets = TypeOf<typeof SlackWebhookSecretsSchema>;
29+
export type SlackWebApiSecrets = TypeOf<typeof SlackWebApiSecretsSchema>;
30+
31+
export type SlackWebhookExecutorOptions = ConnectorTypeExecutorOptions<
32+
SlackConfig,
33+
SlackWebhookSecrets,
34+
WebhookParams
35+
>;
36+
export type SlackWebApiExecutorOptions = ConnectorTypeExecutorOptions<
37+
SlackConfig,
38+
SlackWebApiSecrets,
39+
WebApiParams
40+
>;
41+
42+
export type SlackExecutorOptions = ConnectorTypeExecutorOptions<
43+
SlackConfig,
44+
SlackSecrets,
45+
WebhookParams | WebApiParams
46+
>;
47+
48+
export type SlackConnectorType = ConnectorType<
49+
SlackConfig,
50+
SlackSecrets,
51+
WebhookParams | WebApiParams,
52+
unknown
53+
>;
54+
55+
export type WebhookParams = TypeOf<typeof WebhookParamsSchema>;
56+
export type WebApiParams = TypeOf<typeof WebApiParamsSchema>;
57+
export type SlackActionParams = WebhookParams | WebApiParams;

x-pack/plugins/stack_connectors/public/connector_types/slack/slack.test.tsx

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,16 @@ describe('connectorTypeRegistry.get() works', () => {
3030
});
3131

3232
describe('slack action params validation', () => {
33-
test('if action params validation succeeds when action params is valid', async () => {
33+
test('should succeed when action params include valid message', async () => {
3434
const actionParams = {
3535
message: 'message {test}',
3636
};
3737

3838
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
39-
errors: { message: [] },
39+
errors: {
40+
message: [],
41+
'subActionParams.channels': [],
42+
},
4043
});
4144
});
4245

@@ -48,6 +51,77 @@ describe('slack action params validation', () => {
4851
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
4952
errors: {
5053
message: ['Message is required.'],
54+
'subActionParams.channels': [],
55+
},
56+
});
57+
});
58+
59+
test('should succeed when action params include valid message and channels list', async () => {
60+
const actionParams = {
61+
subAction: 'postMessage',
62+
subActionParams: { channels: ['general'], text: 'some text' },
63+
};
64+
65+
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
66+
errors: {
67+
message: [],
68+
'subActionParams.channels': [],
69+
},
70+
});
71+
});
72+
73+
test('should fail when action params do not includes any channels', async () => {
74+
const actionParams = {
75+
subAction: 'postMessage',
76+
subActionParams: { channels: [], text: 'some text' },
77+
};
78+
79+
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
80+
errors: {
81+
message: [],
82+
'subActionParams.channels': ['At least one selected channel is required.'],
83+
},
84+
});
85+
});
86+
87+
test('should fail when channels field is missing in action params', async () => {
88+
const actionParams = {
89+
subAction: 'postMessage',
90+
subActionParams: { text: 'some text' },
91+
};
92+
93+
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
94+
errors: {
95+
message: [],
96+
'subActionParams.channels': ['At least one selected channel is required.'],
97+
},
98+
});
99+
});
100+
101+
test('should fail when field text doesnot exist', async () => {
102+
const actionParams = {
103+
subAction: 'postMessage',
104+
subActionParams: { channels: ['general'] },
105+
};
106+
107+
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
108+
errors: {
109+
message: ['Message is required.'],
110+
'subActionParams.channels': [],
111+
},
112+
});
113+
});
114+
115+
test('should fail when text is empty string', async () => {
116+
const actionParams = {
117+
subAction: 'postMessage',
118+
subActionParams: { channels: ['general'], text: '' },
119+
};
120+
121+
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
122+
errors: {
123+
message: ['Message is required.'],
124+
'subActionParams.channels': [],
51125
},
52126
});
53127
});

0 commit comments

Comments
 (0)