mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[RAM] Allow users to see event logs from all spaces they have access to (#140449)
* Add all_namespaces prop to global logs api * Display space column and disable link on inactive spaces * Add ability to link across spaces * Fix allNamespace query on default space * Fix KPI and link space switch to permissions * Open alternate space rules in new tab * Fix Jest 11 * Fix Jest 1 * Fix Jest 4 and 10 * Fix i18n * Move space column visibility out of data grid
This commit is contained in:
parent
e502ecfd18
commit
5c50cd4ffd
37 changed files with 704 additions and 74 deletions
|
@ -61,6 +61,7 @@ export interface IExecutionLog {
|
|||
schedule_delay_ms: number;
|
||||
timed_out: boolean;
|
||||
rule_id: string;
|
||||
space_ids: string[];
|
||||
rule_name: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -280,6 +280,7 @@ describe('getExecutionLogAggregation', () => {
|
|||
'error.message',
|
||||
'kibana.version',
|
||||
'rule.id',
|
||||
'kibana.space_ids',
|
||||
'rule.name',
|
||||
'kibana.alerting.outcome',
|
||||
],
|
||||
|
@ -486,6 +487,7 @@ describe('getExecutionLogAggregation', () => {
|
|||
'error.message',
|
||||
'kibana.version',
|
||||
'rule.id',
|
||||
'kibana.space_ids',
|
||||
'rule.name',
|
||||
'kibana.alerting.outcome',
|
||||
],
|
||||
|
@ -692,6 +694,7 @@ describe('getExecutionLogAggregation', () => {
|
|||
'error.message',
|
||||
'kibana.version',
|
||||
'rule.id',
|
||||
'kibana.space_ids',
|
||||
'rule.name',
|
||||
'kibana.alerting.outcome',
|
||||
],
|
||||
|
@ -954,6 +957,7 @@ describe('formatExecutionLogResult', () => {
|
|||
schedule_delay_ms: 3074,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule_name',
|
||||
space_ids: [],
|
||||
},
|
||||
{
|
||||
id: '41b2755e-765a-4044-9745-b03875d5e79a',
|
||||
|
@ -976,6 +980,7 @@ describe('formatExecutionLogResult', () => {
|
|||
schedule_delay_ms: 3126,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule_name',
|
||||
space_ids: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -1203,6 +1208,7 @@ describe('formatExecutionLogResult', () => {
|
|||
schedule_delay_ms: 3074,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule_name',
|
||||
space_ids: [],
|
||||
},
|
||||
{
|
||||
id: '41b2755e-765a-4044-9745-b03875d5e79a',
|
||||
|
@ -1225,6 +1231,7 @@ describe('formatExecutionLogResult', () => {
|
|||
schedule_delay_ms: 3126,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule_name',
|
||||
space_ids: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -1444,6 +1451,7 @@ describe('formatExecutionLogResult', () => {
|
|||
schedule_delay_ms: 3074,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule_name',
|
||||
space_ids: [],
|
||||
},
|
||||
{
|
||||
id: '41b2755e-765a-4044-9745-b03875d5e79a',
|
||||
|
@ -1466,6 +1474,7 @@ describe('formatExecutionLogResult', () => {
|
|||
schedule_delay_ms: 3126,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule_name',
|
||||
space_ids: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -1690,6 +1699,7 @@ describe('formatExecutionLogResult', () => {
|
|||
schedule_delay_ms: 3126,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule_name',
|
||||
space_ids: [],
|
||||
},
|
||||
{
|
||||
id: '61bb867b-661a-471f-bf92-23471afa10b3',
|
||||
|
@ -1712,6 +1722,7 @@ describe('formatExecutionLogResult', () => {
|
|||
schedule_delay_ms: 3133,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule_name',
|
||||
space_ids: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ const DEFAULT_MAX_BUCKETS_LIMIT = 1000; // do not retrieve more than this number
|
|||
const DEFAULT_MAX_KPI_BUCKETS_LIMIT = 10000;
|
||||
|
||||
const RULE_ID_FIELD = 'rule.id';
|
||||
const SPACE_ID_FIELD = 'kibana.space_ids';
|
||||
const RULE_NAME_FIELD = 'rule.name';
|
||||
const PROVIDER_FIELD = 'event.provider';
|
||||
const START_FIELD = 'event.start';
|
||||
|
@ -410,6 +411,7 @@ export function getExecutionLogAggregation({
|
|||
ERROR_MESSAGE_FIELD,
|
||||
VERSION_FIELD,
|
||||
RULE_ID_FIELD,
|
||||
SPACE_ID_FIELD,
|
||||
RULE_NAME_FIELD,
|
||||
ALERTING_OUTCOME_FIELD,
|
||||
],
|
||||
|
@ -494,8 +496,9 @@ function formatExecutionLogAggBucket(bucket: IExecutionUuidAggBucket): IExecutio
|
|||
status === 'failure' ? `${outcomeMessage} - ${outcomeErrorMessage}` : outcomeMessage;
|
||||
const version = outcomeAndMessage.kibana?.version ?? '';
|
||||
|
||||
const ruleId = outcomeAndMessage.rule?.id ?? '';
|
||||
const ruleName = outcomeAndMessage.rule?.name ?? '';
|
||||
const ruleId = outcomeAndMessage ? outcomeAndMessage?.rule?.id ?? '' : '';
|
||||
const spaceIds = outcomeAndMessage ? outcomeAndMessage?.kibana?.space_ids ?? [] : [];
|
||||
const ruleName = outcomeAndMessage ? outcomeAndMessage.rule?.name ?? '' : '';
|
||||
return {
|
||||
id: bucket?.key ?? '',
|
||||
timestamp: bucket?.ruleExecution?.executeStartTime.value_as_string ?? '',
|
||||
|
@ -515,6 +518,7 @@ function formatExecutionLogAggBucket(bucket: IExecutionUuidAggBucket): IExecutio
|
|||
schedule_delay_ms: scheduleDelayUs / Millis2Nanos,
|
||||
timed_out: timedOut,
|
||||
rule_id: ruleId,
|
||||
space_ids: spaceIds,
|
||||
rule_name: ruleName,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -34,15 +34,19 @@ const querySchema = schema.object({
|
|||
per_page: schema.number({ defaultValue: 10, min: 1 }),
|
||||
page: schema.number({ defaultValue: 1, min: 1 }),
|
||||
sort: sortFieldsSchema,
|
||||
namespace: schema.maybe(schema.string()),
|
||||
with_auth: schema.maybe(schema.boolean()),
|
||||
});
|
||||
|
||||
const rewriteReq: RewriteRequestCase<GetActionErrorLogByIdParams> = ({
|
||||
date_start: dateStart,
|
||||
date_end: dateEnd,
|
||||
per_page: perPage,
|
||||
namespace,
|
||||
...rest
|
||||
}) => ({
|
||||
...rest,
|
||||
namespace,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
perPage,
|
||||
|
@ -64,8 +68,13 @@ export const getActionErrorLogRoute = (
|
|||
verifyAccessAndContext(licenseState, async function (context, req, res) {
|
||||
const rulesClient = (await context.alerting).getRulesClient();
|
||||
const { id } = req.params;
|
||||
const withAuth = req.query.with_auth;
|
||||
const rewrittenReq = rewriteReq({ id, ...req.query });
|
||||
const getter = (
|
||||
withAuth ? rulesClient.getActionErrorLogWithAuth : rulesClient.getActionErrorLog
|
||||
).bind(rulesClient);
|
||||
return res.ok({
|
||||
body: await rulesClient.getActionErrorLog(rewriteReq({ id, ...req.query })),
|
||||
body: await getter(rewrittenReq),
|
||||
});
|
||||
})
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { IRouter } from '@kbn/core/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types';
|
||||
import { RewriteRequestCase, verifyAccessAndContext } from './lib';
|
||||
import { RewriteRequestCase, verifyAccessAndContext, rewriteNamespaces } from './lib';
|
||||
import { GetGlobalExecutionKPIParams } from '../rules_client';
|
||||
import { ILicenseState } from '../lib';
|
||||
|
||||
|
@ -15,14 +15,17 @@ const querySchema = schema.object({
|
|||
date_start: schema.string(),
|
||||
date_end: schema.maybe(schema.string()),
|
||||
filter: schema.maybe(schema.string()),
|
||||
namespaces: schema.maybe(schema.arrayOf(schema.string())),
|
||||
});
|
||||
|
||||
const rewriteReq: RewriteRequestCase<GetGlobalExecutionKPIParams> = ({
|
||||
date_start: dateStart,
|
||||
date_end: dateEnd,
|
||||
namespaces,
|
||||
...rest
|
||||
}) => ({
|
||||
...rest,
|
||||
namespaces: rewriteNamespaces(namespaces),
|
||||
dateStart,
|
||||
dateEnd,
|
||||
});
|
||||
|
|
|
@ -47,6 +47,7 @@ describe('getRuleExecutionLogRoute', () => {
|
|||
schedule_delay_ms: 3126,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule-name',
|
||||
space_ids: ['namespace'],
|
||||
},
|
||||
{
|
||||
id: '41b2755e-765a-4044-9745-b03875d5e79a',
|
||||
|
@ -69,6 +70,7 @@ describe('getRuleExecutionLogRoute', () => {
|
|||
schedule_delay_ms: 3008,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule-name',
|
||||
space_ids: ['namespace'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ import { IRouter } from '@kbn/core/server';
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { ILicenseState } from '../lib';
|
||||
import { GetGlobalExecutionLogParams } from '../rules_client';
|
||||
import { RewriteRequestCase, verifyAccessAndContext } from './lib';
|
||||
import { RewriteRequestCase, verifyAccessAndContext, rewriteNamespaces } from './lib';
|
||||
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types';
|
||||
|
||||
const sortOrderSchema = schema.oneOf([schema.literal('asc'), schema.literal('desc')]);
|
||||
|
@ -38,15 +38,18 @@ const querySchema = schema.object({
|
|||
per_page: schema.number({ defaultValue: 10, min: 1 }),
|
||||
page: schema.number({ defaultValue: 1, min: 1 }),
|
||||
sort: sortFieldsSchema,
|
||||
namespaces: schema.maybe(schema.arrayOf(schema.string())),
|
||||
});
|
||||
|
||||
const rewriteReq: RewriteRequestCase<GetGlobalExecutionLogParams> = ({
|
||||
date_start: dateStart,
|
||||
date_end: dateEnd,
|
||||
per_page: perPage,
|
||||
namespaces,
|
||||
...rest
|
||||
}) => ({
|
||||
...rest,
|
||||
namespaces: rewriteNamespaces(namespaces),
|
||||
dateStart,
|
||||
dateEnd,
|
||||
perPage,
|
||||
|
|
|
@ -48,6 +48,7 @@ describe('getRuleExecutionLogRoute', () => {
|
|||
schedule_delay_ms: 3126,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule_name',
|
||||
space_ids: ['namespace'],
|
||||
},
|
||||
{
|
||||
id: '41b2755e-765a-4044-9745-b03875d5e79a',
|
||||
|
@ -70,6 +71,7 @@ describe('getRuleExecutionLogRoute', () => {
|
|||
schedule_delay_ms: 3008,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule_name',
|
||||
space_ids: ['namespace'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -19,3 +19,4 @@ export type {
|
|||
export { verifyAccessAndContext } from './verify_access_and_context';
|
||||
export { countUsageOfPredefinedIds } from './count_usage_of_predefined_ids';
|
||||
export { rewriteRule } from './rewrite_rule';
|
||||
export { rewriteNamespaces } from './rewrite_namespaces';
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const rewriteNamespaces = (namespaces?: Array<string | undefined>) =>
|
||||
namespaces
|
||||
? namespaces.map((id: string | undefined) => (id === 'default' ? undefined : id))
|
||||
: undefined;
|
|
@ -34,6 +34,7 @@ const createRulesClientMock = () => {
|
|||
getGlobalExecutionKpiWithAuth: jest.fn(),
|
||||
getGlobalExecutionLogWithAuth: jest.fn(),
|
||||
getActionErrorLog: jest.fn(),
|
||||
getActionErrorLogWithAuth: jest.fn(),
|
||||
getSpaceId: jest.fn(),
|
||||
bulkEdit: jest.fn(),
|
||||
bulkDeleteRules: jest.fn(),
|
||||
|
|
|
@ -419,6 +419,7 @@ export interface GetGlobalExecutionKPIParams {
|
|||
dateStart: string;
|
||||
dateEnd?: string;
|
||||
filter?: string;
|
||||
namespaces?: Array<string | undefined>;
|
||||
}
|
||||
|
||||
export interface GetGlobalExecutionLogParams {
|
||||
|
@ -428,6 +429,7 @@ export interface GetGlobalExecutionLogParams {
|
|||
page: number;
|
||||
perPage: number;
|
||||
sort: estypes.Sort;
|
||||
namespaces?: Array<string | undefined>;
|
||||
}
|
||||
|
||||
export interface GetActionErrorLogByIdParams {
|
||||
|
@ -438,6 +440,7 @@ export interface GetActionErrorLogByIdParams {
|
|||
page: number;
|
||||
perPage: number;
|
||||
sort: estypes.Sort;
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
interface ScheduleTaskOptions {
|
||||
|
@ -458,6 +461,9 @@ const MAX_RULES_NUMBER_FOR_BULK_OPERATION = 10000;
|
|||
const API_KEY_GENERATE_CONCURRENCY = 50;
|
||||
const RULE_TYPE_CHECKS_CONCURRENCY = 50;
|
||||
|
||||
const actionErrorLogDefaultFilter =
|
||||
'event.provider:actions AND ((event.action:execute AND (event.outcome:failure OR kibana.alerting.status:warning)) OR (event.action:execute-timeout))';
|
||||
|
||||
const alertingAuthorizationFilterOpts: AlertingAuthorizationFilterOpts = {
|
||||
type: AlertingAuthorizationFilterType.KQL,
|
||||
fieldNames: { ruleTypeId: 'alert.attributes.alertTypeId', consumer: 'alert.attributes.consumer' },
|
||||
|
@ -951,6 +957,7 @@ export class RulesClient {
|
|||
page,
|
||||
perPage,
|
||||
sort,
|
||||
namespaces,
|
||||
}: GetGlobalExecutionLogParams): Promise<IExecutionLogResult> {
|
||||
this.logger.debug(`getGlobalExecutionLogWithAuth(): getting global execution log`);
|
||||
|
||||
|
@ -1001,7 +1008,8 @@ export class RulesClient {
|
|||
perPage,
|
||||
sort,
|
||||
}),
|
||||
}
|
||||
},
|
||||
namespaces
|
||||
);
|
||||
|
||||
return formatExecutionLogResult(aggResult);
|
||||
|
@ -1050,9 +1058,6 @@ export class RulesClient {
|
|||
})
|
||||
);
|
||||
|
||||
const defaultFilter =
|
||||
'event.provider:actions AND ((event.action:execute AND (event.outcome:failure OR kibana.alerting.status:warning)) OR (event.action:execute-timeout))';
|
||||
|
||||
// default duration of instance summary is 60 * rule interval
|
||||
const dateNow = new Date();
|
||||
const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow);
|
||||
|
@ -1069,7 +1074,9 @@ export class RulesClient {
|
|||
end: parsedDateEnd.toISOString(),
|
||||
page,
|
||||
per_page: perPage,
|
||||
filter: filter ? `(${defaultFilter}) AND (${filter})` : defaultFilter,
|
||||
filter: filter
|
||||
? `(${actionErrorLogDefaultFilter}) AND (${filter})`
|
||||
: actionErrorLogDefaultFilter,
|
||||
sort: convertEsSortToEventLogSort(sort),
|
||||
},
|
||||
rule.legacyId !== null ? [rule.legacyId] : undefined
|
||||
|
@ -1083,10 +1090,85 @@ export class RulesClient {
|
|||
}
|
||||
}
|
||||
|
||||
public async getActionErrorLogWithAuth({
|
||||
id,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
filter,
|
||||
page,
|
||||
perPage,
|
||||
sort,
|
||||
namespace,
|
||||
}: GetActionErrorLogByIdParams): Promise<IExecutionErrorsResult> {
|
||||
this.logger.debug(`getActionErrorLogWithAuth(): getting action error logs for rule ${id}`);
|
||||
|
||||
let authorizationTuple;
|
||||
try {
|
||||
authorizationTuple = await this.authorization.getFindAuthorizationFilter(
|
||||
AlertingAuthorizationEntity.Alert,
|
||||
{
|
||||
type: AlertingAuthorizationFilterType.KQL,
|
||||
fieldNames: {
|
||||
ruleTypeId: 'kibana.alert.rule.rule_type_id',
|
||||
consumer: 'kibana.alert.rule.consumer',
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
this.auditLogger?.log(
|
||||
ruleAuditEvent({
|
||||
action: RuleAuditAction.GET_ACTION_ERROR_LOG,
|
||||
error,
|
||||
})
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.auditLogger?.log(
|
||||
ruleAuditEvent({
|
||||
action: RuleAuditAction.GET_ACTION_ERROR_LOG,
|
||||
savedObject: { type: 'alert', id },
|
||||
})
|
||||
);
|
||||
|
||||
// default duration of instance summary is 60 * rule interval
|
||||
const dateNow = new Date();
|
||||
const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow);
|
||||
const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow);
|
||||
|
||||
const eventLogClient = await this.getEventLogClient();
|
||||
|
||||
try {
|
||||
const errorResult = await eventLogClient.findEventsWithAuthFilter(
|
||||
'alert',
|
||||
[id],
|
||||
authorizationTuple.filter as KueryNode,
|
||||
namespace,
|
||||
{
|
||||
start: parsedDateStart.toISOString(),
|
||||
end: parsedDateEnd.toISOString(),
|
||||
page,
|
||||
per_page: perPage,
|
||||
filter: filter
|
||||
? `(${actionErrorLogDefaultFilter}) AND (${filter})`
|
||||
: actionErrorLogDefaultFilter,
|
||||
sort: convertEsSortToEventLogSort(sort),
|
||||
}
|
||||
);
|
||||
return formatExecutionErrorsResult(errorResult);
|
||||
} catch (err) {
|
||||
this.logger.debug(
|
||||
`rulesClient.getActionErrorLog(): error searching event log for rule ${id}: ${err.message}`
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
public async getGlobalExecutionKpiWithAuth({
|
||||
dateStart,
|
||||
dateEnd,
|
||||
filter,
|
||||
namespaces,
|
||||
}: GetGlobalExecutionKPIParams) {
|
||||
this.logger.debug(`getGlobalExecutionLogWithAuth(): getting global execution log`);
|
||||
|
||||
|
@ -1132,7 +1214,8 @@ export class RulesClient {
|
|||
start: parsedDateStart.toISOString(),
|
||||
end: parsedDateEnd.toISOString(),
|
||||
aggs: getExecutionKPIAggregation(filter),
|
||||
}
|
||||
},
|
||||
namespaces
|
||||
);
|
||||
|
||||
return formatExecutionKPIResult(aggResult);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { RulesClient, ConstructorOptions, GetActionErrorLogByIdParams } from '../rules_client';
|
||||
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
|
||||
import { fromKueryExpression } from '@kbn/es-query';
|
||||
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
|
||||
|
@ -574,3 +575,63 @@ describe('getActionErrorLog()', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getActionErrorLogWithAuth()', () => {
|
||||
let rulesClient: RulesClient;
|
||||
|
||||
beforeEach(() => {
|
||||
rulesClient = new RulesClient(rulesClientParams);
|
||||
});
|
||||
|
||||
test('returns the expected return values when called', async () => {
|
||||
const ruleSO = getRuleSavedObject({});
|
||||
authorization.getFindAuthorizationFilter.mockResolvedValue({
|
||||
filter: fromKueryExpression('*'),
|
||||
ensureRuleTypeIsAuthorized() {},
|
||||
});
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce(ruleSO);
|
||||
eventLogClient.findEventsWithAuthFilter.mockResolvedValueOnce(findResults);
|
||||
|
||||
const result = await rulesClient.getActionErrorLogWithAuth(getActionErrorLogParams());
|
||||
expect(result).toEqual({
|
||||
totalErrors: 5,
|
||||
errors: [
|
||||
{
|
||||
id: '08d9b0f5-0b41-47c9-951f-a666b5788ddc',
|
||||
timestamp: '2022-03-23T17:37:07.106Z',
|
||||
type: 'actions',
|
||||
message:
|
||||
'action execution failure: .server-log:9e67b8b0-9e2c-11ec-bd64-774ed95c43ef: s - an error occurred while running the action executor: something funky with the server log',
|
||||
},
|
||||
{
|
||||
id: '08d9b0f5-0b41-47c9-951f-a666b5788ddc',
|
||||
timestamp: '2022-03-23T17:37:07.102Z',
|
||||
type: 'actions',
|
||||
message:
|
||||
'action execution failure: .server-log:9e67b8b0-9e2c-11ec-bd64-774ed95c43ef: s - an error occurred while running the action executor: something funky with the server log',
|
||||
},
|
||||
{
|
||||
id: '08d9b0f5-0b41-47c9-951f-a666b5788ddc',
|
||||
timestamp: '2022-03-23T17:37:07.098Z',
|
||||
type: 'actions',
|
||||
message:
|
||||
'action execution failure: .server-log:9e67b8b0-9e2c-11ec-bd64-774ed95c43ef: s - an error occurred while running the action executor: something funky with the server log',
|
||||
},
|
||||
{
|
||||
id: '08d9b0f5-0b41-47c9-951f-a666b5788ddc',
|
||||
timestamp: '2022-03-23T17:37:07.096Z',
|
||||
type: 'actions',
|
||||
message:
|
||||
'action execution failure: .server-log:9e67b8b0-9e2c-11ec-bd64-774ed95c43ef: s - an error occurred while running the action executor: something funky with the server log',
|
||||
},
|
||||
{
|
||||
id: '08d9b0f5-0b41-47c9-951f-a666b5788ddc',
|
||||
timestamp: '2022-03-23T17:37:07.086Z',
|
||||
type: 'actions',
|
||||
message:
|
||||
'action execution failure: .server-log:9e67b8b0-9e2c-11ec-bd64-774ed95c43ef: s - an error occurred while running the action executor: something funky with the server log',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -385,6 +385,7 @@ describe('getExecutionLogForRule()', () => {
|
|||
schedule_delay_ms: 3126,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule-name',
|
||||
space_ids: [],
|
||||
},
|
||||
{
|
||||
id: '41b2755e-765a-4044-9745-b03875d5e79a',
|
||||
|
@ -407,6 +408,7 @@ describe('getExecutionLogForRule()', () => {
|
|||
schedule_delay_ms: 3345,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule-name',
|
||||
space_ids: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -719,6 +721,7 @@ describe('getGlobalExecutionLogWithAuth()', () => {
|
|||
schedule_delay_ms: 3126,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule-name',
|
||||
space_ids: [],
|
||||
},
|
||||
{
|
||||
id: '41b2755e-765a-4044-9745-b03875d5e79a',
|
||||
|
@ -741,6 +744,7 @@ describe('getGlobalExecutionLogWithAuth()', () => {
|
|||
schedule_delay_ms: 3345,
|
||||
rule_id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef',
|
||||
rule_name: 'rule-name',
|
||||
space_ids: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ const createClusterClientMock = () => {
|
|||
getExistingIndexAliases: jest.fn(),
|
||||
setIndexAliasToHidden: jest.fn(),
|
||||
queryEventsBySavedObjects: jest.fn(),
|
||||
queryEventsWithAuthFilter: jest.fn(),
|
||||
aggregateEventsBySavedObjects: jest.fn(),
|
||||
aggregateEventsWithAuthFilter: jest.fn(),
|
||||
shutdown: jest.fn(),
|
||||
|
|
|
@ -779,7 +779,7 @@ describe('aggregateEventsWithAuthFilter', () => {
|
|||
});
|
||||
const options: AggregateEventsWithAuthFilter = {
|
||||
index: 'index-name',
|
||||
namespace: 'namespace',
|
||||
namespaces: ['namespace'],
|
||||
type: 'saved-object-type',
|
||||
aggregateOptions: DEFAULT_OPTIONS as AggregateOptionsType,
|
||||
authFilter: fromKueryExpression('test:test'),
|
||||
|
@ -1515,7 +1515,7 @@ describe('getQueryBody', () => {
|
|||
describe('getQueryBodyWithAuthFilter', () => {
|
||||
const options = {
|
||||
index: 'index-name',
|
||||
namespace: undefined,
|
||||
namespaces: undefined,
|
||||
type: 'saved-object-type',
|
||||
authFilter: fromKueryExpression('test:test'),
|
||||
};
|
||||
|
@ -1559,11 +1559,17 @@ describe('getQueryBodyWithAuthFilter', () => {
|
|||
},
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'kibana.saved_objects.namespace',
|
||||
should: [
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'kibana.saved_objects.namespace',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1580,7 +1586,7 @@ describe('getQueryBodyWithAuthFilter', () => {
|
|||
expect(
|
||||
getQueryBodyWithAuthFilter(
|
||||
logger,
|
||||
{ ...options, namespace: 'namespace' } as AggregateEventsWithAuthFilter,
|
||||
{ ...options, namespaces: ['namespace'] } as AggregateEventsWithAuthFilter,
|
||||
{}
|
||||
)
|
||||
).toEqual({
|
||||
|
@ -1619,10 +1625,16 @@ describe('getQueryBodyWithAuthFilter', () => {
|
|||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'kibana.saved_objects.namespace': {
|
||||
value: 'namespace',
|
||||
},
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
term: {
|
||||
'kibana.saved_objects.namespace': {
|
||||
value: 'namespace',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1713,11 +1725,17 @@ describe('getQueryBodyWithAuthFilter', () => {
|
|||
},
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'kibana.saved_objects.namespace',
|
||||
should: [
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'kibana.saved_objects.namespace',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1772,11 +1790,17 @@ describe('getQueryBodyWithAuthFilter', () => {
|
|||
},
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'kibana.saved_objects.namespace',
|
||||
should: [
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'kibana.saved_objects.namespace',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1838,11 +1862,17 @@ describe('getQueryBodyWithAuthFilter', () => {
|
|||
},
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'kibana.saved_objects.namespace',
|
||||
should: [
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'kibana.saved_objects.namespace',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1905,11 +1935,17 @@ describe('getQueryBodyWithAuthFilter', () => {
|
|||
},
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'kibana.saved_objects.namespace',
|
||||
should: [
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'kibana.saved_objects.namespace',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -50,14 +50,26 @@ interface QueryOptionsEventsBySavedObjectFilter {
|
|||
legacyIds?: string[];
|
||||
}
|
||||
|
||||
export interface AggregateEventsWithAuthFilter {
|
||||
interface QueryOptionsEventsWithAuthFilter {
|
||||
index: string;
|
||||
namespace: string | undefined;
|
||||
type: string;
|
||||
ids: string[];
|
||||
authFilter: KueryNode;
|
||||
}
|
||||
|
||||
export interface AggregateEventsWithAuthFilter {
|
||||
index: string;
|
||||
namespaces?: Array<string | undefined>;
|
||||
type: string;
|
||||
authFilter: KueryNode;
|
||||
aggregateOptions: AggregateOptionsType;
|
||||
}
|
||||
|
||||
export type FindEventsOptionsWithAuthFilter = QueryOptionsEventsWithAuthFilter & {
|
||||
findOptions: FindOptionsType;
|
||||
};
|
||||
|
||||
export type FindEventsOptionsBySavedObjectFilter = QueryOptionsEventsBySavedObjectFilter & {
|
||||
findOptions: FindOptionsType;
|
||||
};
|
||||
|
@ -70,6 +82,12 @@ export interface AggregateEventsBySavedObjectResult {
|
|||
aggregations: Record<string, estypes.AggregationsAggregate> | undefined;
|
||||
}
|
||||
|
||||
type GetQueryBodyWithAuthFilterOpts =
|
||||
| (FindEventsOptionsWithAuthFilter & {
|
||||
namespaces: AggregateEventsWithAuthFilter['namespaces'];
|
||||
})
|
||||
| AggregateEventsWithAuthFilter;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type AliasAny = any;
|
||||
|
||||
|
@ -389,6 +407,50 @@ export class ClusterClientAdapter<TDoc extends { body: AliasAny; index: string }
|
|||
}
|
||||
}
|
||||
|
||||
public async queryEventsWithAuthFilter(
|
||||
queryOptions: FindEventsOptionsWithAuthFilter
|
||||
): Promise<QueryEventsBySavedObjectResult> {
|
||||
const { index, type, ids, findOptions } = queryOptions;
|
||||
const { page, per_page: perPage, sort } = findOptions;
|
||||
|
||||
const esClient = await this.elasticsearchClientPromise;
|
||||
|
||||
const query = getQueryBodyWithAuthFilter(
|
||||
this.logger,
|
||||
{ ...queryOptions, namespaces: [queryOptions.namespace] },
|
||||
pick(queryOptions.findOptions, ['start', 'end', 'filter'])
|
||||
);
|
||||
|
||||
const body: estypes.SearchRequest['body'] = {
|
||||
size: perPage,
|
||||
from: (page - 1) * perPage,
|
||||
query,
|
||||
...(sort
|
||||
? { sort: sort.map((s) => ({ [s.sort_field]: { order: s.sort_order } })) as estypes.Sort }
|
||||
: {}),
|
||||
};
|
||||
|
||||
try {
|
||||
const {
|
||||
hits: { hits, total },
|
||||
} = await esClient.search<IValidatedEvent>({
|
||||
index,
|
||||
track_total_hits: true,
|
||||
body,
|
||||
});
|
||||
return {
|
||||
page,
|
||||
per_page: perPage,
|
||||
total: isNumber(total) ? total : total!.value,
|
||||
data: hits.map((hit) => hit._source),
|
||||
};
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`querying for Event Log by for type "${type}" and ids "${ids}" failed with: ${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async aggregateEventsBySavedObjects(
|
||||
queryOptions: AggregateEventsOptionsBySavedObjectFilter
|
||||
): Promise<AggregateEventsBySavedObjectResult> {
|
||||
|
@ -462,13 +524,15 @@ export class ClusterClientAdapter<TDoc extends { body: AliasAny; index: string }
|
|||
|
||||
export function getQueryBodyWithAuthFilter(
|
||||
logger: Logger,
|
||||
opts: AggregateEventsWithAuthFilter,
|
||||
opts: GetQueryBodyWithAuthFilterOpts,
|
||||
queryOptions: QueryOptionsType
|
||||
) {
|
||||
const { namespace, type, authFilter } = opts;
|
||||
const { namespaces, type, authFilter } = opts;
|
||||
const { start, end, filter } = queryOptions ?? {};
|
||||
|
||||
const namespaceQuery = getNamespaceQuery(namespace);
|
||||
const namespaceQuery = (namespaces ?? [undefined]).map((namespace) =>
|
||||
getNamespaceQuery(namespace)
|
||||
);
|
||||
let dslFilterQuery: estypes.QueryDslBoolQuery['filter'];
|
||||
try {
|
||||
const filterKueryNode = filter ? fromKueryExpression(filter) : null;
|
||||
|
@ -501,8 +565,12 @@ export function getQueryBodyWithAuthFilter(
|
|||
},
|
||||
},
|
||||
},
|
||||
// @ts-expect-error undefined is not assignable as QueryDslTermQuery value
|
||||
namespaceQuery,
|
||||
{
|
||||
bool: {
|
||||
// @ts-expect-error undefined is not assignable as QueryDslTermQuery value
|
||||
should: namespaceQuery,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const musts: estypes.QueryDslQueryContainer[] = [
|
||||
|
|
|
@ -10,6 +10,7 @@ import { IEventLogClient } from './types';
|
|||
const createEventLogClientMock = () => {
|
||||
const mock: jest.Mocked<IEventLogClient> = {
|
||||
findEventsBySavedObjectIds: jest.fn(),
|
||||
findEventsWithAuthFilter: jest.fn(),
|
||||
aggregateEventsBySavedObjectIds: jest.fn(),
|
||||
aggregateEventsWithAuthFilter: jest.fn(),
|
||||
};
|
||||
|
|
|
@ -256,7 +256,7 @@ describe('EventLogStart', () => {
|
|||
});
|
||||
expect(esContext.esAdapter.aggregateEventsWithAuthFilter).toHaveBeenCalledWith({
|
||||
index: esContext.esNames.indexPattern,
|
||||
namespace: undefined,
|
||||
namespaces: [undefined],
|
||||
type: 'saved-object-type',
|
||||
authFilter: testAuthFilter,
|
||||
aggregateOptions: {
|
||||
|
|
|
@ -112,6 +112,31 @@ export class EventLogClient implements IEventLogClient {
|
|||
});
|
||||
}
|
||||
|
||||
public async findEventsWithAuthFilter(
|
||||
type: string,
|
||||
ids: string[],
|
||||
authFilter: KueryNode,
|
||||
namespace: string | undefined,
|
||||
options?: Partial<FindOptionsType>
|
||||
): Promise<QueryEventsBySavedObjectResult> {
|
||||
if (!authFilter) {
|
||||
throw new Error('No authorization filter defined!');
|
||||
}
|
||||
|
||||
const findOptions = queryOptionsSchema.validate(options ?? {});
|
||||
|
||||
return await this.esContext.esAdapter.queryEventsWithAuthFilter({
|
||||
index: this.esContext.esNames.indexPattern,
|
||||
namespace: namespace
|
||||
? this.spacesService?.spaceIdToNamespace(namespace)
|
||||
: await this.getNamespace(),
|
||||
type,
|
||||
ids,
|
||||
findOptions,
|
||||
authFilter,
|
||||
});
|
||||
}
|
||||
|
||||
public async aggregateEventsBySavedObjectIds(
|
||||
type: string,
|
||||
ids: string[],
|
||||
|
@ -142,7 +167,8 @@ export class EventLogClient implements IEventLogClient {
|
|||
public async aggregateEventsWithAuthFilter(
|
||||
type: string,
|
||||
authFilter: KueryNode,
|
||||
options?: AggregateOptionsType
|
||||
options?: AggregateOptionsType,
|
||||
namespaces?: Array<string | undefined>
|
||||
) {
|
||||
if (!authFilter) {
|
||||
throw new Error('No authorization filter defined!');
|
||||
|
@ -158,7 +184,7 @@ export class EventLogClient implements IEventLogClient {
|
|||
|
||||
return await this.esContext.esAdapter.aggregateEventsWithAuthFilter({
|
||||
index: this.esContext.esNames.indexPattern,
|
||||
namespace: await this.getNamespace(),
|
||||
namespaces: namespaces ?? [await this.getNamespace()],
|
||||
type,
|
||||
authFilter,
|
||||
aggregateOptions: { ...aggregateOptions, aggs } as AggregateOptionsType,
|
||||
|
|
|
@ -57,6 +57,13 @@ export interface IEventLogClient {
|
|||
options?: Partial<FindOptionsType>,
|
||||
legacyIds?: string[]
|
||||
): Promise<QueryEventsBySavedObjectResult>;
|
||||
findEventsWithAuthFilter(
|
||||
type: string,
|
||||
ids: string[],
|
||||
authFilter: KueryNode,
|
||||
namespace: string | undefined,
|
||||
options?: Partial<FindOptionsType>
|
||||
): Promise<QueryEventsBySavedObjectResult>;
|
||||
aggregateEventsBySavedObjectIds(
|
||||
type: string,
|
||||
ids: string[],
|
||||
|
@ -66,7 +73,8 @@ export interface IEventLogClient {
|
|||
aggregateEventsWithAuthFilter(
|
||||
type: string,
|
||||
authFilter: KueryNode,
|
||||
options?: Partial<AggregateOptionsType>
|
||||
options?: Partial<AggregateOptionsType>,
|
||||
namespaces?: Array<string | undefined>
|
||||
): Promise<AggregateEventsBySavedObjectResult>;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ export const DEFAULT_RULE_INTERVAL = '1m';
|
|||
export const RULE_EXECUTION_LOG_COLUMN_IDS = [
|
||||
'rule_id',
|
||||
'rule_name',
|
||||
'space_ids',
|
||||
'id',
|
||||
'timestamp',
|
||||
'execution_duration',
|
||||
|
|
|
@ -118,9 +118,11 @@ describe('loadActionErrorLog', () => {
|
|||
"date_end": "2022-03-23T16:17:53.482Z",
|
||||
"date_start": "2022-03-23T16:17:53.482Z",
|
||||
"filter": "(message: \\"test\\" OR error.message: \\"test\\") and kibana.alert.rule.execution.uuid: 123",
|
||||
"namespace": undefined,
|
||||
"page": 1,
|
||||
"per_page": 10,
|
||||
"sort": "[{\\"@timestamp\\":{\\"order\\":\\"asc\\"}}]",
|
||||
"with_auth": false,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -28,6 +28,8 @@ export interface LoadActionErrorLogProps {
|
|||
perPage?: number;
|
||||
page?: number;
|
||||
sort?: SortField[];
|
||||
namespace?: string;
|
||||
withAuth?: boolean;
|
||||
}
|
||||
|
||||
const SORT_MAP: Record<string, string> = {
|
||||
|
@ -60,6 +62,8 @@ export const loadActionErrorLog = ({
|
|||
perPage = 10,
|
||||
page = 0,
|
||||
sort,
|
||||
namespace,
|
||||
withAuth = false,
|
||||
}: LoadActionErrorLogProps & { http: HttpSetup }) => {
|
||||
const renamedSort = getRenamedSort(sort);
|
||||
const filter = getFilter({ runId, message });
|
||||
|
@ -76,6 +80,8 @@ export const loadActionErrorLog = ({
|
|||
// whereas data grid sorts are 0 indexed.
|
||||
page: page + 1,
|
||||
sort: renamedSort.length ? JSON.stringify(renamedSort) : undefined,
|
||||
namespace,
|
||||
with_auth: withAuth,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
@ -59,7 +59,10 @@ export interface LoadExecutionLogAggregationsProps {
|
|||
sort?: SortField[];
|
||||
}
|
||||
|
||||
export type LoadGlobalExecutionLogAggregationsProps = Omit<LoadExecutionLogAggregationsProps, 'id'>;
|
||||
export type LoadGlobalExecutionLogAggregationsProps = Omit<
|
||||
LoadExecutionLogAggregationsProps,
|
||||
'id'
|
||||
> & { namespaces?: Array<string | undefined> };
|
||||
|
||||
export const loadExecutionLogAggregations = async ({
|
||||
id,
|
||||
|
@ -103,6 +106,7 @@ export const loadGlobalExecutionLogAggregations = async ({
|
|||
perPage = 10,
|
||||
page = 0,
|
||||
sort = [],
|
||||
namespaces,
|
||||
}: LoadGlobalExecutionLogAggregationsProps & { http: HttpSetup }) => {
|
||||
const sortField: any[] = sort;
|
||||
const filter = getFilter({ outcomeFilter, message });
|
||||
|
@ -119,6 +123,7 @@ export const loadGlobalExecutionLogAggregations = async ({
|
|||
// whereas data grid sorts are 0 indexed.
|
||||
page: page + 1,
|
||||
sort: sortField.length ? JSON.stringify(sortField) : undefined,
|
||||
namespaces: namespaces ? JSON.stringify(namespaces) : undefined,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ export interface LoadGlobalExecutionKPIAggregationsProps {
|
|||
message?: string;
|
||||
dateStart: string;
|
||||
dateEnd?: string;
|
||||
namespaces?: Array<string | undefined>;
|
||||
}
|
||||
|
||||
export const loadGlobalExecutionKPIAggregations = ({
|
||||
|
@ -25,6 +26,7 @@ export const loadGlobalExecutionKPIAggregations = ({
|
|||
message,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
namespaces,
|
||||
}: LoadGlobalExecutionKPIAggregationsProps & { http: HttpSetup }) => {
|
||||
const filter = getFilter({ outcomeFilter, message });
|
||||
|
||||
|
@ -33,6 +35,7 @@ export const loadGlobalExecutionKPIAggregations = ({
|
|||
filter: filter.length ? filter.join(' and ') : undefined,
|
||||
date_start: dateStart,
|
||||
date_end: dateEnd,
|
||||
namespaces: namespaces ? JSON.stringify(namespaces) : namespaces,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ export const LogsList = () => {
|
|||
refreshToken: 0,
|
||||
initialPageSize: 50,
|
||||
hasRuleNames: true,
|
||||
hasAllSpaceSwitch: true,
|
||||
localStorageKey: GLOBAL_EVENT_LOG_LIST_STORAGE_KEY,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiTitle,
|
||||
|
@ -28,17 +28,29 @@ export interface RuleActionErrorLogFlyoutProps {
|
|||
runLog: IExecutionLog;
|
||||
refreshToken?: number;
|
||||
onClose: () => void;
|
||||
activeSpaceId?: string;
|
||||
}
|
||||
|
||||
export const RuleActionErrorLogFlyout = (props: RuleActionErrorLogFlyoutProps) => {
|
||||
const { runLog, refreshToken, onClose } = props;
|
||||
const { runLog, refreshToken, onClose, activeSpaceId } = props;
|
||||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const { id, rule_id: ruleId, message, num_errored_actions: totalErrors } = runLog;
|
||||
const {
|
||||
id,
|
||||
rule_id: ruleId,
|
||||
message,
|
||||
num_errored_actions: totalErrors,
|
||||
space_ids: spaceIds = [],
|
||||
} = runLog;
|
||||
|
||||
const isFlyoutPush = useIsWithinBreakpoints(['xl']);
|
||||
|
||||
const logFromDifferentSpace = useMemo(
|
||||
() => Boolean(activeSpaceId && !spaceIds?.includes(activeSpaceId)),
|
||||
[activeSpaceId, spaceIds]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
type={isFlyoutPush ? 'push' : 'overlay'}
|
||||
|
@ -82,7 +94,13 @@ export const RuleActionErrorLogFlyout = (props: RuleActionErrorLogFlyoutProps) =
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
<RuleErrorLogWithApi ruleId={ruleId} runId={id} refreshToken={refreshToken} />
|
||||
<RuleErrorLogWithApi
|
||||
ruleId={ruleId}
|
||||
runId={id}
|
||||
spaceId={spaceIds[0]}
|
||||
logFromDifferentSpace={logFromDifferentSpace}
|
||||
refreshToken={refreshToken}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -63,11 +63,13 @@ export type RuleErrorLogProps = {
|
|||
ruleId: string;
|
||||
runId?: string;
|
||||
refreshToken?: number;
|
||||
spaceId?: string;
|
||||
logFromDifferentSpace?: boolean;
|
||||
requestRefresh?: () => Promise<void>;
|
||||
} & Pick<RuleApis, 'loadActionErrorLog'>;
|
||||
|
||||
export const RuleErrorLog = (props: RuleErrorLogProps) => {
|
||||
const { ruleId, runId, loadActionErrorLog, refreshToken } = props;
|
||||
const { ruleId, runId, loadActionErrorLog, refreshToken, spaceId, logFromDifferentSpace } = props;
|
||||
|
||||
const { uiSettings, notifications } = useKibana().services;
|
||||
|
||||
|
@ -138,6 +140,8 @@ export const RuleErrorLog = (props: RuleErrorLogProps) => {
|
|||
page: pagination.pageIndex,
|
||||
perPage: pagination.pageSize,
|
||||
sort: formattedSort,
|
||||
namespace: spaceId,
|
||||
withAuth: logFromDifferentSpace,
|
||||
});
|
||||
setLogs(result.errors);
|
||||
setPagination({
|
||||
|
|
|
@ -60,6 +60,7 @@ export interface RuleEventLogDataGrid {
|
|||
pageSizeOptions?: number[];
|
||||
selectedRunLog?: IExecutionLog;
|
||||
showRuleNameAndIdColumns?: boolean;
|
||||
showSpaceColumns?: boolean;
|
||||
onChangeItemsPerPage: (pageSize: number) => void;
|
||||
onChangePage: (pageIndex: number) => void;
|
||||
onFilterChange: (filter: string[]) => void;
|
||||
|
@ -162,6 +163,7 @@ export const RuleEventLogDataGrid = (props: RuleEventLogDataGrid) => {
|
|||
visibleColumns,
|
||||
selectedRunLog,
|
||||
showRuleNameAndIdColumns = false,
|
||||
showSpaceColumns = false,
|
||||
setVisibleColumns,
|
||||
setSortingColumns,
|
||||
onChangeItemsPerPage,
|
||||
|
@ -215,6 +217,25 @@ export const RuleEventLogDataGrid = (props: RuleEventLogDataGrid) => {
|
|||
},
|
||||
]
|
||||
: []),
|
||||
...(showSpaceColumns
|
||||
? [
|
||||
{
|
||||
id: 'space_ids',
|
||||
displayAsText: i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.ruleDetails.eventLogColumn.spaceIds',
|
||||
{
|
||||
defaultMessage: 'Space',
|
||||
}
|
||||
),
|
||||
isSortable: getIsColumnSortable('space_ids'),
|
||||
actions: {
|
||||
showSortAsc: false,
|
||||
showSortDesc: false,
|
||||
showHide: false,
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
id: 'id',
|
||||
displayAsText: i18n.translate(
|
||||
|
@ -429,16 +450,22 @@ export const RuleEventLogDataGrid = (props: RuleEventLogDataGrid) => {
|
|||
isSortable: getIsColumnSortable('timed_out'),
|
||||
},
|
||||
],
|
||||
[getPaginatedRowIndex, onFlyoutOpen, onFilterChange, showRuleNameAndIdColumns, logs]
|
||||
[
|
||||
getPaginatedRowIndex,
|
||||
onFlyoutOpen,
|
||||
onFilterChange,
|
||||
showRuleNameAndIdColumns,
|
||||
showSpaceColumns,
|
||||
logs,
|
||||
]
|
||||
);
|
||||
|
||||
const columnVisibilityProps = useMemo(
|
||||
() => ({
|
||||
const columnVisibilityProps = useMemo(() => {
|
||||
return {
|
||||
visibleColumns,
|
||||
setVisibleColumns,
|
||||
}),
|
||||
[visibleColumns, setVisibleColumns]
|
||||
);
|
||||
};
|
||||
}, [visibleColumns, setVisibleColumns]);
|
||||
|
||||
const sortingProps = useMemo(
|
||||
() => ({
|
||||
|
@ -560,6 +587,7 @@ export const RuleEventLogDataGrid = (props: RuleEventLogDataGrid) => {
|
|||
const actionErrors = logs[pagedRowIndex]?.num_errored_actions || (0 as number);
|
||||
const version = logs?.[pagedRowIndex]?.version;
|
||||
const ruleId = runLog?.rule_id;
|
||||
const spaceIds = runLog?.space_ids;
|
||||
|
||||
if (columnId === 'num_errored_actions' && runLog) {
|
||||
return (
|
||||
|
@ -592,6 +620,7 @@ export const RuleEventLogDataGrid = (props: RuleEventLogDataGrid) => {
|
|||
version={version}
|
||||
dateFormat={dateFormat}
|
||||
ruleId={ruleId}
|
||||
spaceIds={spaceIds}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { EuiIcon, EuiLink } from '@elastic/eui';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import {
|
||||
RuleEventLogListCellRenderer,
|
||||
|
@ -16,7 +16,53 @@ import {
|
|||
import { RuleEventLogListStatus } from './rule_event_log_list_status';
|
||||
import { RuleDurationFormat } from '../../rules_list/components/rule_duration_format';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: () => ({
|
||||
location: {
|
||||
pathname: '/logs',
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../common/lib/kibana', () => ({
|
||||
useSpacesData: () => ({
|
||||
spacesMap: new Map([
|
||||
['space1', { id: 'space1' }],
|
||||
['space2', { id: 'space2' }],
|
||||
]),
|
||||
activeSpaceId: 'space1',
|
||||
}),
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
http: {
|
||||
basePath: {
|
||||
get: () => '/basePath',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('rule_event_log_list_cell_renderer', () => {
|
||||
const savedLocation = window.location;
|
||||
beforeAll(() => {
|
||||
// @ts-ignore Mocking window.location
|
||||
delete window.location;
|
||||
// @ts-ignore
|
||||
window.location = Object.assign(
|
||||
new URL('https://localhost/app/management/insightsAndAlerting/triggersActions/logs'),
|
||||
{
|
||||
ancestorOrigins: '',
|
||||
assign: jest.fn(),
|
||||
reload: jest.fn(),
|
||||
replace: jest.fn(),
|
||||
}
|
||||
);
|
||||
});
|
||||
afterAll(() => {
|
||||
window.location = savedLocation;
|
||||
});
|
||||
|
||||
it('renders primitive values correctly', () => {
|
||||
const wrapper = mount(<RuleEventLogListCellRenderer columnId="message" value="test" />);
|
||||
|
||||
|
@ -67,4 +113,31 @@ describe('rule_event_log_list_cell_renderer', () => {
|
|||
expect(wrapper.find(RuleEventLogListStatus).text()).toEqual('newOutcome');
|
||||
expect(wrapper.find(EuiIcon).props().color).toEqual('gray');
|
||||
});
|
||||
|
||||
it('links to rules on the correct space', () => {
|
||||
const wrapper1 = shallow(
|
||||
<RuleEventLogListCellRenderer
|
||||
columnId="rule_name"
|
||||
value="Rule"
|
||||
ruleId="1"
|
||||
spaceIds={['space1']}
|
||||
/>
|
||||
);
|
||||
// @ts-ignore data-href is not a native EuiLink prop
|
||||
expect(wrapper1.find(EuiLink).props()['data-href']).toEqual('/rule/1');
|
||||
const wrapper2 = shallow(
|
||||
<RuleEventLogListCellRenderer
|
||||
columnId="rule_name"
|
||||
value="Rule"
|
||||
ruleId="1"
|
||||
spaceIds={['space2']}
|
||||
/>
|
||||
);
|
||||
// @ts-ignore data-href is not a native EuiLink prop
|
||||
expect(wrapper2.find(EuiLink).props()['data-href']).toEqual(
|
||||
'/basePath/s/space2/app/management/insightsAndAlerting/triggersActions/rule/1'
|
||||
);
|
||||
|
||||
window.location = savedLocation;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import moment from 'moment';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import { RuleAlertingOutcome } from '@kbn/alerting-plugin/common';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { routeToRuleDetails } from '../../../constants';
|
||||
import { formatRuleAlertCount } from '../../../../common/lib/format_rule_alert_count';
|
||||
import { useKibana, useSpacesData } from '../../../../common/lib/kibana';
|
||||
import { RuleEventLogListStatus } from './rule_event_log_list_status';
|
||||
import { RuleDurationFormat } from '../../rules_list/components/rule_duration_format';
|
||||
import {
|
||||
|
@ -27,20 +28,58 @@ export type ColumnId = typeof RULE_EXECUTION_LOG_COLUMN_IDS[number];
|
|||
interface RuleEventLogListCellRendererProps {
|
||||
columnId: ColumnId;
|
||||
version?: string;
|
||||
value?: string;
|
||||
value?: string | string[];
|
||||
dateFormat?: string;
|
||||
ruleId?: string;
|
||||
spaceIds?: string[];
|
||||
}
|
||||
|
||||
export const RuleEventLogListCellRenderer = (props: RuleEventLogListCellRendererProps) => {
|
||||
const { columnId, value, version, dateFormat = DEFAULT_DATE_FORMAT, ruleId } = props;
|
||||
const { columnId, value, version, dateFormat = DEFAULT_DATE_FORMAT, ruleId, spaceIds } = props;
|
||||
const spacesData = useSpacesData();
|
||||
const { http } = useKibana().services;
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
const onClickRuleName = useCallback(
|
||||
() => ruleId && history.push(routeToRuleDetails.replace(':ruleId', ruleId)),
|
||||
[ruleId, history]
|
||||
const activeSpace = useMemo(
|
||||
() => spacesData?.spacesMap.get(spacesData?.activeSpaceId),
|
||||
[spacesData]
|
||||
);
|
||||
|
||||
const ruleOnDifferentSpace = useMemo(
|
||||
() => activeSpace && !spaceIds?.includes(activeSpace.id),
|
||||
[activeSpace, spaceIds]
|
||||
);
|
||||
|
||||
const ruleNamePathname = useMemo(() => {
|
||||
if (!ruleId) return '';
|
||||
const ruleRoute = routeToRuleDetails.replace(':ruleId', ruleId);
|
||||
if (ruleOnDifferentSpace) {
|
||||
const [linkedSpaceId] = spaceIds ?? [];
|
||||
const basePath = http.basePath.get();
|
||||
const spacePath = linkedSpaceId !== 'default' ? `/s/${linkedSpaceId}` : '';
|
||||
const historyPathname = history.location.pathname;
|
||||
const newPathname = `${basePath.replace(
|
||||
`/s/${activeSpace!.id}`,
|
||||
''
|
||||
)}${spacePath}${window.location.pathname
|
||||
.replace(basePath, '')
|
||||
.replace(historyPathname, ruleRoute)}`;
|
||||
return newPathname;
|
||||
}
|
||||
return ruleRoute;
|
||||
}, [ruleId, ruleOnDifferentSpace, history, activeSpace, http, spaceIds]);
|
||||
|
||||
const onClickRuleName = useCallback(() => {
|
||||
if (!ruleId) return;
|
||||
if (ruleOnDifferentSpace) {
|
||||
const newUrl = window.location.href.replace(window.location.pathname, ruleNamePathname);
|
||||
window.open(newUrl, '_blank');
|
||||
return;
|
||||
}
|
||||
history.push(ruleNamePathname);
|
||||
}, [ruleNamePathname, history, ruleOnDifferentSpace, ruleId]);
|
||||
|
||||
if (typeof value === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
@ -54,15 +93,24 @@ export const RuleEventLogListCellRenderer = (props: RuleEventLogListCellRenderer
|
|||
}
|
||||
|
||||
if (columnId === 'rule_name' && ruleId) {
|
||||
return <EuiLink onClick={onClickRuleName}>{value}</EuiLink>;
|
||||
return (
|
||||
<EuiLink onClick={onClickRuleName} data-href={ruleNamePathname}>
|
||||
{value}
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
|
||||
if (columnId === 'space_ids') {
|
||||
if (activeSpace && value.includes(activeSpace.id)) return <>{activeSpace.name}</>;
|
||||
if (spacesData) return <>{spacesData.spacesMap.get(value[0])?.name ?? value[0]}</>;
|
||||
}
|
||||
|
||||
if (RULE_EXECUTION_LOG_ALERT_COUNT_COLUMNS.includes(columnId)) {
|
||||
return <>{formatRuleAlertCount(value, version)}</>;
|
||||
return <>{formatRuleAlertCount(value as string, version)}</>;
|
||||
}
|
||||
|
||||
if (RULE_EXECUTION_LOG_DURATION_COLUMNS.includes(columnId)) {
|
||||
return <RuleDurationFormat duration={parseInt(value, 10)} />;
|
||||
return <RuleDurationFormat duration={parseInt(value as string, 10)} />;
|
||||
}
|
||||
|
||||
return <>{value}</>;
|
||||
|
|
|
@ -84,6 +84,7 @@ export type RuleEventLogListKPIProps = {
|
|||
outcomeFilter?: string[];
|
||||
message?: string;
|
||||
refreshToken?: number;
|
||||
namespaces?: Array<string | undefined>;
|
||||
} & Pick<RuleApis, 'loadExecutionKPIAggregations' | 'loadGlobalExecutionKPIAggregations'>;
|
||||
|
||||
export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => {
|
||||
|
@ -94,6 +95,7 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => {
|
|||
outcomeFilter,
|
||||
message,
|
||||
refreshToken,
|
||||
namespaces,
|
||||
loadExecutionKPIAggregations,
|
||||
loadGlobalExecutionKPIAggregations,
|
||||
} = props;
|
||||
|
@ -122,6 +124,7 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => {
|
|||
dateEnd: getParsedDate(dateEnd),
|
||||
outcomeFilter,
|
||||
message,
|
||||
...(namespaces ? { namespaces } : {}),
|
||||
});
|
||||
setKpi(newKpi);
|
||||
} catch (e) {
|
||||
|
@ -136,7 +139,7 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => {
|
|||
useEffect(() => {
|
||||
loadKPIs();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [ruleId, dateStart, dateEnd, outcomeFilter, message]);
|
||||
}, [ruleId, dateStart, dateEnd, outcomeFilter, message, namespaces]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized.current) {
|
||||
|
|
|
@ -18,9 +18,11 @@ import {
|
|||
Pagination,
|
||||
EuiSuperDatePicker,
|
||||
OnTimeChangeProps,
|
||||
EuiSwitch,
|
||||
} from '@elastic/eui';
|
||||
import { IExecutionLog } from '@kbn/alerting-plugin/common';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { SpacesContextProps } from '@kbn/spaces-plugin/public';
|
||||
import { useKibana, useSpacesData } from '../../../../common/lib/kibana';
|
||||
import {
|
||||
RULE_EXECUTION_DEFAULT_INITIAL_VISIBLE_COLUMNS,
|
||||
GLOBAL_EXECUTION_DEFAULT_INITIAL_VISIBLE_COLUMNS,
|
||||
|
@ -38,6 +40,8 @@ import {
|
|||
withBulkRuleOperations,
|
||||
} from '../../common/components/with_bulk_rule_api_operations';
|
||||
|
||||
const getEmptyFunctionComponent: React.FC<SpacesContextProps> = ({ children }) => <>{children}</>;
|
||||
|
||||
const getParsedDate = (date: string) => {
|
||||
if (date.includes('now')) {
|
||||
return datemath.parse(date)?.format() || date;
|
||||
|
@ -66,6 +70,13 @@ const getDefaultColumns = (columns: string[]) => {
|
|||
return [...LOCKED_COLUMNS, ...columnsWithoutLockedColumn];
|
||||
};
|
||||
|
||||
const ALL_SPACES_LABEL = i18n.translate(
|
||||
'xpack.triggersActionsUI.ruleEventLogList.showAllSpacesToggle',
|
||||
{
|
||||
defaultMessage: 'Show rules from all spaces',
|
||||
}
|
||||
);
|
||||
|
||||
const updateButtonProps = {
|
||||
iconOnly: true,
|
||||
fill: false,
|
||||
|
@ -84,6 +95,7 @@ export type RuleEventLogListCommonProps = {
|
|||
overrideLoadExecutionLogAggregations?: RuleApis['loadExecutionLogAggregations'];
|
||||
overrideLoadGlobalExecutionLogAggregations?: RuleApis['loadGlobalExecutionLogAggregations'];
|
||||
hasRuleNames?: boolean;
|
||||
hasAllSpaceSwitch?: boolean;
|
||||
} & Pick<RuleApis, 'loadExecutionLogAggregations' | 'loadGlobalExecutionLogAggregations'>;
|
||||
|
||||
export type RuleEventLogListTableProps<T extends RuleEventLogListOptions = 'default'> =
|
||||
|
@ -106,6 +118,7 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
overrideLoadExecutionLogAggregations,
|
||||
initialPageSize = 10,
|
||||
hasRuleNames = false,
|
||||
hasAllSpaceSwitch = false,
|
||||
} = props;
|
||||
|
||||
const { uiSettings, notifications } = useKibana().services;
|
||||
|
@ -117,6 +130,7 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
const [internalRefreshToken, setInternalRefreshToken] = useState<number | undefined>(
|
||||
refreshToken
|
||||
);
|
||||
const [showFromAllSpaces, setShowFromAllSpaces] = useState(false);
|
||||
|
||||
// Data grid states
|
||||
const [logs, setLogs] = useState<IExecutionLog[]>();
|
||||
|
@ -153,6 +167,24 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
);
|
||||
});
|
||||
|
||||
const spacesData = useSpacesData();
|
||||
const accessibleSpaceIds = useMemo(
|
||||
() => (spacesData ? [...spacesData.spacesMap.values()].map((e) => e.id) : []),
|
||||
[spacesData]
|
||||
);
|
||||
const areMultipleSpacesAccessible = useMemo(
|
||||
() => accessibleSpaceIds.length > 1,
|
||||
[accessibleSpaceIds]
|
||||
);
|
||||
const namespaces = useMemo(
|
||||
() => (showFromAllSpaces && spacesData ? accessibleSpaceIds : undefined),
|
||||
[showFromAllSpaces, spacesData, accessibleSpaceIds]
|
||||
);
|
||||
const activeSpace = useMemo(
|
||||
() => spacesData?.spacesMap.get(spacesData?.activeSpaceId),
|
||||
[spacesData]
|
||||
);
|
||||
|
||||
const isInitialized = useRef(false);
|
||||
|
||||
const isOnLastPage = useMemo(() => {
|
||||
|
@ -197,6 +229,7 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
dateEnd: getParsedDate(dateEnd),
|
||||
page: pagination.pageIndex,
|
||||
perPage: pagination.pageSize,
|
||||
namespaces,
|
||||
});
|
||||
setLogs(result.data);
|
||||
setPagination({
|
||||
|
@ -290,6 +323,20 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
[search, setSearchText]
|
||||
);
|
||||
|
||||
const onShowAllSpacesChange = useCallback(() => {
|
||||
setShowFromAllSpaces((prev) => !prev);
|
||||
const nextShowFromAllSpaces = !showFromAllSpaces;
|
||||
|
||||
if (nextShowFromAllSpaces && !visibleColumns.includes('space_ids')) {
|
||||
const ruleNameIndex = visibleColumns.findIndex((c) => c === 'rule_name');
|
||||
const newVisibleColumns = [...visibleColumns];
|
||||
newVisibleColumns.splice(ruleNameIndex + 1, 0, 'space_ids');
|
||||
setVisibleColumns(newVisibleColumns);
|
||||
} else if (!nextShowFromAllSpaces && visibleColumns.includes('space_ids')) {
|
||||
setVisibleColumns(visibleColumns.filter((c) => c !== 'space_ids'));
|
||||
}
|
||||
}, [setShowFromAllSpaces, showFromAllSpaces, visibleColumns]);
|
||||
|
||||
const renderList = () => {
|
||||
if (!logs) {
|
||||
return <CenterJustifiedSpinner />;
|
||||
|
@ -307,6 +354,7 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
dateFormat={dateFormat}
|
||||
selectedRunLog={selectedRunLog}
|
||||
showRuleNameAndIdColumns={hasRuleNames}
|
||||
showSpaceColumns={showFromAllSpaces}
|
||||
onChangeItemsPerPage={onChangeItemsPerPage}
|
||||
onChangePage={onChangePage}
|
||||
onFlyoutOpen={onFlyoutOpen}
|
||||
|
@ -329,6 +377,7 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
pagination.pageIndex,
|
||||
pagination.pageSize,
|
||||
searchText,
|
||||
showFromAllSpaces,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -350,7 +399,7 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
return (
|
||||
<EuiFlexGroup gutterSize="none" direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFieldSearch
|
||||
fullWidth
|
||||
|
@ -378,6 +427,15 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
updateButtonProps={updateButtonProps}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{hasAllSpaceSwitch && areMultipleSpacesAccessible && (
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
label={ALL_SPACES_LABEL}
|
||||
checked={showFromAllSpaces}
|
||||
onChange={onShowAllSpacesChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
</EuiFlexItem>
|
||||
|
@ -389,6 +447,7 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
outcomeFilter={filter}
|
||||
message={searchText}
|
||||
refreshToken={internalRefreshToken}
|
||||
namespaces={namespaces}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
</EuiFlexItem>
|
||||
|
@ -407,13 +466,29 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
runLog={selectedRunLog}
|
||||
refreshToken={refreshToken}
|
||||
onClose={onFlyoutClose}
|
||||
activeSpaceId={activeSpace?.id}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const RuleEventLogListTableWithApi = withBulkRuleOperations(RuleEventLogListTable);
|
||||
const RuleEventLogListTableWithSpaces: React.FC<RuleEventLogListTableProps> = (props) => {
|
||||
const { spaces } = useKibana().services;
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const SpacesContextWrapper = useCallback(
|
||||
spaces ? spaces.ui.components.getSpacesContextProvider : getEmptyFunctionComponent,
|
||||
[spaces]
|
||||
);
|
||||
return (
|
||||
<SpacesContextWrapper feature="triggersActions">
|
||||
<RuleEventLogListTable {...props} />
|
||||
</SpacesContextWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const RuleEventLogListTableWithApi = withBulkRuleOperations(RuleEventLogListTableWithSpaces);
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { RuleEventLogListTableWithApi as default };
|
||||
|
|
|
@ -31,3 +31,4 @@ export const useCurrentUser = jest.fn();
|
|||
export const withKibana = jest.fn(createWithKibanaMock());
|
||||
export const KibanaContextProvider = jest.fn(createKibanaContextProviderMock());
|
||||
export const useGetUserSavedObjectPermissions = jest.fn();
|
||||
export const useSpacesData = jest.fn();
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
*/
|
||||
|
||||
export * from './kibana_react';
|
||||
export * from './use_spaces_data';
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { SpacesData } from '@kbn/spaces-plugin/public';
|
||||
import { useKibana } from './kibana_react';
|
||||
|
||||
export const useSpacesData = () => {
|
||||
const { spaces } = useKibana().services;
|
||||
const [spacesData, setSpacesData] = useState<SpacesData | undefined>(undefined);
|
||||
const spacesService = spaces?.ui.useSpaces();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const result = await spacesService?.spacesDataPromise;
|
||||
setSpacesData(result);
|
||||
})();
|
||||
}, [spaces, spacesService, setSpacesData]);
|
||||
return spacesData;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue