+ {i18n.translate('xpack.investigateApp.InvestigationsNotFound.title', {
+ defaultMessage: 'Unable to load investigations',
+ })}
+
+ }
+ body={
+
+ {i18n.translate('xpack.investigateApp.InvestigationsNotFound.body', {
+ defaultMessage:
+ 'There was an error loading the investigations. Contact your administrator for help.',
+ })}
+
+ }
+ />
+ );
+}
diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/search_bar/search_bar.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/search_bar/search_bar.tsx
new file mode 100644
index 0000000000000..6c89df8532b71
--- /dev/null
+++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/search_bar/search_bar.tsx
@@ -0,0 +1,52 @@
+/*
+ * 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 { EuiFieldSearch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { StatusFilter } from './status_filter';
+import { TagsFilter } from './tags_filter';
+
+interface Props {
+ isLoading: boolean;
+ onSearch: (value: string) => void;
+ onStatusFilterChange: (status: string[]) => void;
+ onTagsFilterChange: (tags: string[]) => void;
+}
+
+const SEARCH_LABEL = i18n.translate('xpack.investigateApp.investigationList.searchField.label', {
+ defaultMessage: 'Search...',
+});
+
+export function SearchBar({
+ onSearch,
+ onStatusFilterChange,
+ onTagsFilterChange,
+ isLoading,
+}: Props) {
+ return (
+
+
+ onSearch(value)}
+ isLoading={isLoading}
+ />
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/search_bar/status_filter.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/search_bar/status_filter.tsx
new file mode 100644
index 0000000000000..df65845595905
--- /dev/null
+++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/search_bar/status_filter.tsx
@@ -0,0 +1,85 @@
+/*
+ * 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 {
+ useGeneratedHtmlId,
+ EuiFilterButton,
+ EuiFilterGroup,
+ EuiPopover,
+ EuiSelectable,
+ EuiPopoverTitle,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React, { useState } from 'react';
+
+const STATUS_LABEL = i18n.translate('xpack.investigateApp.searchBar.statusFilterButtonLabel', {
+ defaultMessage: 'Status',
+});
+
+interface Props {
+ isLoading: boolean;
+ onChange: (status: string[]) => void;
+}
+
+export function StatusFilter({ isLoading, onChange }: Props) {
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+ const filterStatusPopoverId = useGeneratedHtmlId({
+ prefix: 'filterStatusPopover',
+ });
+
+ const [items, setItems] = useState>([
+ { label: 'triage' },
+ { label: 'active' },
+ { label: 'mitigated' },
+ { label: 'resolved' },
+ { label: 'cancelled' },
+ ]);
+
+ const button = (
+ setIsPopoverOpen(!isPopoverOpen)}
+ isSelected={isPopoverOpen}
+ numFilters={items.length}
+ hasActiveFilters={!!items.find((item) => item.checked === 'on')}
+ numActiveFilters={items.filter((item) => item.checked === 'on').length}
+ >
+ {STATUS_LABEL}
+
+ );
+ return (
+
+ setIsPopoverOpen(false)}
+ panelPaddingSize="none"
+ >
+ {
+ setItems(newOptions);
+ onChange(newOptions.filter((item) => item.checked === 'on').map((item) => item.label));
+ }}
+ isLoading={isLoading}
+ >
+ {(list, search) => (
+
+ {search}
+ {list}
+
+ )}
+
+
+
+ );
+}
diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/search_bar/tags_filter.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/search_bar/tags_filter.tsx
new file mode 100644
index 0000000000000..5a82f84a47fe1
--- /dev/null
+++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/search_bar/tags_filter.tsx
@@ -0,0 +1,86 @@
+/*
+ * 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 {
+ useGeneratedHtmlId,
+ EuiFilterButton,
+ EuiFilterGroup,
+ EuiPopover,
+ EuiSelectable,
+ EuiPopoverTitle,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React, { useEffect, useState } from 'react';
+import { useFetchAllInvestigationTags } from '../../../../hooks/use_fetch_all_investigation_tags';
+
+const TAGS_LABEL = i18n.translate('xpack.investigateApp.searchBar.tagsFilterButtonLabel', {
+ defaultMessage: 'Tags',
+});
+
+interface Props {
+ isLoading: boolean;
+ onChange: (tags: string[]) => void;
+}
+
+export function TagsFilter({ isLoading, onChange }: Props) {
+ const { isLoading: isTagsLoading, data: tags } = useFetchAllInvestigationTags();
+ const [items, setItems] = useState>([]);
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+ const filterTagsPopoverId = useGeneratedHtmlId({
+ prefix: 'filterTagsPopover',
+ });
+
+ useEffect(() => {
+ if (tags) {
+ setItems(tags.map((tag) => ({ label: tag })));
+ }
+ }, [tags]);
+
+ const button = (
+ setIsPopoverOpen(!isPopoverOpen)}
+ isSelected={isPopoverOpen}
+ numFilters={items.length}
+ hasActiveFilters={!!items.find((item) => item.checked === 'on')}
+ numActiveFilters={items.filter((item) => item.checked === 'on').length}
+ >
+ {TAGS_LABEL}
+
+ );
+ return (
+
+ setIsPopoverOpen(false)}
+ panelPaddingSize="none"
+ >
+ {
+ setItems(newOptions);
+ onChange(newOptions.filter((item) => item.checked === 'on').map((item) => item.label));
+ }}
+ isLoading={isLoading || isTagsLoading}
+ >
+ {(list, search) => (
+
+ {search}
+ {list}
+
+ )}
+
+
+
+ );
+}
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts b/x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts
index 1755d283b3763..5d62b745e0a73 100644
--- a/x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts
@@ -13,6 +13,7 @@ import {
deleteInvestigationNoteParamsSchema,
deleteInvestigationParamsSchema,
findInvestigationsParamsSchema,
+ getAllInvestigationTagsParamsSchema,
getInvestigationItemsParamsSchema,
getInvestigationNotesParamsSchema,
getInvestigationParamsSchema,
@@ -27,14 +28,15 @@ import { deleteInvestigation } from '../services/delete_investigation';
import { deleteInvestigationItem } from '../services/delete_investigation_item';
import { deleteInvestigationNote } from '../services/delete_investigation_note';
import { findInvestigations } from '../services/find_investigations';
+import { getAllInvestigationTags } from '../services/get_all_investigation_tags';
import { getInvestigation } from '../services/get_investigation';
+import { getInvestigationItems } from '../services/get_investigation_items';
import { getInvestigationNotes } from '../services/get_investigation_notes';
import { investigationRepositoryFactory } from '../services/investigation_repository';
-import { createInvestigateAppServerRoute } from './create_investigate_app_server_route';
-import { getInvestigationItems } from '../services/get_investigation_items';
-import { updateInvestigationNote } from '../services/update_investigation_note';
-import { updateInvestigationItem } from '../services/update_investigation_item';
import { updateInvestigation } from '../services/update_investigation';
+import { updateInvestigationItem } from '../services/update_investigation_item';
+import { updateInvestigationNote } from '../services/update_investigation_note';
+import { createInvestigateAppServerRoute } from './create_investigate_app_server_route';
const createInvestigationRoute = createInvestigateAppServerRoute({
endpoint: 'POST /api/observability/investigations 2023-10-31',
@@ -138,6 +140,20 @@ const createInvestigationNoteRoute = createInvestigateAppServerRoute({
},
});
+const getAllInvestigationTagsRoute = createInvestigateAppServerRoute({
+ endpoint: 'GET /api/observability/investigations/_tags 2023-10-31',
+ options: {
+ tags: [],
+ },
+ params: getAllInvestigationTagsParamsSchema,
+ handler: async ({ params, context, request, logger }) => {
+ const soClient = (await context.core).savedObjects.client;
+ const repository = investigationRepositoryFactory({ soClient, logger });
+
+ return await getAllInvestigationTags(repository);
+ },
+});
+
const getInvestigationNotesRoute = createInvestigateAppServerRoute({
endpoint: 'GET /api/observability/investigations/{investigationId}/notes 2023-10-31',
options: {
@@ -296,6 +312,7 @@ export function getGlobalInvestigateAppServerRouteRepository() {
...deleteInvestigationItemRoute,
...updateInvestigationItemRoute,
...getInvestigationItemsRoute,
+ ...getAllInvestigationTagsRoute,
};
}
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/saved_objects/investigation.ts b/x-pack/plugins/observability_solution/investigate_app/server/saved_objects/investigation.ts
index eeb937fb16cfa..20ed328689050 100644
--- a/x-pack/plugins/observability_solution/investigate_app/server/saved_objects/investigation.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/server/saved_objects/investigation.ts
@@ -27,6 +27,7 @@ export const investigation: SavedObjectsType = {
},
},
status: { type: 'keyword' },
+ tags: { type: 'keyword' },
},
},
management: {
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation.ts
index 2aed0baed8923..eb8277d7d6f83 100644
--- a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation.ts
@@ -18,9 +18,11 @@ export async function createInvestigation(
throw new Error(`Investigation [id=${params.id}] already exists`);
}
+ const now = Date.now();
const investigation: Investigation = {
...params,
- createdAt: Date.now(),
+ updatedAt: now,
+ createdAt: now,
createdBy: user.username,
status: 'triage',
notes: [],
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_item.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_item.ts
index 1ed6f1289280b..cf77887aab0a3 100644
--- a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_item.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_item.ts
@@ -20,10 +20,12 @@ export async function createInvestigationItem(
): Promise {
const investigation = await repository.findById(investigationId);
+ const now = Date.now();
const investigationItem = {
id: v4(),
createdBy: user.username,
- createdAt: Date.now(),
+ createdAt: now,
+ updatedAt: now,
...params,
};
investigation.items.push(investigationItem);
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_note.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_note.ts
index 9ce727c0f2e08..2f74123b6f269 100644
--- a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_note.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_note.ts
@@ -20,11 +20,13 @@ export async function createInvestigationNote(
): Promise {
const investigation = await repository.findById(investigationId);
+ const now = Date.now();
const investigationNote = {
id: v4(),
content: params.content,
createdBy: user.username,
- createdAt: Date.now(),
+ updatedAt: now,
+ createdAt: now,
};
investigation.notes.push(investigationNote);
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/find_investigations.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/find_investigations.ts
index 7530b3c768610..c3d4606645764 100644
--- a/x-pack/plugins/observability_solution/investigate_app/server/services/find_investigations.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/server/services/find_investigations.ts
@@ -10,14 +10,18 @@ import {
FindInvestigationsResponse,
findInvestigationsResponseSchema,
} from '@kbn/investigation-shared';
-import { InvestigationRepository } from './investigation_repository';
+import { InvestigationRepository, Search } from './investigation_repository';
import { InvestigationStatus } from '../models/investigation';
export async function findInvestigations(
params: FindInvestigationsParams,
repository: InvestigationRepository
): Promise {
- const investigations = await repository.search(toFilter(params), toPagination(params));
+ const investigations = await repository.search({
+ search: toSearch(params),
+ filter: toFilter(params),
+ pagination: toPagination(params),
+ });
return findInvestigationsResponseSchema.parse(investigations);
}
@@ -26,16 +30,28 @@ function toPagination(params: FindInvestigationsParams) {
const DEFAULT_PER_PAGE = 10;
const DEFAULT_PAGE = 1;
return {
- page: params?.page ? parseInt(params.page, 10) : DEFAULT_PAGE,
- perPage: params?.perPage ? parseInt(params.perPage, 10) : DEFAULT_PER_PAGE,
+ page: params?.page && params.page >= 1 ? params.page : DEFAULT_PAGE,
+ perPage:
+ params?.perPage && params.perPage > 0 && params.perPage <= 100
+ ? params.perPage
+ : DEFAULT_PER_PAGE,
};
}
-function toFilter(params: FindInvestigationsParams) {
+function toSearch(params: FindInvestigationsParams): Search | undefined {
+ if (params?.search) {
+ return { search: params.search };
+ }
+}
+
+function toFilter(params: FindInvestigationsParams): string | undefined {
if (params?.alertId) {
const activeStatus: InvestigationStatus = 'active';
const triageStatus: InvestigationStatus = 'triage';
return `investigation.attributes.origin.id:(${params.alertId}) AND (investigation.attributes.status: ${activeStatus} OR investigation.attributes.status: ${triageStatus})`;
}
- return '';
+
+ if (params?.filter) {
+ return params.filter;
+ }
}
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/get_all_investigation_tags.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/get_all_investigation_tags.ts
new file mode 100644
index 0000000000000..48b1624a434d7
--- /dev/null
+++ b/x-pack/plugins/observability_solution/investigate_app/server/services/get_all_investigation_tags.ts
@@ -0,0 +1,19 @@
+/*
+ * 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 {
+ GetAllInvestigationTagsResponse,
+ getAllInvestigationTagsResponseSchema,
+} from '@kbn/investigation-shared';
+import { InvestigationRepository } from './investigation_repository';
+
+export async function getAllInvestigationTags(
+ repository: InvestigationRepository
+): Promise {
+ const tags = await repository.findAllTags();
+ return getAllInvestigationTagsResponseSchema.parse(tags);
+}
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts
index 73c9136cb3673..b7de0b96949da 100644
--- a/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts
@@ -11,11 +11,23 @@ import { Investigation, StoredInvestigation } from '../models/investigation';
import { Paginated, Pagination } from '../models/pagination';
import { SO_INVESTIGATION_TYPE } from '../saved_objects/investigation';
+export interface Search {
+ search: string;
+}
export interface InvestigationRepository {
save(investigation: Investigation): Promise;
findById(id: string): Promise;
deleteById(id: string): Promise;
- search(filter: string, pagination: Pagination): Promise>;
+ search({
+ search,
+ filter,
+ pagination,
+ }: {
+ search?: Search;
+ filter?: string;
+ pagination: Pagination;
+ }): Promise>;
+ findAllTags(): Promise;
}
export function investigationRepositoryFactory({
@@ -89,12 +101,15 @@ export function investigationRepositoryFactory({
await soClient.delete(SO_INVESTIGATION_TYPE, response.saved_objects[0].id);
},
- async search(filter: string, pagination: Pagination): Promise> {
+ async search({ search, filter, pagination }): Promise> {
const response = await soClient.find({
type: SO_INVESTIGATION_TYPE,
page: pagination.page,
perPage: pagination.perPage,
- filter,
+ sortField: 'updated_at',
+ sortOrder: 'desc',
+ ...(filter && { filter }),
+ ...(search && { search: search.search }),
});
return {
@@ -106,5 +121,25 @@ export function investigationRepositoryFactory({
.filter((investigation) => investigation !== undefined) as Investigation[],
};
},
+
+ async findAllTags(): Promise {
+ interface AggsTagsTerms {
+ tags: { buckets: [{ key: string }] };
+ }
+
+ const response = await soClient.find({
+ type: SO_INVESTIGATION_TYPE,
+ aggs: {
+ tags: {
+ terms: {
+ field: 'investigation.attributes.tags',
+ size: 10000,
+ },
+ },
+ },
+ });
+
+ return response.aggregations?.tags?.buckets.map((bucket) => bucket.key) ?? [];
+ },
};
}
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation_note.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation_note.ts
index dda5ae34f2a71..fc4c5a2c0b1fc 100644
--- a/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation_note.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation_note.ts
@@ -27,7 +27,7 @@ export async function updateInvestigationNote(
investigation.notes = investigation.notes.filter((currNote) => {
if (currNote.id === noteId) {
- currNote.content = params.content;
+ currNote = Object.assign(currNote, { content: params.content, updatedAt: Date.now() });
}
return currNote;
From 1199a5ce66ea04c568fb36dfd88aa5a4090a3974 Mon Sep 17 00:00:00 2001
From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Date: Tue, 17 Sep 2024 04:21:26 +1000
Subject: [PATCH 15/26] skip failing test suite (#193061)
---
.../test_suites/common/alerting/summary_actions.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts
index 2726af585e28f..ec63653bef7c7 100644
--- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts
@@ -39,7 +39,8 @@ export default function ({ getService }: FtrProviderContext) {
const alertingApi = getService('alertingApi');
let roleAdmin: RoleCredentials;
- describe('Summary actions', function () {
+ // Failing: See https://github.com/elastic/kibana/issues/193061
+ describe.skip('Summary actions', function () {
const RULE_TYPE_ID = '.es-query';
const ALERT_ACTION_INDEX = 'alert-action-es-query';
const ALERT_INDEX = '.alerts-stack.alerts-default';
From 8c8696d537b3661c72d98f9ed50d2922d77d5142 Mon Sep 17 00:00:00 2001
From: Nicolas Chaulet
Date: Mon, 16 Sep 2024 16:14:29 -0400
Subject: [PATCH 16/26] [Fleet] Fix bulk GET agent policies permissions
(#193070)
---
.../server/routes/agent_policy/handlers.ts | 5 ++
.../fleet/server/routes/agent_policy/index.ts | 5 +-
.../apis/agent_policy/privileges.ts | 62 +++++++++++++++++++
3 files changed, 70 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts
index c24b36c382dc0..a86c627688207 100644
--- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts
+++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts
@@ -191,6 +191,11 @@ export const bulkGetAgentPoliciesHandler: FleetRequestHandler<
const fleetContext = await context.fleet;
const soClient = fleetContext.internalSoClient;
const { full: withPackagePolicies = false, ignoreMissing = false, ids } = request.body;
+ if (!fleetContext.authz.fleet.readAgentPolicies && withPackagePolicies) {
+ throw new FleetUnauthorizedError(
+ 'full query parameter require agent policies read permissions'
+ );
+ }
let items = await agentPolicyService.getByIDs(soClient, ids, {
withPackagePolicies,
ignoreMissing,
diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/index.ts b/x-pack/plugins/fleet/server/routes/agent_policy/index.ts
index 66e84cf4a76fe..0ff76addd1b16 100644
--- a/x-pack/plugins/fleet/server/routes/agent_policy/index.ts
+++ b/x-pack/plugins/fleet/server/routes/agent_policy/index.ts
@@ -61,8 +61,9 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
router.versioned
.post({
path: AGENT_POLICY_API_ROUTES.BULK_GET_PATTERN,
- fleetAuthz: {
- fleet: { readAgentPolicies: true },
+ fleetAuthz: (authz) => {
+ // Allow to retrieve agent policies metadata (no full) for user with only read agents permissions
+ return authz.fleet.readAgentPolicies || authz.fleet.readAgents;
},
})
.addVersion(
diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/privileges.ts b/x-pack/test/fleet_api_integration/apis/agent_policy/privileges.ts
index d4e9aab9cdbd2..d22fa9380769d 100644
--- a/x-pack/test/fleet_api_integration/apis/agent_policy/privileges.ts
+++ b/x-pack/test/fleet_api_integration/apis/agent_policy/privileges.ts
@@ -49,6 +49,46 @@ const READ_SCENARIOS = [
},
];
+const READ_SCENARIOS_FULL_POLICIES = [
+ {
+ user: testUsers.fleet_all_only,
+ statusCode: 200,
+ },
+ {
+ user: testUsers.fleet_read_only,
+ statusCode: 200,
+ },
+ {
+ user: testUsers.fleet_agent_policies_read_only,
+ statusCode: 200,
+ },
+ {
+ user: testUsers.fleet_agent_policies_all_only,
+ statusCode: 200,
+ },
+ {
+ // Expect minimal access
+ user: testUsers.fleet_agents_read_only,
+ statusCode: 403,
+ },
+ {
+ user: testUsers.fleet_no_access,
+ statusCode: 403,
+ },
+ {
+ user: testUsers.fleet_minimal_all_only,
+ statusCode: 403,
+ },
+ {
+ user: testUsers.fleet_minimal_read_only,
+ statusCode: 403,
+ },
+ {
+ user: testUsers.fleet_settings_read_only,
+ statusCode: 403,
+ },
+];
+
const ALL_SCENARIOS = [
{
user: testUsers.fleet_all_only,
@@ -101,11 +141,33 @@ export default function (providerContext: FtrProviderContext) {
path: '/api/fleet/agent_policies',
scenarios: READ_SCENARIOS,
},
+ {
+ method: 'GET',
+ path: '/api/fleet/agent_policies?full=true',
+ scenarios: READ_SCENARIOS_FULL_POLICIES,
+ },
{
method: 'GET',
path: '/api/fleet/agent_policies/policy-test-privileges-1',
scenarios: READ_SCENARIOS,
},
+ {
+ method: 'POST',
+ path: '/api/fleet/agent_policies/_bulk_get',
+ scenarios: READ_SCENARIOS,
+ send: {
+ ids: ['policy-test-privileges-1'],
+ },
+ },
+ {
+ method: 'POST',
+ path: '/api/fleet/agent_policies/_bulk_get',
+ scenarios: READ_SCENARIOS_FULL_POLICIES,
+ send: {
+ ids: ['policy-test-privileges-1'],
+ full: true,
+ },
+ },
{
method: 'POST',
path: '/api/fleet/agent_policies',
From bcab410c5006e49a37f5eddd5230a6b74593c2d7 Mon Sep 17 00:00:00 2001
From: Bena Kansara <69037875+benakansara@users.noreply.github.com>
Date: Mon, 16 Sep 2024 22:26:52 +0200
Subject: [PATCH 17/26] [RCA] Remove x-axis and labels from library
visualizations (#192994)
Removes y-axis label, x-axis and adjusts height of the component for
library visualizations
---
.../register_embeddable_item.tsx | 7 ++++++-
.../items/esql_item/register_esql_item.tsx | 12 ++++++++++--
.../items/lens_item/register_lens_item.tsx | 17 +++++------------
.../utils/get_lens_attrs_for_suggestion.ts | 13 -------------
4 files changed, 21 insertions(+), 28 deletions(-)
diff --git a/x-pack/plugins/observability_solution/investigate_app/public/items/embeddable_item/register_embeddable_item.tsx b/x-pack/plugins/observability_solution/investigate_app/public/items/embeddable_item/register_embeddable_item.tsx
index 3be16c83c8018..8ebf3829b073d 100644
--- a/x-pack/plugins/observability_solution/investigate_app/public/items/embeddable_item/register_embeddable_item.tsx
+++ b/x-pack/plugins/observability_solution/investigate_app/public/items/embeddable_item/register_embeddable_item.tsx
@@ -89,6 +89,11 @@ function LegacyEmbeddable({ type, config, timeRange: { from, to }, savedObjectId
from,
to,
},
+ overrides: {
+ axisX: { hide: true },
+ axisLeft: { style: { axisTitle: { visible: false } } },
+ settings: { showLegend: false },
+ },
};
if (savedObjectId) {
@@ -188,7 +193,7 @@ export function registerEmbeddableItem({
grow={true}
className={css`
> div {
- height: 196px;
+ height: 128px;
}
`}
>
diff --git a/x-pack/plugins/observability_solution/investigate_app/public/items/esql_item/register_esql_item.tsx b/x-pack/plugins/observability_solution/investigate_app/public/items/esql_item/register_esql_item.tsx
index 7e64db5557fc2..54d3698a5148b 100644
--- a/x-pack/plugins/observability_solution/investigate_app/public/items/esql_item/register_esql_item.tsx
+++ b/x-pack/plugins/observability_solution/investigate_app/public/items/esql_item/register_esql_item.tsx
@@ -132,7 +132,11 @@ export function EsqlWidget({ suggestion, dataView, esqlQuery, dateHistogramResul
);
} else {
@@ -147,7 +151,11 @@ export function EsqlWidget({ suggestion, dataView, esqlQuery, dateHistogramResul
);
diff --git a/x-pack/plugins/observability_solution/investigate_app/public/items/lens_item/register_lens_item.tsx b/x-pack/plugins/observability_solution/investigate_app/public/items/lens_item/register_lens_item.tsx
index 3f2b1d9f9c1bf..2896719a49e20 100644
--- a/x-pack/plugins/observability_solution/investigate_app/public/items/lens_item/register_lens_item.tsx
+++ b/x-pack/plugins/observability_solution/investigate_app/public/items/lens_item/register_lens_item.tsx
@@ -178,17 +178,6 @@ export function LensWidget({
const attributesLens = new LensAttributesBuilder({
visualization: new XYChart({
- visualOptions: {
- axisTitlesVisibilitySettings: {
- x: false,
- yLeft: false,
- yRight: false,
- },
- legend: {
- isVisible: false,
- position: 'right',
- },
- },
layers,
formulaAPI: formulaAsync.value.formula,
dataView,
@@ -227,7 +216,11 @@ export function LensWidget({
query={(searchConfiguration?.query as Query) || defaultQuery}
disableTriggers={true}
filters={filters}
- overrides={{ axisX: { hide: true } }}
+ overrides={{
+ axisX: { hide: true },
+ axisLeft: { style: { axisTitle: { visible: false } } },
+ settings: { showLegend: false },
+ }}
/>
);
diff --git a/x-pack/plugins/observability_solution/investigate_app/public/utils/get_lens_attrs_for_suggestion.ts b/x-pack/plugins/observability_solution/investigate_app/public/utils/get_lens_attrs_for_suggestion.ts
index 79693ff2941aa..0483d771954c0 100644
--- a/x-pack/plugins/observability_solution/investigate_app/public/utils/get_lens_attrs_for_suggestion.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/public/utils/get_lens_attrs_for_suggestion.ts
@@ -32,19 +32,6 @@ export function getLensAttrsForSuggestion({
dataView,
}) as TypedLensByValueInput['attributes'];
- attrs.state.visualization = {
- ...(attrs.state.visualization as any),
- axisTitlesVisibilitySettings: {
- x: false,
- yLeft: false,
- yRight: false,
- },
- legend: {
- isVisible: false,
- position: 'right',
- },
- };
-
const lensEmbeddableInput: TypedLensByValueInput = {
attributes: attrs,
id: v4(),
From b3baadd14ae0cc07e22d878bdd24861309a26426 Mon Sep 17 00:00:00 2001
From: Maxim Palenov
Date: Mon, 16 Sep 2024 23:57:22 +0300
Subject: [PATCH 18/26] [Security Solution] Unskip
`install_large_prebuilt_rules_package` (#192563)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
**Addresses:** https://github.com/elastic/kibana/issues/192479
## Summary
This PR unskips `install_large_prebuilt_rules_package` FTR test.
## Details
The flakiness is caused by chunked assets installation inside the Fleet plugin. Used no index refresh strategy leads to reading stale data in `PUT /api/detection_engine/rules/prepackaged` endpoint handler and returning arbitrary data.
The problem was fixed by adding `refresh=wait_for` for the last assets chunk installation.
## Flaky test runner
- 🟢 (100 runs) https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6938
---
.../services/epm/kibana/assets/install.ts | 95 +++++++++++++------
.../install_large_prebuilt_rules_package.ts | 21 ++--
2 files changed, 78 insertions(+), 38 deletions(-)
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts
index 8f4397883eb05..276478099daf8 100644
--- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts
+++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts
@@ -146,35 +146,12 @@ export async function installKibanaAssets(options: {
await makeManagedIndexPatternsGlobal(savedObjectsClient);
- let installedAssets: SavedObjectsImportSuccess[] = [];
-
- if (
- assetsToInstall.length > MAX_ASSETS_TO_INSTALL_IN_PARALLEL &&
- !hasReferences(assetsToInstall)
- ) {
- // If the package size is too large, we need to install in chunks to avoid
- // memory issues as the SO import creates a lot of objects in memory
-
- // NOTE: if there are references, we can't chunk the install because
- // referenced objects might end up in different chunks leading to import
- // errors.
- for (const assetChunk of chunk(assetsToInstall, MAX_ASSETS_TO_INSTALL_IN_PARALLEL)) {
- const result = await installKibanaSavedObjects({
- logger,
- savedObjectsImporter,
- kibanaAssets: assetChunk,
- });
- installedAssets = installedAssets.concat(result);
- }
- } else {
- installedAssets = await installKibanaSavedObjects({
- logger,
- savedObjectsImporter,
- kibanaAssets: assetsToInstall,
- });
- }
-
- return installedAssets;
+ return await installKibanaSavedObjects({
+ logger,
+ savedObjectsImporter,
+ kibanaAssets: assetsToInstall,
+ assetsChunkSize: MAX_ASSETS_TO_INSTALL_IN_PARALLEL,
+ });
}
export async function installKibanaAssetsAndReferencesMultispace({
@@ -411,13 +388,71 @@ async function retryImportOnConflictError(
// only exported for testing
export async function installKibanaSavedObjects({
+ savedObjectsImporter,
+ kibanaAssets,
+ assetsChunkSize,
+ logger,
+}: {
+ kibanaAssets: ArchiveAsset[];
+ savedObjectsImporter: SavedObjectsImporterContract;
+ logger: Logger;
+ assetsChunkSize?: number;
+}): Promise {
+ if (!assetsChunkSize || kibanaAssets.length <= assetsChunkSize || hasReferences(kibanaAssets)) {
+ return await installKibanaSavedObjectsChunk({
+ logger,
+ savedObjectsImporter,
+ kibanaAssets,
+ refresh: 'wait_for',
+ });
+ }
+
+ const installedAssets: SavedObjectsImportSuccess[] = [];
+
+ // If the package size is too large, we need to install in chunks to avoid
+ // memory issues as the SO import creates a lot of objects in memory
+
+ // NOTE: if there are references, we can't chunk the install because
+ // referenced objects might end up in different chunks leading to import
+ // errors.
+ const assetChunks = chunk(kibanaAssets, assetsChunkSize);
+ const allAssetChunksButLast = assetChunks.slice(0, -1);
+ const lastAssetChunk = assetChunks.slice(-1)[0];
+
+ for (const assetChunk of allAssetChunksButLast) {
+ const result = await installKibanaSavedObjectsChunk({
+ logger,
+ savedObjectsImporter,
+ kibanaAssets: assetChunk,
+ refresh: false,
+ });
+
+ installedAssets.push(...result);
+ }
+
+ const result = await installKibanaSavedObjectsChunk({
+ logger,
+ savedObjectsImporter,
+ kibanaAssets: lastAssetChunk,
+ refresh: 'wait_for',
+ });
+
+ installedAssets.push(...result);
+
+ return installedAssets;
+}
+
+// only exported for testing
+async function installKibanaSavedObjectsChunk({
savedObjectsImporter,
kibanaAssets,
logger,
+ refresh,
}: {
kibanaAssets: ArchiveAsset[];
savedObjectsImporter: SavedObjectsImporterContract;
logger: Logger;
+ refresh?: boolean | 'wait_for';
}) {
if (!kibanaAssets.length) {
return [];
@@ -437,8 +472,8 @@ export async function installKibanaSavedObjects({
overwrite: true,
readStream,
createNewCopies: false,
- refresh: false,
managed: true,
+ refresh,
});
});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/large_prebuilt_rules_package/trial_license_complete_tier/install_large_prebuilt_rules_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/large_prebuilt_rules_package/trial_license_complete_tier/install_large_prebuilt_rules_package.ts
index 10a5adaf83346..29ca3eea30239 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/large_prebuilt_rules_package/trial_license_complete_tier/install_large_prebuilt_rules_package.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/large_prebuilt_rules_package/trial_license_complete_tier/install_large_prebuilt_rules_package.ts
@@ -18,8 +18,7 @@ export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const log = getService('log');
- // Failing: See https://github.com/elastic/kibana/issues/192479
- describe.skip('@ess @serverless @skipInServerlessMKI install_large_prebuilt_rules_package', () => {
+ describe('@ess @serverless @skipInServerlessMKI install_large_prebuilt_rules_package', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllPrebuiltRuleAssets(es, log);
@@ -36,9 +35,12 @@ export default ({ getService }: FtrProviderContext): void => {
es,
supertest
);
- expect(statusBeforePackageInstallation.rules_installed).toBe(0);
- expect(statusBeforePackageInstallation.rules_not_installed).toBe(0);
- expect(statusBeforePackageInstallation.rules_not_updated).toBe(0);
+
+ expect(statusBeforePackageInstallation).toMatchObject({
+ rules_installed: 0,
+ rules_not_installed: 0,
+ rules_not_updated: 0,
+ });
// Install the package with 15000 prebuilt historical version of rules rules and 750 unique rules
await installPrebuiltRulesAndTimelines(es, supertest);
@@ -48,9 +50,12 @@ export default ({ getService }: FtrProviderContext): void => {
es,
supertest
);
- expect(statusAfterPackageInstallation.rules_installed).toBe(750);
- expect(statusAfterPackageInstallation.rules_not_installed).toBe(0);
- expect(statusAfterPackageInstallation.rules_not_updated).toBe(0);
+
+ expect(statusAfterPackageInstallation).toMatchObject({
+ rules_installed: 750,
+ rules_not_installed: 0,
+ rules_not_updated: 0,
+ });
});
});
};
From bb1898ec8f56fd77cfe0858cce243d714e788194 Mon Sep 17 00:00:00 2001
From: Kevin Delemme
Date: Mon, 16 Sep 2024 17:22:03 -0400
Subject: [PATCH 19/26] feat(rca): display investigations stats (#193069)
---
.../rest_specs/get_all_investigation_stats.ts | 25 ++++++
.../src/rest_specs/index.ts | 2 +
.../src/schema/index.ts | 2 +
.../src/schema/investigation.ts | 3 +
.../public/hooks/query_key_factory.ts | 1 +
.../use_fetch_all_investigation_stats.ts | 73 ++++++++++++++++
.../list/components/investigation_list.tsx | 17 ++--
.../list/components/investigation_stats.tsx | 83 +++++++++++++++++++
...investigate_app_server_route_repository.ts | 17 ++++
.../services/get_all_investigation_stats.ts | 19 +++++
.../services/investigation_repository.ts | 42 ++++++++++
.../server/services/update_investigation.ts | 10 ++-
12 files changed, 286 insertions(+), 8 deletions(-)
create mode 100644 packages/kbn-investigation-shared/src/rest_specs/get_all_investigation_stats.ts
create mode 100644 x-pack/plugins/observability_solution/investigate_app/public/hooks/use_fetch_all_investigation_stats.ts
create mode 100644 x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/investigation_stats.tsx
create mode 100644 x-pack/plugins/observability_solution/investigate_app/server/services/get_all_investigation_stats.ts
diff --git a/packages/kbn-investigation-shared/src/rest_specs/get_all_investigation_stats.ts b/packages/kbn-investigation-shared/src/rest_specs/get_all_investigation_stats.ts
new file mode 100644
index 0000000000000..bee9f15db587d
--- /dev/null
+++ b/packages/kbn-investigation-shared/src/rest_specs/get_all_investigation_stats.ts
@@ -0,0 +1,25 @@
+/*
+ * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+import { z } from '@kbn/zod';
+import { statusSchema } from '../schema';
+
+const getAllInvestigationStatsParamsSchema = z.object({
+ query: z.object({}),
+});
+
+const getAllInvestigationStatsResponseSchema = z.object({
+ count: z.record(statusSchema, z.number()),
+ total: z.number(),
+});
+
+type GetAllInvestigationStatsResponse = z.output;
+
+export { getAllInvestigationStatsParamsSchema, getAllInvestigationStatsResponseSchema };
+export type { GetAllInvestigationStatsResponse };
diff --git a/packages/kbn-investigation-shared/src/rest_specs/index.ts b/packages/kbn-investigation-shared/src/rest_specs/index.ts
index 9b81aca896f55..c00ec5035765e 100644
--- a/packages/kbn-investigation-shared/src/rest_specs/index.ts
+++ b/packages/kbn-investigation-shared/src/rest_specs/index.ts
@@ -17,6 +17,7 @@ export type * from './find';
export type * from './get';
export type * from './get_items';
export type * from './get_notes';
+export type * from './get_all_investigation_stats';
export type * from './get_all_investigation_tags';
export type * from './investigation';
export type * from './investigation_item';
@@ -35,6 +36,7 @@ export * from './find';
export * from './get';
export * from './get_items';
export * from './get_notes';
+export * from './get_all_investigation_stats';
export * from './get_all_investigation_tags';
export * from './investigation';
export * from './investigation_item';
diff --git a/packages/kbn-investigation-shared/src/schema/index.ts b/packages/kbn-investigation-shared/src/schema/index.ts
index f48b6a40416d0..7491ecce76cc2 100644
--- a/packages/kbn-investigation-shared/src/schema/index.ts
+++ b/packages/kbn-investigation-shared/src/schema/index.ts
@@ -11,3 +11,5 @@ export * from './investigation';
export * from './investigation_item';
export * from './investigation_note';
export * from './origin';
+
+export type * from './investigation';
diff --git a/packages/kbn-investigation-shared/src/schema/investigation.ts b/packages/kbn-investigation-shared/src/schema/investigation.ts
index 47d198665657d..9be39b5b2a7b3 100644
--- a/packages/kbn-investigation-shared/src/schema/investigation.ts
+++ b/packages/kbn-investigation-shared/src/schema/investigation.ts
@@ -36,4 +36,7 @@ const investigationSchema = z.object({
items: z.array(investigationItemSchema),
});
+type Status = z.infer;
+
+export type { Status };
export { investigationSchema, statusSchema };
diff --git a/x-pack/plugins/observability_solution/investigate_app/public/hooks/query_key_factory.ts b/x-pack/plugins/observability_solution/investigate_app/public/hooks/query_key_factory.ts
index 454c77ddf56e0..85a8c35b63a5e 100644
--- a/x-pack/plugins/observability_solution/investigate_app/public/hooks/query_key_factory.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/public/hooks/query_key_factory.ts
@@ -10,6 +10,7 @@
export const investigationKeys = {
all: ['investigations'] as const,
tags: () => [...investigationKeys.all, 'tags'] as const,
+ stats: () => [...investigationKeys.all, 'stats'] as const,
lists: () => [...investigationKeys.all, 'list'] as const,
list: (params: { page: number; perPage: number; search?: string; filter?: string }) =>
[...investigationKeys.lists(), params] as const,
diff --git a/x-pack/plugins/observability_solution/investigate_app/public/hooks/use_fetch_all_investigation_stats.ts b/x-pack/plugins/observability_solution/investigate_app/public/hooks/use_fetch_all_investigation_stats.ts
new file mode 100644
index 0000000000000..2b2c8b92b0d4f
--- /dev/null
+++ b/x-pack/plugins/observability_solution/investigate_app/public/hooks/use_fetch_all_investigation_stats.ts
@@ -0,0 +1,73 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import type { GetAllInvestigationStatsResponse, Status } from '@kbn/investigation-shared';
+import { useQuery } from '@tanstack/react-query';
+import { investigationKeys } from './query_key_factory';
+import { useKibana } from './use_kibana';
+
+export interface Response {
+ isInitialLoading: boolean;
+ isLoading: boolean;
+ isRefetching: boolean;
+ isSuccess: boolean;
+ isError: boolean;
+ data: { count: Record; total: number } | undefined;
+}
+
+export function useFetchAllInvestigationStats(): Response {
+ const {
+ core: {
+ http,
+ notifications: { toasts },
+ },
+ } = useKibana();
+
+ const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({
+ queryKey: investigationKeys.stats(),
+ queryFn: async ({ signal }) => {
+ const response = await http.get(
+ `/api/observability/investigations/_stats`,
+ {
+ version: '2023-10-31',
+ signal,
+ }
+ );
+
+ return {
+ count: {
+ triage: response.count.triage ?? 0,
+ active: response.count.active ?? 0,
+ mitigated: response.count.mitigated ?? 0,
+ resolved: response.count.resolved ?? 0,
+ cancelled: response.count.cancelled ?? 0,
+ },
+ total: response.total ?? 0,
+ };
+ },
+ retry: false,
+ cacheTime: 600 * 1000, // 10 minutes
+ staleTime: 0,
+ onError: (error: Error) => {
+ toasts.addError(error, {
+ title: i18n.translate('xpack.investigateApp.useFetchAllInvestigationStats.errorTitle', {
+ defaultMessage: 'Something went wrong while fetching the investigation stats',
+ }),
+ });
+ },
+ });
+
+ return {
+ data,
+ isInitialLoading,
+ isLoading,
+ isRefetching,
+ isSuccess,
+ isError,
+ };
+}
diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/investigation_list.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/investigation_list.tsx
index 8e1bc793b545e..d75710f817703 100644
--- a/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/investigation_list.tsx
+++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/investigation_list.tsx
@@ -10,6 +10,7 @@ import {
EuiBasicTable,
EuiBasicTableColumn,
EuiFlexGroup,
+ EuiFlexItem,
EuiLink,
EuiLoadingSpinner,
EuiText,
@@ -23,6 +24,7 @@ import { InvestigationStatusBadge } from '../../../components/investigation_stat
import { useFetchInvestigationList } from '../../../hooks/use_fetch_investigation_list';
import { useKibana } from '../../../hooks/use_kibana';
import { InvestigationListActions } from './investigation_list_actions';
+import { InvestigationStats } from './investigation_stats';
import { InvestigationsError } from './investigations_error';
import { SearchBar } from './search_bar/search_bar';
@@ -82,11 +84,15 @@ export function InvestigationList() {
defaultMessage: 'Tags',
}),
render: (value: InvestigationResponse['tags']) => {
- return value.map((tag) => (
-
- {tag}
-
- ));
+ return (
+
+ {value.map((tag) => (
+
+ {tag}
+
+ ))}
+
+ );
},
},
{
@@ -153,6 +159,7 @@ export function InvestigationList() {
return (
+
setSearch(value)}
diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/investigation_stats.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/investigation_stats.tsx
new file mode 100644
index 0000000000000..7f654dce415c9
--- /dev/null
+++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/investigation_stats.tsx
@@ -0,0 +1,83 @@
+/*
+ * 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 numeral from '@elastic/numeral';
+import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { useFetchAllInvestigationStats } from '../../../hooks/use_fetch_all_investigation_stats';
+import { useKibana } from '../../../hooks/use_kibana';
+
+export function InvestigationStats() {
+ const {
+ core: { uiSettings },
+ } = useKibana();
+ const { data, isLoading: isStatsLoading } = useFetchAllInvestigationStats();
+ const numberFormat = uiSettings.get('format:number:defaultPattern');
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts b/x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts
index 5d62b745e0a73..b0ecd89275914 100644
--- a/x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts
@@ -13,6 +13,7 @@ import {
deleteInvestigationNoteParamsSchema,
deleteInvestigationParamsSchema,
findInvestigationsParamsSchema,
+ getAllInvestigationStatsParamsSchema,
getAllInvestigationTagsParamsSchema,
getInvestigationItemsParamsSchema,
getInvestigationNotesParamsSchema,
@@ -37,6 +38,7 @@ import { updateInvestigation } from '../services/update_investigation';
import { updateInvestigationItem } from '../services/update_investigation_item';
import { updateInvestigationNote } from '../services/update_investigation_note';
import { createInvestigateAppServerRoute } from './create_investigate_app_server_route';
+import { getAllInvestigationStats } from '../services/get_all_investigation_stats';
const createInvestigationRoute = createInvestigateAppServerRoute({
endpoint: 'POST /api/observability/investigations 2023-10-31',
@@ -154,6 +156,20 @@ const getAllInvestigationTagsRoute = createInvestigateAppServerRoute({
},
});
+const getAllInvestigationStatsRoute = createInvestigateAppServerRoute({
+ endpoint: 'GET /api/observability/investigations/_stats 2023-10-31',
+ options: {
+ tags: [],
+ },
+ params: getAllInvestigationStatsParamsSchema,
+ handler: async ({ params, context, request, logger }) => {
+ const soClient = (await context.core).savedObjects.client;
+ const repository = investigationRepositoryFactory({ soClient, logger });
+
+ return await getAllInvestigationStats(repository);
+ },
+});
+
const getInvestigationNotesRoute = createInvestigateAppServerRoute({
endpoint: 'GET /api/observability/investigations/{investigationId}/notes 2023-10-31',
options: {
@@ -312,6 +328,7 @@ export function getGlobalInvestigateAppServerRouteRepository() {
...deleteInvestigationItemRoute,
...updateInvestigationItemRoute,
...getInvestigationItemsRoute,
+ ...getAllInvestigationStatsRoute,
...getAllInvestigationTagsRoute,
};
}
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/get_all_investigation_stats.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/get_all_investigation_stats.ts
new file mode 100644
index 0000000000000..eb2304b4950c5
--- /dev/null
+++ b/x-pack/plugins/observability_solution/investigate_app/server/services/get_all_investigation_stats.ts
@@ -0,0 +1,19 @@
+/*
+ * 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 {
+ GetAllInvestigationStatsResponse,
+ getAllInvestigationStatsResponseSchema,
+} from '@kbn/investigation-shared';
+import { InvestigationRepository } from './investigation_repository';
+
+export async function getAllInvestigationStats(
+ repository: InvestigationRepository
+): Promise {
+ const stats = await repository.getStats();
+ return getAllInvestigationStatsResponseSchema.parse(stats);
+}
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts
index b7de0b96949da..ffefe757c7c72 100644
--- a/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts
@@ -6,6 +6,7 @@
*/
import { Logger, SavedObjectsClientContract } from '@kbn/core/server';
+import type { Status } from '@kbn/investigation-shared';
import { investigationSchema } from '@kbn/investigation-shared';
import { Investigation, StoredInvestigation } from '../models/investigation';
import { Paginated, Pagination } from '../models/pagination';
@@ -14,6 +15,11 @@ import { SO_INVESTIGATION_TYPE } from '../saved_objects/investigation';
export interface Search {
search: string;
}
+interface Stats {
+ count: Record;
+ total: number;
+}
+
export interface InvestigationRepository {
save(investigation: Investigation): Promise;
findById(id: string): Promise;
@@ -28,6 +34,7 @@ export interface InvestigationRepository {
pagination: Pagination;
}): Promise>;
findAllTags(): Promise;
+ getStats(): Promise;
}
export function investigationRepositoryFactory({
@@ -141,5 +148,40 @@ export function investigationRepositoryFactory({
return response.aggregations?.tags?.buckets.map((bucket) => bucket.key) ?? [];
},
+
+ async getStats(): Promise<{ count: Record; total: number }> {
+ interface AggsStatusTerms {
+ status: { buckets: [{ key: string; doc_count: number }] };
+ }
+
+ const response = await soClient.find({
+ type: SO_INVESTIGATION_TYPE,
+ aggs: {
+ status: {
+ terms: {
+ field: 'investigation.attributes.status',
+ size: 10,
+ },
+ },
+ },
+ });
+
+ const countByStatus: Record = {
+ active: 0,
+ triage: 0,
+ mitigated: 0,
+ resolved: 0,
+ cancelled: 0,
+ };
+
+ return {
+ count:
+ response.aggregations?.status?.buckets.reduce(
+ (acc, bucket) => ({ ...acc, [bucket.key]: bucket.doc_count }),
+ countByStatus
+ ) ?? countByStatus,
+ total: response.total,
+ };
+ },
};
}
diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation.ts
index b4e33c4a5f673..ee1289ec4b9fa 100644
--- a/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation.ts
+++ b/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation.ts
@@ -7,7 +7,7 @@
import type { AuthenticatedUser } from '@kbn/core-security-common';
import { UpdateInvestigationParams, UpdateInvestigationResponse } from '@kbn/investigation-shared';
-import { isEqual } from 'lodash';
+import { isEqual, omit } from 'lodash';
import { InvestigationRepository } from './investigation_repository';
import { Investigation } from '../models/investigation';
@@ -18,9 +18,13 @@ export async function updateInvestigation(
): Promise {
const originalInvestigation = await repository.findById(investigationId);
- const updatedInvestigation: Investigation = Object.assign({}, originalInvestigation, params);
+ const updatedInvestigation: Investigation = Object.assign({}, originalInvestigation, params, {
+ updatedAt: Date.now(),
+ });
- if (isEqual(originalInvestigation, updatedInvestigation)) {
+ if (
+ isEqual(omit(originalInvestigation, ['updatedAt']), omit(updatedInvestigation, ['updatedAt']))
+ ) {
return originalInvestigation;
}
From d776e390b013b57badafa7951c19f279cdf0f59d Mon Sep 17 00:00:00 2001
From: Philippe Oberti
Date: Mon, 16 Sep 2024 23:37:36 +0200
Subject: [PATCH 20/26] [kbn-expandable-flyout] - refactor internal redux store
(#192644)
---
packages/kbn-expandable-flyout/index.ts | 2 +-
.../src/components/preview_section.test.tsx | 24 ++-
.../src/hooks/use_expandable_flyout_api.ts | 4 +-
.../src/hooks/use_expandable_flyout_state.ts | 2 +-
.../src/index.stories.tsx | 140 ++++++++------
.../kbn-expandable-flyout/src/index.test.tsx | 82 ++++----
.../src/provider.test.tsx | 38 ++--
.../kbn-expandable-flyout/src/provider.tsx | 10 +-
.../src/{ => store}/actions.ts | 2 +-
.../reducers.test.ts} | 182 ++++++++++--------
.../src/{reducer.ts => store/reducers.ts} | 6 +-
.../src/{ => store}/redux.ts | 13 +-
.../src/{ => store}/state.ts | 21 +-
.../src/test/provider.tsx | 10 +-
14 files changed, 308 insertions(+), 228 deletions(-)
rename packages/kbn-expandable-flyout/src/{ => store}/actions.ts (98%)
rename packages/kbn-expandable-flyout/src/{reducer.test.ts => store/reducers.test.ts} (78%)
rename packages/kbn-expandable-flyout/src/{reducer.ts => store/reducers.ts} (96%)
rename packages/kbn-expandable-flyout/src/{ => store}/redux.ts (77%)
rename packages/kbn-expandable-flyout/src/{ => store}/state.ts (79%)
diff --git a/packages/kbn-expandable-flyout/index.ts b/packages/kbn-expandable-flyout/index.ts
index d2d304bd6aa2c..f06be45a68914 100644
--- a/packages/kbn-expandable-flyout/index.ts
+++ b/packages/kbn-expandable-flyout/index.ts
@@ -12,7 +12,7 @@ export { ExpandableFlyout } from './src';
export { useExpandableFlyoutApi } from './src/hooks/use_expandable_flyout_api';
export { useExpandableFlyoutState } from './src/hooks/use_expandable_flyout_state';
-export { type FlyoutState as ExpandableFlyoutState } from './src/state';
+export { type FlyoutPanels as ExpandableFlyoutState } from './src/store/state';
export { ExpandableFlyoutProvider } from './src/provider';
export { withExpandableFlyoutProvider } from './src/with_provider';
diff --git a/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx b/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx
index 831e916f84f05..ba2b8987cc0a8 100644
--- a/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx
+++ b/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx
@@ -16,18 +16,24 @@ import {
PREVIEW_SECTION_TEST_ID,
} from './test_ids';
import { TestProvider } from '../test/provider';
-import { State } from '../state';
+import { State } from '../store/state';
describe('PreviewSection', () => {
- const context = {
- right: {},
- left: {},
- preview: [
- {
- id: 'key',
+ const context: State = {
+ panels: {
+ byId: {
+ flyout: {
+ right: undefined,
+ left: undefined,
+ preview: [
+ {
+ id: 'key',
+ },
+ ],
+ },
},
- ],
- } as unknown as State;
+ },
+ };
const component = {'component'}
;
const left = 500;
diff --git a/packages/kbn-expandable-flyout/src/hooks/use_expandable_flyout_api.ts b/packages/kbn-expandable-flyout/src/hooks/use_expandable_flyout_api.ts
index 5ae8a4e474887..e1fe482f448f9 100644
--- a/packages/kbn-expandable-flyout/src/hooks/use_expandable_flyout_api.ts
+++ b/packages/kbn-expandable-flyout/src/hooks/use_expandable_flyout_api.ts
@@ -21,8 +21,8 @@ import {
openPreviewPanelAction,
openRightPanelAction,
previousPreviewPanelAction,
-} from '../actions';
-import { useDispatch } from '../redux';
+} from '../store/actions';
+import { useDispatch } from '../store/redux';
import { FlyoutPanelProps, type ExpandableFlyoutApi } from '../types';
export type { ExpandableFlyoutApi };
diff --git a/packages/kbn-expandable-flyout/src/hooks/use_expandable_flyout_state.ts b/packages/kbn-expandable-flyout/src/hooks/use_expandable_flyout_state.ts
index f4fbb0f1f2a3f..49cac7d97a895 100644
--- a/packages/kbn-expandable-flyout/src/hooks/use_expandable_flyout_state.ts
+++ b/packages/kbn-expandable-flyout/src/hooks/use_expandable_flyout_state.ts
@@ -9,7 +9,7 @@
import { REDUX_ID_FOR_MEMORY_STORAGE } from '../constants';
import { useExpandableFlyoutContext } from '../context';
-import { selectPanelsById, useSelector } from '../redux';
+import { selectPanelsById, useSelector } from '../store/redux';
/**
* This hook allows you to access the flyout state, read open right, left and preview panels.
diff --git a/packages/kbn-expandable-flyout/src/index.stories.tsx b/packages/kbn-expandable-flyout/src/index.stories.tsx
index dab81e62f9a0e..a7b1e95e43805 100644
--- a/packages/kbn-expandable-flyout/src/index.stories.tsx
+++ b/packages/kbn-expandable-flyout/src/index.stories.tsx
@@ -21,7 +21,7 @@ import {
} from '@elastic/eui';
import { ExpandableFlyout } from '.';
import { TestProvider } from './test/provider';
-import { State } from './state';
+import { State } from './store/state';
export default {
component: ExpandableFlyout,
@@ -103,13 +103,15 @@ const registeredPanels = [
export const Right: Story = () => {
const state: State = {
- byId: {
- memory: {
- right: {
- id: 'right',
+ panels: {
+ byId: {
+ memory: {
+ right: {
+ id: 'right',
+ },
+ left: undefined,
+ preview: undefined,
},
- left: undefined,
- preview: undefined,
},
},
};
@@ -126,15 +128,17 @@ export const Right: Story = () => {
export const Left: Story = () => {
const state: State = {
- byId: {
- memory: {
- right: {
- id: 'right',
- },
- left: {
- id: 'left',
+ panels: {
+ byId: {
+ memory: {
+ right: {
+ id: 'right',
+ },
+ left: {
+ id: 'left',
+ },
+ preview: undefined,
},
- preview: undefined,
},
},
};
@@ -151,19 +155,21 @@ export const Left: Story = () => {
export const Preview: Story = () => {
const state: State = {
- byId: {
- memory: {
- right: {
- id: 'right',
- },
- left: {
- id: 'left',
- },
- preview: [
- {
- id: 'preview1',
+ panels: {
+ byId: {
+ memory: {
+ right: {
+ id: 'right',
+ },
+ left: {
+ id: 'left',
},
- ],
+ preview: [
+ {
+ id: 'preview1',
+ },
+ ],
+ },
},
},
};
@@ -180,22 +186,24 @@ export const Preview: Story = () => {
export const MultiplePreviews: Story = () => {
const state: State = {
- byId: {
- memory: {
- right: {
- id: 'right',
- },
- left: {
- id: 'left',
- },
- preview: [
- {
- id: 'preview1',
+ panels: {
+ byId: {
+ memory: {
+ right: {
+ id: 'right',
},
- {
- id: 'preview2',
+ left: {
+ id: 'left',
},
- ],
+ preview: [
+ {
+ id: 'preview1',
+ },
+ {
+ id: 'preview2',
+ },
+ ],
+ },
},
},
};
@@ -212,13 +220,15 @@ export const MultiplePreviews: Story = () => {
export const CollapsedPushVsOverlay: Story = () => {
const state: State = {
- byId: {
- memory: {
- right: {
- id: 'right',
+ panels: {
+ byId: {
+ memory: {
+ right: {
+ id: 'right',
+ },
+ left: undefined,
+ preview: undefined,
},
- left: undefined,
- preview: undefined,
},
},
};
@@ -232,15 +242,17 @@ export const CollapsedPushVsOverlay: Story = () => {
export const ExpandedPushVsOverlay: Story = () => {
const state: State = {
- byId: {
- memory: {
- right: {
- id: 'right',
- },
- left: {
- id: 'left',
+ panels: {
+ byId: {
+ memory: {
+ right: {
+ id: 'right',
+ },
+ left: {
+ id: 'left',
+ },
+ preview: undefined,
},
- preview: undefined,
},
},
};
@@ -254,15 +266,17 @@ export const ExpandedPushVsOverlay: Story = () => {
export const DisableTypeSelection: Story = () => {
const state: State = {
- byId: {
- memory: {
- right: {
- id: 'right',
- },
- left: {
- id: 'left',
+ panels: {
+ byId: {
+ memory: {
+ right: {
+ id: 'right',
+ },
+ left: {
+ id: 'left',
+ },
+ preview: undefined,
},
- preview: undefined,
},
},
};
diff --git a/packages/kbn-expandable-flyout/src/index.test.tsx b/packages/kbn-expandable-flyout/src/index.test.tsx
index 1ec37bcd547c0..14146e2da4541 100644
--- a/packages/kbn-expandable-flyout/src/index.test.tsx
+++ b/packages/kbn-expandable-flyout/src/index.test.tsx
@@ -18,7 +18,7 @@ import {
SETTINGS_MENU_BUTTON_TEST_ID,
RIGHT_SECTION_TEST_ID,
} from './components/test_ids';
-import { type State } from './state';
+import { type State } from './store/state';
import { TestProvider } from './test/provider';
import { REDUX_ID_FOR_MEMORY_STORAGE } from './constants';
@@ -33,7 +33,9 @@ const registeredPanels: Panel[] = [
describe('ExpandableFlyout', () => {
it(`shouldn't render flyout if no panels`, () => {
const state: State = {
- byId: {},
+ panels: {
+ byId: {},
+ },
};
const result = render(
@@ -47,13 +49,15 @@ describe('ExpandableFlyout', () => {
it('should render right section', () => {
const state = {
- byId: {
- [id]: {
- right: {
- id: 'key',
+ panels: {
+ byId: {
+ [id]: {
+ right: {
+ id: 'key',
+ },
+ left: undefined,
+ preview: undefined,
},
- left: undefined,
- preview: undefined,
},
},
};
@@ -69,13 +73,15 @@ describe('ExpandableFlyout', () => {
it('should render left section', () => {
const state = {
- byId: {
- [id]: {
- right: undefined,
- left: {
- id: 'key',
+ panels: {
+ byId: {
+ [id]: {
+ right: undefined,
+ left: {
+ id: 'key',
+ },
+ preview: undefined,
},
- preview: undefined,
},
},
};
@@ -91,15 +97,17 @@ describe('ExpandableFlyout', () => {
it('should render preview section', () => {
const state = {
- byId: {
- [id]: {
- right: undefined,
- left: undefined,
- preview: [
- {
- id: 'key',
- },
- ],
+ panels: {
+ byId: {
+ [id]: {
+ right: undefined,
+ left: undefined,
+ preview: [
+ {
+ id: 'key',
+ },
+ ],
+ },
},
},
};
@@ -115,13 +123,15 @@ describe('ExpandableFlyout', () => {
it('should not render flyout when right has value but does not matches registered panels', () => {
const state = {
- byId: {
- [id]: {
- right: {
- id: 'key1',
+ panels: {
+ byId: {
+ [id]: {
+ right: {
+ id: 'key1',
+ },
+ left: undefined,
+ preview: undefined,
},
- left: undefined,
- preview: undefined,
},
},
};
@@ -138,13 +148,15 @@ describe('ExpandableFlyout', () => {
it('should render the menu to change display options', () => {
const state = {
- byId: {
- [id]: {
- right: {
- id: 'key',
+ panels: {
+ byId: {
+ [id]: {
+ right: {
+ id: 'key',
+ },
+ left: undefined,
+ preview: undefined,
},
- left: undefined,
- preview: undefined,
},
},
};
diff --git a/packages/kbn-expandable-flyout/src/provider.test.tsx b/packages/kbn-expandable-flyout/src/provider.test.tsx
index 5bf71f31653e9..5aa386090aa30 100644
--- a/packages/kbn-expandable-flyout/src/provider.test.tsx
+++ b/packages/kbn-expandable-flyout/src/provider.test.tsx
@@ -11,8 +11,8 @@ import React from 'react';
import { render } from '@testing-library/react';
import { TestProvider } from './test/provider';
import { UrlSynchronizer } from './provider';
-import * as actions from './actions';
-import { State } from './state';
+import * as actions from './store/actions';
+import { State } from './store/state';
import { of } from 'rxjs';
const mockGet = jest.fn();
@@ -28,14 +28,16 @@ describe('UrlSynchronizer', () => {
const urlChangedAction = jest.spyOn(actions, 'urlChangedAction');
const initialState: State = {
- byId: {
- [urlKey]: {
- right: { id: 'key1' },
- left: { id: 'key11' },
- preview: undefined,
+ panels: {
+ byId: {
+ [urlKey]: {
+ right: { id: 'key1' },
+ left: { id: 'key11' },
+ preview: undefined,
+ },
},
+ needsSync: true,
},
- needsSync: true,
};
render(
@@ -55,8 +57,10 @@ describe('UrlSynchronizer', () => {
change$: mockChange$,
});
const initialState: State = {
- byId: {},
- needsSync: true,
+ panels: {
+ byId: {},
+ needsSync: true,
+ },
};
render(
@@ -81,14 +85,16 @@ describe('UrlSynchronizer', () => {
change$: mockChange$,
});
const initialState: State = {
- byId: {
- [urlKey]: {
- right: { id: 'key1' },
- left: { id: 'key2' },
- preview: undefined,
+ panels: {
+ byId: {
+ [urlKey]: {
+ right: { id: 'key1' },
+ left: { id: 'key2' },
+ preview: undefined,
+ },
},
+ needsSync: true,
},
- needsSync: true,
};
render(
diff --git a/packages/kbn-expandable-flyout/src/provider.tsx b/packages/kbn-expandable-flyout/src/provider.tsx
index 15bcabc11fc10..cad83bb0ee808 100644
--- a/packages/kbn-expandable-flyout/src/provider.tsx
+++ b/packages/kbn-expandable-flyout/src/provider.tsx
@@ -12,10 +12,10 @@ import React, { FC, PropsWithChildren, useEffect, useMemo } from 'react';
import { Provider as ReduxProvider } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { ExpandableFlyoutContextProvider, useExpandableFlyoutContext } from './context';
-import { FlyoutState } from './state';
+import { FlyoutPanels } from './store/state';
import { useExpandableFlyoutState } from './hooks/use_expandable_flyout_state';
-import { Context, selectNeedsSync, store, useDispatch, useSelector } from './redux';
-import { urlChangedAction } from './actions';
+import { Context, selectNeedsSync, store, useDispatch, useSelector } from './store/redux';
+import { urlChangedAction } from './store/actions';
/**
* Dispatches actions when url state changes and initializes the state when the app is loaded with flyout url parameters
@@ -43,7 +43,7 @@ export const UrlSynchronizer = () => {
return;
}
- const currentValue = urlStorage.get(urlKey);
+ const currentValue = urlStorage.get(urlKey);
// Dispatch current value to redux store as it does not happen automatically
if (currentValue) {
@@ -56,7 +56,7 @@ export const UrlSynchronizer = () => {
);
}
- const subscription = urlStorage.change$(urlKey).subscribe((value) => {
+ const subscription = urlStorage.change$(urlKey).subscribe((value) => {
dispatch(urlChangedAction({ ...value, preview: value?.preview?.at(-1), id: urlKey }));
});
diff --git a/packages/kbn-expandable-flyout/src/actions.ts b/packages/kbn-expandable-flyout/src/store/actions.ts
similarity index 98%
rename from packages/kbn-expandable-flyout/src/actions.ts
rename to packages/kbn-expandable-flyout/src/store/actions.ts
index 6b127da797271..237a3d0226b05 100644
--- a/packages/kbn-expandable-flyout/src/actions.ts
+++ b/packages/kbn-expandable-flyout/src/store/actions.ts
@@ -8,7 +8,7 @@
*/
import { createAction } from '@reduxjs/toolkit';
-import { FlyoutPanelProps } from './types';
+import { FlyoutPanelProps } from '../types';
export enum ActionType {
openFlyout = 'open_flyout',
diff --git a/packages/kbn-expandable-flyout/src/reducer.test.ts b/packages/kbn-expandable-flyout/src/store/reducers.test.ts
similarity index 78%
rename from packages/kbn-expandable-flyout/src/reducer.test.ts
rename to packages/kbn-expandable-flyout/src/store/reducers.test.ts
index 6cb56f86c6794..aafd72196d0f5 100644
--- a/packages/kbn-expandable-flyout/src/reducer.test.ts
+++ b/packages/kbn-expandable-flyout/src/store/reducers.test.ts
@@ -7,9 +7,9 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { FlyoutPanelProps } from './types';
-import { reducer } from './reducer';
-import { initialState, State } from './state';
+import { FlyoutPanelProps } from '../types';
+import { panelsReducer } from './reducers';
+import { initialPanelsState, PanelsState } from './state';
import {
closeLeftPanelAction,
closePanelsAction,
@@ -49,17 +49,18 @@ const previewPanel2: FlyoutPanelProps = {
id: 'preview2',
state: { id: 'state' },
};
-describe('reducer', () => {
+
+describe('panelsReducer', () => {
describe('should handle openFlyout action', () => {
it('should add panels to empty state', () => {
- const state: State = initialState;
+ const state: PanelsState = initialPanelsState;
const action = openPanelsAction({
right: rightPanel1,
left: leftPanel1,
preview: previewPanel1,
id: id1,
});
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -74,7 +75,7 @@ describe('reducer', () => {
});
it('should override all panels in the state', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -89,7 +90,7 @@ describe('reducer', () => {
preview: previewPanel2,
id: id1,
});
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -104,7 +105,7 @@ describe('reducer', () => {
});
it('should remove all panels despite only passing a single section ', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -117,7 +118,7 @@ describe('reducer', () => {
right: rightPanel2,
id: id1,
});
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -132,7 +133,7 @@ describe('reducer', () => {
});
it('should add panels to a new key', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -145,7 +146,7 @@ describe('reducer', () => {
right: rightPanel2,
id: id2,
});
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -167,9 +168,9 @@ describe('reducer', () => {
describe('should handle openRightPanel action', () => {
it('should add right panel to empty state', () => {
- const state: State = initialState;
+ const state: PanelsState = initialPanelsState;
const action = openRightPanelAction({ right: rightPanel1, id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -184,7 +185,7 @@ describe('reducer', () => {
});
it('should replace right panel', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -194,7 +195,7 @@ describe('reducer', () => {
},
};
const action = openRightPanelAction({ right: rightPanel2, id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -209,7 +210,7 @@ describe('reducer', () => {
});
it('should add right panel to a different key', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -219,7 +220,7 @@ describe('reducer', () => {
},
};
const action = openRightPanelAction({ right: rightPanel2, id: id2 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -241,9 +242,9 @@ describe('reducer', () => {
describe('should handle openLeftPanel action', () => {
it('should add left panel to empty state', () => {
- const state: State = initialState;
+ const state: PanelsState = initialPanelsState;
const action = openLeftPanelAction({ left: leftPanel1, id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -258,7 +259,7 @@ describe('reducer', () => {
});
it('should replace only left panel', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -268,7 +269,7 @@ describe('reducer', () => {
},
};
const action = openLeftPanelAction({ left: leftPanel2, id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -283,7 +284,7 @@ describe('reducer', () => {
});
it('should add left panel to a different key', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -293,7 +294,7 @@ describe('reducer', () => {
},
};
const action = openLeftPanelAction({ left: leftPanel2, id: id2 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -315,9 +316,9 @@ describe('reducer', () => {
describe('should handle openPreviewPanel action', () => {
it('should add preview panel to empty state', () => {
- const state: State = initialState;
+ const state: PanelsState = initialPanelsState;
const action = openPreviewPanelAction({ preview: previewPanel1, id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -332,7 +333,7 @@ describe('reducer', () => {
});
it('should add preview panel to the list of preview panels', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -342,7 +343,7 @@ describe('reducer', () => {
},
};
const action = openPreviewPanelAction({ preview: previewPanel2, id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -357,7 +358,7 @@ describe('reducer', () => {
});
it('should add preview panel to a different key', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -367,7 +368,7 @@ describe('reducer', () => {
},
};
const action = openPreviewPanelAction({ preview: previewPanel2, id: id2 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -389,15 +390,18 @@ describe('reducer', () => {
describe('should handle closeRightPanel action', () => {
it('should return empty state when removing right panel from empty state', () => {
- const state: State = initialState;
+ const state: PanelsState = initialPanelsState;
const action = closeRightPanelAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
- expect(newState).toEqual({ ...state, needsSync: true });
+ expect(newState).toEqual({
+ ...state,
+ needsSync: true,
+ });
});
it(`should return unmodified state when removing right panel when no right panel exist`, () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -407,13 +411,16 @@ describe('reducer', () => {
},
};
const action = closeRightPanelAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
- expect(newState).toEqual({ ...state, needsSync: true });
+ expect(newState).toEqual({
+ ...state,
+ needsSync: true,
+ });
});
it('should remove right panel', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -424,7 +431,7 @@ describe('reducer', () => {
};
const action = closeRightPanelAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -439,7 +446,7 @@ describe('reducer', () => {
});
it('should not remove right panel for a different key', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -450,7 +457,7 @@ describe('reducer', () => {
};
const action = closeRightPanelAction({ id: id2 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -467,15 +474,18 @@ describe('reducer', () => {
describe('should handle closeLeftPanel action', () => {
it('should return empty state when removing left panel on empty state', () => {
- const state: State = initialState;
+ const state: PanelsState = initialPanelsState;
const action = closeLeftPanelAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
- expect(newState).toEqual({ ...state, needsSync: true });
+ expect(newState).toEqual({
+ ...state,
+ needsSync: true,
+ });
});
it(`should return unmodified state when removing left panel when no left panel exist`, () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: undefined,
@@ -485,13 +495,16 @@ describe('reducer', () => {
},
};
const action = closeLeftPanelAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
- expect(newState).toEqual({ ...state, needsSync: true });
+ expect(newState).toEqual({
+ ...state,
+ needsSync: true,
+ });
});
it('should remove left panel', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -501,7 +514,7 @@ describe('reducer', () => {
},
};
const action = closeLeftPanelAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -516,7 +529,7 @@ describe('reducer', () => {
});
it('should not remove left panel for a different key', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -526,7 +539,7 @@ describe('reducer', () => {
},
};
const action = closeLeftPanelAction({ id: id2 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -543,15 +556,18 @@ describe('reducer', () => {
describe('should handle closePreviewPanel action', () => {
it('should return empty state when removing preview panel on empty state', () => {
- const state: State = initialState;
+ const state: PanelsState = initialPanelsState;
const action = closePreviewPanelAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
- expect(newState).toEqual({ ...state, needsSync: true });
+ expect(newState).toEqual({
+ ...state,
+ needsSync: true,
+ });
});
it(`should return unmodified state when removing preview panel when no preview panel exist`, () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -561,13 +577,16 @@ describe('reducer', () => {
},
};
const action = closePreviewPanelAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
- expect(newState).toEqual({ ...state, needsSync: true });
+ expect(newState).toEqual({
+ ...state,
+ needsSync: true,
+ });
});
it('should remove all preview panels', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: rightPanel1,
@@ -577,7 +596,7 @@ describe('reducer', () => {
},
};
const action = closePreviewPanelAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -592,7 +611,7 @@ describe('reducer', () => {
});
it('should not remove preview panels for a different key', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -602,7 +621,7 @@ describe('reducer', () => {
},
};
const action = closePreviewPanelAction({ id: id2 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -619,15 +638,18 @@ describe('reducer', () => {
describe('should handle previousPreviewPanel action', () => {
it('should return empty state when previous preview panel on an empty state', () => {
- const state: State = initialState;
+ const state: PanelsState = initialPanelsState;
const action = previousPreviewPanelAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
- expect(newState).toEqual({ ...initialState, needsSync: false });
+ expect(newState).toEqual({
+ ...initialPanelsState,
+ needsSync: false,
+ });
});
it(`should return unmodified state when previous preview panel when no preview panel exist`, () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -637,13 +659,16 @@ describe('reducer', () => {
},
};
const action = previousPreviewPanelAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
- expect(newState).toEqual({ ...state, needsSync: false });
+ expect(newState).toEqual({
+ ...state,
+ needsSync: false,
+ });
});
it('should remove only last preview panel', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: rightPanel1,
@@ -653,7 +678,7 @@ describe('reducer', () => {
},
};
const action = previousPreviewPanelAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -668,7 +693,7 @@ describe('reducer', () => {
});
it('should not remove the last preview panel for a different key', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -678,7 +703,7 @@ describe('reducer', () => {
},
};
const action = previousPreviewPanelAction({ id: id2 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -695,15 +720,18 @@ describe('reducer', () => {
describe('should handle closeFlyout action', () => {
it('should return empty state when closing flyout on an empty state', () => {
- const state: State = initialState;
+ const state: PanelsState = initialPanelsState;
const action = closePanelsAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
- expect(newState).toEqual({ ...initialState, needsSync: true });
+ expect(newState).toEqual({
+ ...initialPanelsState,
+ needsSync: true,
+ });
});
it('should remove all panels', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -713,7 +741,7 @@ describe('reducer', () => {
},
};
const action = closePanelsAction({ id: id1 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
@@ -728,7 +756,7 @@ describe('reducer', () => {
});
it('should not remove panels for a different key', () => {
- const state: State = {
+ const state: PanelsState = {
byId: {
[id1]: {
left: leftPanel1,
@@ -738,7 +766,7 @@ describe('reducer', () => {
},
};
const action = closePanelsAction({ id: id2 });
- const newState: State = reducer(state, action);
+ const newState: PanelsState = panelsReducer(state, action);
expect(newState).toEqual({
byId: {
diff --git a/packages/kbn-expandable-flyout/src/reducer.ts b/packages/kbn-expandable-flyout/src/store/reducers.ts
similarity index 96%
rename from packages/kbn-expandable-flyout/src/reducer.ts
rename to packages/kbn-expandable-flyout/src/store/reducers.ts
index 49c4c4b9774b1..8971fd55f7571 100644
--- a/packages/kbn-expandable-flyout/src/reducer.ts
+++ b/packages/kbn-expandable-flyout/src/store/reducers.ts
@@ -21,9 +21,9 @@ import {
openPreviewPanelAction,
urlChangedAction,
} from './actions';
-import { initialState } from './state';
+import { initialPanelsState } from './state';
-export const reducer = createReducer(initialState, (builder) => {
+export const panelsReducer = createReducer(initialPanelsState, (builder) => {
builder.addCase(openPanelsAction, (state, { payload: { preview, left, right, id } }) => {
if (id in state.byId) {
state.byId[id].right = right;
@@ -72,7 +72,7 @@ export const reducer = createReducer(initialState, (builder) => {
if (id in state.byId) {
if (state.byId[id].preview) {
const previewIdenticalToLastOne = deepEqual(preview, state.byId[id].preview?.at(-1));
- // Only append preview when it does not match the last item in state.byId[id].preview
+ // Only append preview when it does not match the last item in state.data.byId[id].preview
if (!previewIdenticalToLastOne) {
state.byId[id].preview?.push(preview);
}
diff --git a/packages/kbn-expandable-flyout/src/redux.ts b/packages/kbn-expandable-flyout/src/store/redux.ts
similarity index 77%
rename from packages/kbn-expandable-flyout/src/redux.ts
rename to packages/kbn-expandable-flyout/src/store/redux.ts
index 5cc80517c5c9f..0e81ba74de2de 100644
--- a/packages/kbn-expandable-flyout/src/redux.ts
+++ b/packages/kbn-expandable-flyout/src/store/redux.ts
@@ -11,13 +11,14 @@ import { createContext } from 'react';
import { createDispatchHook, createSelectorHook, ReactReduxContextValue } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import { createSelector } from 'reselect';
-import { reducer } from './reducer';
+import { panelsReducer } from './reducers';
import { initialState, State } from './state';
export const store = configureStore({
- reducer,
+ reducer: {
+ panels: panelsReducer,
+ },
devTools: process.env.NODE_ENV !== 'production',
- enhancers: [],
});
export const Context = createContext>({
@@ -30,7 +31,7 @@ export const useSelector = createSelectorHook(Context);
const stateSelector = (state: State) => state;
+const panelsSelector = createSelector(stateSelector, (state) => state.panels);
export const selectPanelsById = (id: string) =>
- createSelector(stateSelector, (state) => state.byId[id] || {});
-
-export const selectNeedsSync = () => createSelector(stateSelector, (state) => state.needsSync);
+ createSelector(panelsSelector, (state) => state.byId[id] || {});
+export const selectNeedsSync = () => createSelector(panelsSelector, (state) => state.needsSync);
diff --git a/packages/kbn-expandable-flyout/src/state.ts b/packages/kbn-expandable-flyout/src/store/state.ts
similarity index 79%
rename from packages/kbn-expandable-flyout/src/state.ts
rename to packages/kbn-expandable-flyout/src/store/state.ts
index 40cf03f43d868..12f1b0135460b 100644
--- a/packages/kbn-expandable-flyout/src/state.ts
+++ b/packages/kbn-expandable-flyout/src/store/state.ts
@@ -7,9 +7,9 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { FlyoutPanelProps } from './types';
+import { FlyoutPanelProps } from '../..';
-export interface FlyoutState {
+export interface FlyoutPanels {
/**
* Panel to render in the left section
*/
@@ -24,12 +24,12 @@ export interface FlyoutState {
preview: FlyoutPanelProps[] | undefined;
}
-export interface State {
+export interface PanelsState {
/**
* Store the panels for multiple flyouts
*/
byId: {
- [id: string]: FlyoutState;
+ [id: string]: FlyoutPanels;
};
/**
* Is the flyout in sync with external storage (eg. url)?
@@ -39,7 +39,18 @@ export interface State {
needsSync?: boolean;
}
-export const initialState: State = {
+export const initialPanelsState: PanelsState = {
byId: {},
needsSync: false,
};
+
+export interface State {
+ /**
+ * All panels related information
+ */
+ panels: PanelsState;
+}
+
+export const initialState: State = {
+ panels: initialPanelsState,
+};
diff --git a/packages/kbn-expandable-flyout/src/test/provider.tsx b/packages/kbn-expandable-flyout/src/test/provider.tsx
index bf0ca914927b2..b6914099e2e42 100644
--- a/packages/kbn-expandable-flyout/src/test/provider.tsx
+++ b/packages/kbn-expandable-flyout/src/test/provider.tsx
@@ -12,9 +12,9 @@ import { configureStore } from '@reduxjs/toolkit';
import React, { FC, PropsWithChildren } from 'react';
import { I18nProvider } from '@kbn/i18n-react';
import { ExpandableFlyoutContextProvider } from '../context';
-import { reducer } from '../reducer';
-import { Context } from '../redux';
-import { initialState, State } from '../state';
+import { panelsReducer } from '../store/reducers';
+import { Context } from '../store/redux';
+import { initialState, State } from '../store/state';
interface TestProviderProps {
state?: State;
@@ -27,7 +27,9 @@ export const TestProvider: FC> = ({
urlKey,
}) => {
const store = configureStore({
- reducer,
+ reducer: {
+ panels: panelsReducer,
+ },
devTools: false,
preloadedState: state,
enhancers: [],
From d6fdc825b4dd7c5878d32a01c4bd8ff9d5eec91e Mon Sep 17 00:00:00 2001
From: Rodney Norris
Date: Mon, 16 Sep 2024 17:02:49 -0500
Subject: [PATCH 21/26] [Search][Onboarding] Use new index details page
(#193058)
## Summary
- Updated the empty state redirect to use the new index details page
- Updated the empty state to only show "Run in Console" when cURL is the
selected coding language.
---
.../components/start/create_index_code.tsx | 20 ++++++++++---------
.../public/components/start/hooks/utils.ts | 5 ++---
.../svl_search_elasticsearch_start_page.ts | 4 +---
3 files changed, 14 insertions(+), 15 deletions(-)
diff --git a/x-pack/plugins/search_indices/public/components/start/create_index_code.tsx b/x-pack/plugins/search_indices/public/components/start/create_index_code.tsx
index 4901847eeed22..b78137e7a3fdd 100644
--- a/x-pack/plugins/search_indices/public/components/start/create_index_code.tsx
+++ b/x-pack/plugins/search_indices/public/components/start/create_index_code.tsx
@@ -45,21 +45,23 @@ export const CreateIndexCodeView = ({ createIndexForm }: CreateIndexCodeViewProp
return (
-
+
setSelectedLanguage(value)}
/>
-
-
-
+ {selectedLanguage === 'curl' && (
+
+
+
+ )}
{selectedCodeExample.installCommand && (
{
- expect(await browser.getCurrentUrl()).contain(
- '/app/management/data/index_management/indices/index_details'
- );
+ expect(await browser.getCurrentUrl()).contain('/app/elasticsearch/indices/index_details');
});
},
async expectToBeOnIndexListPage() {
From 42fef0b84bda007bb24f149512cbe7de8354f339 Mon Sep 17 00:00:00 2001
From: Jonathan Budzenski
Date: Mon, 16 Sep 2024 17:04:40 -0500
Subject: [PATCH 22/26] skip failing test suite (#189719)
---
.../public/components/case_form_fields/assignees.test.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/cases/public/components/case_form_fields/assignees.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/assignees.test.tsx
index 77a781535eb91..fd0a75f6d7a28 100644
--- a/x-pack/plugins/cases/public/components/case_form_fields/assignees.test.tsx
+++ b/x-pack/plugins/cases/public/components/case_form_fields/assignees.test.tsx
@@ -23,7 +23,8 @@ jest.mock('../../containers/user_profiles/api');
const currentUserProfile = userProfiles[0];
-describe('Assignees', () => {
+// Failing: See https://github.com/elastic/kibana/issues/189719
+describe.skip('Assignees', () => {
let globalForm: FormHook;
let appMockRender: AppMockRenderer;
From e7c8cba25cf693bd40a2d9d4f041cfd99273ac31 Mon Sep 17 00:00:00 2001
From: Jonathan Budzenski
Date: Mon, 16 Sep 2024 17:06:41 -0500
Subject: [PATCH 23/26] skip failing test suite (#192944)
---
.../cases/public/components/files/file_name_link.test.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/cases/public/components/files/file_name_link.test.tsx b/x-pack/plugins/cases/public/components/files/file_name_link.test.tsx
index 0fa3324d295f3..efededf3fba89 100644
--- a/x-pack/plugins/cases/public/components/files/file_name_link.test.tsx
+++ b/x-pack/plugins/cases/public/components/files/file_name_link.test.tsx
@@ -15,7 +15,8 @@ import { createAppMockRenderer } from '../../common/mock';
import { basicFileMock } from '../../containers/mock';
import { FileNameLink } from './file_name_link';
-describe('FileNameLink', () => {
+// Failing: See https://github.com/elastic/kibana/issues/192944
+describe.skip('FileNameLink', () => {
let appMockRender: AppMockRenderer;
const defaultProps = {
From 760379ebf344f428551dfa63d046a043e61a7740 Mon Sep 17 00:00:00 2001
From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Date: Tue, 17 Sep 2024 08:10:21 +1000
Subject: [PATCH 24/26] skip failing test suite (#191808)
---
test/functional/apps/console/monaco/_autocomplete.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/test/functional/apps/console/monaco/_autocomplete.ts b/test/functional/apps/console/monaco/_autocomplete.ts
index 36d96443a5a69..6e0d83ffcd56e 100644
--- a/test/functional/apps/console/monaco/_autocomplete.ts
+++ b/test/functional/apps/console/monaco/_autocomplete.ts
@@ -34,7 +34,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
}
- describe('console autocomplete feature', function describeIndexTests() {
+ // Failing: See https://github.com/elastic/kibana/issues/191808
+ describe.skip('console autocomplete feature', function describeIndexTests() {
this.tags('includeFirefox');
before(async () => {
log.debug('navigateTo console');
From f6510f67f73804e01a783ed9a57ef72f84bbe9bd Mon Sep 17 00:00:00 2001
From: Jonathan Budzenski
Date: Mon, 16 Sep 2024 17:10:56 -0500
Subject: [PATCH 25/26] skip failing test suite (#192999)
---
.../integrations/sections/epm/screens/detail/index.test.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx
index 4617cc9bd8b1f..58c512a26c294 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx
@@ -39,7 +39,8 @@ import { Detail } from '.';
// @ts-ignore this saves us having to define all experimental features
ExperimentalFeaturesService.init({});
-describe('when on integration detail', () => {
+// Failing: See https://github.com/elastic/kibana/issues/192999
+describe.skip('when on integration detail', () => {
const pkgkey = 'nginx-0.3.7';
const detailPageUrlPath = pagePathGetters.integration_details_overview({ pkgkey })[1];
let testRenderer: TestRenderer;
From 4405f94cad29adf5adf2424e69cbc030e11883f7 Mon Sep 17 00:00:00 2001
From: Nicolas Chaulet
Date: Mon, 16 Sep 2024 18:47:45 -0400
Subject: [PATCH 26/26] [Fleet] Add new space aware saved object type to Fleet
saved object permissions (#193025)
---
x-pack/plugins/fleet/server/plugin.ts | 11 ++-
.../apis/space_awareness/agent_policies.ts | 9 ++-
.../apis/space_awareness/api_helper.ts | 4 ++
.../platform_security/authorization.ts | 68 +++++++++++++++++++
4 files changed, 87 insertions(+), 5 deletions(-)
diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts
index 3967c3b6abc7c..21c3f1bf97f12 100644
--- a/x-pack/plugins/fleet/server/plugin.ts
+++ b/x-pack/plugins/fleet/server/plugin.ts
@@ -66,7 +66,12 @@ import {
} from '../common';
import type { ExperimentalFeatures } from '../common/experimental_features';
import { parseExperimentalConfigValue } from '../common/experimental_features';
-
+import {
+ LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE,
+ PACKAGE_POLICY_SAVED_OBJECT_TYPE,
+ AGENT_POLICY_SAVED_OBJECT_TYPE,
+ LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE,
+} from '../common/constants';
import { getFilesClientFactory } from './services/files/get_files_client_factory';
import type { MessageSigningServiceInterface } from './services/security';
@@ -79,12 +84,10 @@ import {
} from './services/security';
import {
- LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE,
ASSETS_SAVED_OBJECT_TYPE,
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
OUTPUT_SAVED_OBJECT_TYPE,
- PACKAGE_POLICY_SAVED_OBJECT_TYPE,
PACKAGES_SAVED_OBJECT_TYPE,
PLUGIN_ID,
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
@@ -191,6 +194,8 @@ export type FleetSetupContract = void;
const allSavedObjectTypes = [
OUTPUT_SAVED_OBJECT_TYPE,
LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE,
+ AGENT_POLICY_SAVED_OBJECT_TYPE,
+ LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE,
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
PACKAGES_SAVED_OBJECT_TYPE,
ASSETS_SAVED_OBJECT_TYPE,
diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/agent_policies.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/agent_policies.ts
index 40e163dfabf47..037ba332cfefb 100644
--- a/x-pack/test/fleet_api_integration/apis/space_awareness/agent_policies.ts
+++ b/x-pack/test/fleet_api_integration/apis/space_awareness/agent_policies.ts
@@ -11,10 +11,11 @@ import { FtrProviderContext } from '../../../api_integration/ftr_provider_contex
import { skipIfNoDockerRegistry } from '../../helpers';
import { SpaceTestApiClient } from './api_helper';
import { cleanFleetIndices, expectToRejectWithNotFound } from './helpers';
+import { setupTestUsers, testUsers } from '../test_users';
export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
- const supertest = getService('supertest');
+ const supertestWithoutAuth = getService('supertestWithoutAuth');
const esClient = getService('es');
const kibanaServer = getService('kibanaServer');
const spaces = getService('spaces');
@@ -22,13 +23,17 @@ export default function (providerContext: FtrProviderContext) {
describe('agent policies', function () {
skipIfNoDockerRegistry(providerContext);
- const apiClient = new SpaceTestApiClient(supertest);
+ const apiClient = new SpaceTestApiClient(supertestWithoutAuth, {
+ username: testUsers.fleet_all_int_all.username,
+ password: testUsers.fleet_all_int_all.password,
+ });
let defaultSpacePolicy1: CreateAgentPolicyResponse;
let spaceTest1Policy1: CreateAgentPolicyResponse;
let spaceTest1Policy2: CreateAgentPolicyResponse;
before(async () => {
+ await setupTestUsers(getService('security'));
TEST_SPACE_1 = spaces.getDefaultTestSpace();
await kibanaServer.savedObjects.cleanStandardList();
await kibanaServer.savedObjects.cleanStandardList({
diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts
index 9009e2b81a73b..1de90ae3dcfaa 100644
--- a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts
+++ b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts
@@ -133,6 +133,7 @@ export class SpaceTestApiClient {
async deleteAgentPolicy(agentPolicyId: string, spaceId?: string) {
await this.supertest
.post(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies/delete`)
+ .auth(this.auth.username, this.auth.password)
.send({
agentPolicyId,
})
@@ -142,6 +143,7 @@ export class SpaceTestApiClient {
async getAgentPolicy(policyId: string, spaceId?: string): Promise {
const { body: res } = await this.supertest
.get(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies/${policyId}`)
+ .auth(this.auth.username, this.auth.password)
.expect(200);
return res;
@@ -172,6 +174,7 @@ export class SpaceTestApiClient {
async getAgentPolicies(spaceId?: string): Promise {
const { body: res } = await this.supertest
.get(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies`)
+ .auth(this.auth.username, this.auth.password)
.expect(200);
return res;
@@ -482,6 +485,7 @@ export class SpaceTestApiClient {
async postEnableSpaceAwareness(spaceId?: string): Promise {
const { body: res } = await this.supertest
.post(`${this.getBaseUrl(spaceId)}/internal/fleet/enable_space_awareness`)
+ .auth(this.auth.username, this.auth.password)
.set('kbn-xsrf', 'xxxx')
.set('elastic-api-version', '1');
diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts b/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts
index 329b9be0de561..2e41125e8265b 100644
--- a/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts
@@ -3666,6 +3666,18 @@ export default function ({ getService }: FtrProviderContext) {
"saved_object:ingest-agent-policies/delete",
"saved_object:ingest-agent-policies/bulk_delete",
"saved_object:ingest-agent-policies/share_to_space",
+ "saved_object:fleet-agent-policies/bulk_get",
+ "saved_object:fleet-agent-policies/get",
+ "saved_object:fleet-agent-policies/find",
+ "saved_object:fleet-agent-policies/open_point_in_time",
+ "saved_object:fleet-agent-policies/close_point_in_time",
+ "saved_object:fleet-agent-policies/create",
+ "saved_object:fleet-agent-policies/bulk_create",
+ "saved_object:fleet-agent-policies/update",
+ "saved_object:fleet-agent-policies/bulk_update",
+ "saved_object:fleet-agent-policies/delete",
+ "saved_object:fleet-agent-policies/bulk_delete",
+ "saved_object:fleet-agent-policies/share_to_space",
"saved_object:ingest-package-policies/bulk_get",
"saved_object:ingest-package-policies/get",
"saved_object:ingest-package-policies/find",
@@ -3678,6 +3690,18 @@ export default function ({ getService }: FtrProviderContext) {
"saved_object:ingest-package-policies/delete",
"saved_object:ingest-package-policies/bulk_delete",
"saved_object:ingest-package-policies/share_to_space",
+ "saved_object:fleet-package-policies/bulk_get",
+ "saved_object:fleet-package-policies/get",
+ "saved_object:fleet-package-policies/find",
+ "saved_object:fleet-package-policies/open_point_in_time",
+ "saved_object:fleet-package-policies/close_point_in_time",
+ "saved_object:fleet-package-policies/create",
+ "saved_object:fleet-package-policies/bulk_create",
+ "saved_object:fleet-package-policies/update",
+ "saved_object:fleet-package-policies/bulk_update",
+ "saved_object:fleet-package-policies/delete",
+ "saved_object:fleet-package-policies/bulk_delete",
+ "saved_object:fleet-package-policies/share_to_space",
"saved_object:epm-packages/bulk_get",
"saved_object:epm-packages/get",
"saved_object:epm-packages/find",
@@ -3993,6 +4017,18 @@ export default function ({ getService }: FtrProviderContext) {
"saved_object:ingest-agent-policies/delete",
"saved_object:ingest-agent-policies/bulk_delete",
"saved_object:ingest-agent-policies/share_to_space",
+ "saved_object:fleet-agent-policies/bulk_get",
+ "saved_object:fleet-agent-policies/get",
+ "saved_object:fleet-agent-policies/find",
+ "saved_object:fleet-agent-policies/open_point_in_time",
+ "saved_object:fleet-agent-policies/close_point_in_time",
+ "saved_object:fleet-agent-policies/create",
+ "saved_object:fleet-agent-policies/bulk_create",
+ "saved_object:fleet-agent-policies/update",
+ "saved_object:fleet-agent-policies/bulk_update",
+ "saved_object:fleet-agent-policies/delete",
+ "saved_object:fleet-agent-policies/bulk_delete",
+ "saved_object:fleet-agent-policies/share_to_space",
"saved_object:ingest-package-policies/bulk_get",
"saved_object:ingest-package-policies/get",
"saved_object:ingest-package-policies/find",
@@ -4005,6 +4041,18 @@ export default function ({ getService }: FtrProviderContext) {
"saved_object:ingest-package-policies/delete",
"saved_object:ingest-package-policies/bulk_delete",
"saved_object:ingest-package-policies/share_to_space",
+ "saved_object:fleet-package-policies/bulk_get",
+ "saved_object:fleet-package-policies/get",
+ "saved_object:fleet-package-policies/find",
+ "saved_object:fleet-package-policies/open_point_in_time",
+ "saved_object:fleet-package-policies/close_point_in_time",
+ "saved_object:fleet-package-policies/create",
+ "saved_object:fleet-package-policies/bulk_create",
+ "saved_object:fleet-package-policies/update",
+ "saved_object:fleet-package-policies/bulk_update",
+ "saved_object:fleet-package-policies/delete",
+ "saved_object:fleet-package-policies/bulk_delete",
+ "saved_object:fleet-package-policies/share_to_space",
"saved_object:epm-packages/bulk_get",
"saved_object:epm-packages/get",
"saved_object:epm-packages/find",
@@ -4305,11 +4353,21 @@ export default function ({ getService }: FtrProviderContext) {
"saved_object:ingest-agent-policies/find",
"saved_object:ingest-agent-policies/open_point_in_time",
"saved_object:ingest-agent-policies/close_point_in_time",
+ "saved_object:fleet-agent-policies/bulk_get",
+ "saved_object:fleet-agent-policies/get",
+ "saved_object:fleet-agent-policies/find",
+ "saved_object:fleet-agent-policies/open_point_in_time",
+ "saved_object:fleet-agent-policies/close_point_in_time",
"saved_object:ingest-package-policies/bulk_get",
"saved_object:ingest-package-policies/get",
"saved_object:ingest-package-policies/find",
"saved_object:ingest-package-policies/open_point_in_time",
"saved_object:ingest-package-policies/close_point_in_time",
+ "saved_object:fleet-package-policies/bulk_get",
+ "saved_object:fleet-package-policies/get",
+ "saved_object:fleet-package-policies/find",
+ "saved_object:fleet-package-policies/open_point_in_time",
+ "saved_object:fleet-package-policies/close_point_in_time",
"saved_object:epm-packages/bulk_get",
"saved_object:epm-packages/get",
"saved_object:epm-packages/find",
@@ -4457,11 +4515,21 @@ export default function ({ getService }: FtrProviderContext) {
"saved_object:ingest-agent-policies/find",
"saved_object:ingest-agent-policies/open_point_in_time",
"saved_object:ingest-agent-policies/close_point_in_time",
+ "saved_object:fleet-agent-policies/bulk_get",
+ "saved_object:fleet-agent-policies/get",
+ "saved_object:fleet-agent-policies/find",
+ "saved_object:fleet-agent-policies/open_point_in_time",
+ "saved_object:fleet-agent-policies/close_point_in_time",
"saved_object:ingest-package-policies/bulk_get",
"saved_object:ingest-package-policies/get",
"saved_object:ingest-package-policies/find",
"saved_object:ingest-package-policies/open_point_in_time",
"saved_object:ingest-package-policies/close_point_in_time",
+ "saved_object:fleet-package-policies/bulk_get",
+ "saved_object:fleet-package-policies/get",
+ "saved_object:fleet-package-policies/find",
+ "saved_object:fleet-package-policies/open_point_in_time",
+ "saved_object:fleet-package-policies/close_point_in_time",
"saved_object:epm-packages/bulk_get",
"saved_object:epm-packages/get",
"saved_object:epm-packages/find",