From b8c17b022704f146be8f066edc5fcea39a39cfc2 Mon Sep 17 00:00:00 2001
From: Josh Dover <1813008+joshdover@users.noreply.github.com>
Date: Tue, 4 Oct 2022 12:56:28 +0200
Subject: [PATCH 01/27] [Fleet] Allow agent force upgrading to a newer patch
release (#142450)
---
.../routes/agent/upgrade_handler.test.ts | 14 ++++++
.../server/routes/agent/upgrade_handler.ts | 46 ++++++++++++++++---
2 files changed, 53 insertions(+), 7 deletions(-)
diff --git a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.test.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.test.ts
index 5eafcf1a94104..aefcbfc5cd87f 100644
--- a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.test.ts
+++ b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.test.ts
@@ -22,5 +22,19 @@ describe('upgrade handler', () => {
it('should not throw if upgrade version is equal to kibana version with snapshot', () => {
expect(() => checkKibanaVersion('8.4.0', '8.4.0-SNAPSHOT')).not.toThrowError();
});
+
+ it('should not throw if force is specified and patch is newer', () => {
+ expect(() => checkKibanaVersion('8.4.1', '8.4.0', true)).not.toThrowError();
+ expect(() => checkKibanaVersion('8.4.1-SNAPSHOT', '8.4.0', true)).not.toThrowError();
+ });
+
+ it('should throw if force is specified and minor is newer', () => {
+ expect(() => checkKibanaVersion('8.5.0', '8.4.0', true)).toThrowError();
+ });
+
+ it('should not throw if force is specified and major and minor is newer', () => {
+ expect(() => checkKibanaVersion('7.5.0', '8.4.0', true)).not.toThrowError();
+ expect(() => checkKibanaVersion('8.4.0', '8.4.0', true)).not.toThrowError();
+ });
});
});
diff --git a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts
index a79edbaa36856..d3fffac7d9050 100644
--- a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts
+++ b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts
@@ -10,6 +10,8 @@ import type { TypeOf } from '@kbn/config-schema';
import semverCoerce from 'semver/functions/coerce';
import semverGt from 'semver/functions/gt';
+import semverMajor from 'semver/functions/major';
+import semverMinor from 'semver/functions/minor';
import type { PostAgentUpgradeResponse, GetCurrentUpgradesResponse } from '../../../common/types';
import type { PostAgentUpgradeRequestSchema, PostBulkAgentUpgradeRequestSchema } from '../../types';
@@ -34,7 +36,7 @@ export const postAgentUpgradeHandler: RequestHandler<
const { version, source_uri: sourceUri, force } = request.body;
const kibanaVersion = appContextService.getKibanaVersion();
try {
- checkKibanaVersion(version, kibanaVersion);
+ checkKibanaVersion(version, kibanaVersion, force);
} catch (err) {
return response.customError({
statusCode: 400,
@@ -114,9 +116,9 @@ export const postBulkAgentsUpgradeHandler: RequestHandler<
} = request.body;
const kibanaVersion = appContextService.getKibanaVersion();
try {
- checkKibanaVersion(version, kibanaVersion);
+ checkKibanaVersion(version, kibanaVersion, force);
const fleetServerAgents = await getAllFleetServerAgents(soClient, esClient);
- checkFleetServerVersion(version, fleetServerAgents);
+ checkFleetServerVersion(version, fleetServerAgents, force);
} catch (err) {
return response.customError({
statusCode: 400,
@@ -158,7 +160,7 @@ export const getCurrentUpgradesHandler: RequestHandler = async (context, request
}
};
-export const checkKibanaVersion = (version: string, kibanaVersion: string) => {
+export const checkKibanaVersion = (version: string, kibanaVersion: string, force = false) => {
// get version number only in case "-SNAPSHOT" is in it
const kibanaVersionNumber = semverCoerce(kibanaVersion)?.version;
if (!kibanaVersionNumber) throw new Error(`kibanaVersion ${kibanaVersionNumber} is not valid`);
@@ -166,14 +168,31 @@ export const checkKibanaVersion = (version: string, kibanaVersion: string) => {
if (!versionToUpgradeNumber)
throw new Error(`version to upgrade ${versionToUpgradeNumber} is not valid`);
- if (semverGt(versionToUpgradeNumber, kibanaVersionNumber))
+ if (!force && semverGt(versionToUpgradeNumber, kibanaVersionNumber)) {
throw new Error(
`cannot upgrade agent to ${versionToUpgradeNumber} because it is higher than the installed kibana version ${kibanaVersionNumber}`
);
+ }
+
+ const kibanaMajorGt = semverMajor(kibanaVersionNumber) > semverMajor(versionToUpgradeNumber);
+ const kibanaMajorEqMinorGte =
+ semverMajor(kibanaVersionNumber) === semverMajor(versionToUpgradeNumber) &&
+ semverMinor(kibanaVersionNumber) >= semverMinor(versionToUpgradeNumber);
+
+ // When force is enabled, only the major and minor versions are checked
+ if (force && !(kibanaMajorGt || kibanaMajorEqMinorGte)) {
+ throw new Error(
+ `cannot force upgrade agent to ${versionToUpgradeNumber} because it does not satisfy the major and minor of the installed kibana version ${kibanaVersionNumber}`
+ );
+ }
};
// Check the installed fleet server version
-const checkFleetServerVersion = (versionToUpgradeNumber: string, fleetServerAgents: Agent[]) => {
+const checkFleetServerVersion = (
+ versionToUpgradeNumber: string,
+ fleetServerAgents: Agent[],
+ force = false
+) => {
const fleetServerVersions = fleetServerAgents.map(
(agent) => agent.local_metadata.elastic.agent.version
) as string[];
@@ -184,9 +203,22 @@ const checkFleetServerVersion = (versionToUpgradeNumber: string, fleetServerAgen
return;
}
- if (semverGt(versionToUpgradeNumber, maxFleetServerVersion)) {
+ if (!force && semverGt(versionToUpgradeNumber, maxFleetServerVersion)) {
throw new Error(
`cannot upgrade agent to ${versionToUpgradeNumber} because it is higher than the latest fleet server version ${maxFleetServerVersion}`
);
}
+
+ const fleetServerMajorGt =
+ semverMajor(maxFleetServerVersion) > semverMajor(versionToUpgradeNumber);
+ const fleetServerMajorEqMinorGte =
+ semverMajor(maxFleetServerVersion) === semverMajor(versionToUpgradeNumber) &&
+ semverMinor(maxFleetServerVersion) >= semverMinor(versionToUpgradeNumber);
+
+ // When force is enabled, only the major and minor versions are checked
+ if (force && !(fleetServerMajorGt || fleetServerMajorEqMinorGte)) {
+ throw new Error(
+ `cannot force upgrade agent to ${versionToUpgradeNumber} because it does not satisfy the major and minor of the latest fleet server version ${maxFleetServerVersion}`
+ );
+ }
};
From a43f235c28de70d3bb11dde99e7fb4c76a99cb0e Mon Sep 17 00:00:00 2001
From: Abdul Wahab Zahid
Date: Tue, 4 Oct 2022 12:58:35 +0200
Subject: [PATCH 02/27] Fix: Render 404 step screenshots as "Image not
available". (#142320)
By default, mark `allStepsLoading = true` where steps loading isn't important.
---
.../monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx
index 4cee2eb9bfca8..ed3f5499ba0ab 100644
--- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx
+++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx
@@ -41,7 +41,7 @@ export const PingTimestamp = ({
label,
checkGroup,
stepStatus,
- allStepsLoaded,
+ allStepsLoaded = true,
initialStepNo = 1,
}: Props) => {
const [stepNumber, setStepNumber] = useState(initialStepNo);
From d60acf8774ac210e842e8ee39336a54205dae3de Mon Sep 17 00:00:00 2001
From: Milton Hultgren
Date: Tue, 4 Oct 2022 13:11:11 +0200
Subject: [PATCH 03/27] [Infra] Make nav react to Hosts view enabled flag
changing (#142477)
* [Infra] Make nav react to Hosts view enabled flag changing (#140996)
* Move comment to more relevant location
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/infra/public/plugin.ts | 50 +++++++++++++++++++++------
1 file changed, 39 insertions(+), 11 deletions(-)
diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts
index 6eb6663b10eeb..c14a13d1a7ea1 100644
--- a/x-pack/plugins/infra/public/plugin.ts
+++ b/x-pack/plugins/infra/public/plugin.ts
@@ -5,12 +5,17 @@
* 2.0.
*/
+import {
+ AppMountParameters,
+ AppUpdater,
+ CoreStart,
+ DEFAULT_APP_CATEGORIES,
+ PluginInitializerContext,
+} from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
-import { AppMountParameters, PluginInitializerContext } from '@kbn/core/public';
-import { from } from 'rxjs';
-import { map } from 'rxjs/operators';
-import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
import { enableInfrastructureHostsView } from '@kbn/observability-plugin/public';
+import { BehaviorSubject, combineLatest, from } from 'rxjs';
+import { map } from 'rxjs/operators';
import { defaultLogViewsStaticConfig } from '../common/log_views';
import { InfraPublicConfig } from '../common/plugin_config_types';
import { createInventoryMetricRuleType } from './alerting/inventory';
@@ -38,6 +43,7 @@ import { getLogsHasDataFetcher, getLogsOverviewDataFetcher } from './utils/logs_
export class Plugin implements InfraClientPluginClass {
public config: InfraPublicConfig;
private logViews: LogViewsService;
+ private readonly appUpdater$ = new BehaviorSubject(() => ({}));
constructor(context: PluginInitializerContext) {
this.config = context.config.get();
@@ -74,6 +80,11 @@ export class Plugin implements InfraClientPluginClass {
fetchData: createMetricsFetchData(core.getStartServices),
});
+ const startDep$AndHostViewFlag$ = combineLatest([
+ from(core.getStartServices()),
+ core.uiSettings.get$(enableInfrastructureHostsView),
+ ]);
+
/** !! Need to be kept in sync with the deepLinks in x-pack/plugins/infra/public/plugin.ts */
const infraEntries = [
{ label: 'Inventory', app: 'metrics', path: '/inventory' },
@@ -81,12 +92,15 @@ export class Plugin implements InfraClientPluginClass {
];
const hostInfraEntry = { label: 'Hosts', app: 'metrics', path: '/hosts' };
pluginsSetup.observability.navigation.registerSections(
- from(core.getStartServices()).pipe(
+ startDep$AndHostViewFlag$.pipe(
map(
([
- {
- application: { capabilities },
- },
+ [
+ {
+ application: { capabilities },
+ },
+ ],
+ isInfrastructureHostsViewEnabled,
]) => [
...(capabilities.logs.show
? [
@@ -106,7 +120,7 @@ export class Plugin implements InfraClientPluginClass {
{
label: 'Infrastructure',
sortKey: 300,
- entries: core.uiSettings.get(enableInfrastructureHostsView)
+ entries: isInfrastructureHostsViewEnabled
? [hostInfraEntry, ...infraEntries]
: infraEntries,
},
@@ -171,6 +185,7 @@ export class Plugin implements InfraClientPluginClass {
},
});
+ // !! Need to be kept in sync with the routes in x-pack/plugins/infra/public/pages/metrics/index.tsx
const infraDeepLinks = [
{
id: 'inventory',
@@ -210,8 +225,8 @@ export class Plugin implements InfraClientPluginClass {
order: 8200,
appRoute: '/app/metrics',
category: DEFAULT_APP_CATEGORIES.observability,
- // !! Need to be kept in sync with the routes in x-pack/plugins/infra/public/pages/metrics/index.tsx
- deepLinks: core.uiSettings.get(enableInfrastructureHostsView)
+ updater$: this.appUpdater$,
+ deepLinks: core.uiSettings.get(enableInfrastructureHostsView)
? [hostInfraDeepLink, ...infraDeepLinks]
: infraDeepLinks,
mount: async (params: AppMountParameters) => {
@@ -223,6 +238,19 @@ export class Plugin implements InfraClientPluginClass {
},
});
+ startDep$AndHostViewFlag$.subscribe(
+ ([_startServices, isInfrastructureHostsViewEnabled]: [
+ [CoreStart, InfraClientStartDeps, InfraClientStartExports],
+ boolean
+ ]) => {
+ this.appUpdater$.next(() => ({
+ deepLinks: isInfrastructureHostsViewEnabled
+ ? [hostInfraDeepLink, ...infraDeepLinks]
+ : infraDeepLinks,
+ }));
+ }
+ );
+
/* This exists purely to facilitate URL redirects from the old App ID ("infra"),
to our new App IDs ("metrics" and "logs"). With version 8.0.0 we can remove this. */
core.application.register({
From 0e6fa006c183ffdcd89ef25aacd6b4a04fdf2267 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Tue, 4 Oct 2022 12:19:22 +0100
Subject: [PATCH 04/27] [SavedObjectClient] Add support to exclude references
when searching (#142479)
---
docs/api/saved-objects/find.asciidoc | 9 +
.../src/lib/repository.test.ts | 4 +
.../src/lib/repository.ts | 4 +
.../src/lib/search_dsl/query_params.test.ts | 51 ++-
.../src/lib/search_dsl/query_params.ts | 33 +-
.../lib/search_dsl/references_filter.test.ts | 347 +++++++++++++-----
.../src/lib/search_dsl/references_filter.ts | 40 +-
.../src/lib/search_dsl/search_dsl.test.ts | 9 +-
.../src/lib/search_dsl/search_dsl.ts | 11 +-
.../src/apis/find.ts | 12 +
.../src/saved_objects_client.test.ts | 2 +
.../src/saved_objects_client.ts | 5 +
.../src/routes/find.ts | 6 +
.../saved_objects/routes/find.test.ts | 68 ++++
.../apis/saved_objects/find.ts | 125 +++++++
15 files changed, 601 insertions(+), 125 deletions(-)
diff --git a/docs/api/saved-objects/find.asciidoc b/docs/api/saved-objects/find.asciidoc
index 43c7f4cde8fa8..275bd1c21f9ed 100644
--- a/docs/api/saved-objects/find.asciidoc
+++ b/docs/api/saved-objects/find.asciidoc
@@ -52,6 +52,15 @@ experimental[] Retrieve a paginated set of {kib} saved objects by various condit
`has_reference`::
(Optional, object) Filters to objects that have a relationship with the type and ID combination.
+`has_reference_operator`::
+ (Optional, string) The operator to use for the `has_reference` parameter. Either `OR` or `AND`. Defaults to `OR`.
+
+`has_no_reference`::
+ (Optional, object) Filters to objects that do not have a relationship with the type and ID combination.
+
+`has_no_reference_operator`::
+ (Optional, string) The operator to use for the `has_no_reference` parameter. Either `OR` or `AND`. Defaults to `OR`.
+
`filter`::
(Optional, string) The filter is a KQL string with the caveat that if you filter with an attribute from your saved object type,
it should look like that: `savedObjectType.attributes.title: "myTitle"`. However, If you use a root attribute of a saved
diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts
index 0739c9acab8f5..d9a65f984c222 100644
--- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts
+++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts
@@ -4182,6 +4182,10 @@ describe('SavedObjectsRepository', () => {
type: 'foo',
id: '1',
},
+ hasNoReference: {
+ type: 'bar',
+ id: '1',
+ },
};
it(`passes mappings, registry, and search options to getSearchDsl`, async () => {
diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts
index 5569141c7fa0e..f48e031bd23c4 100644
--- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts
+++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts
@@ -1129,6 +1129,8 @@ export class SavedObjectsRepository implements ISavedObjectsRepository {
rootSearchFields,
hasReference,
hasReferenceOperator,
+ hasNoReference,
+ hasNoReferenceOperator,
page = FIND_DEFAULT_PAGE,
perPage = FIND_DEFAULT_PER_PAGE,
pit,
@@ -1235,6 +1237,8 @@ export class SavedObjectsRepository implements ISavedObjectsRepository {
typeToNamespacesMap,
hasReference,
hasReferenceOperator,
+ hasNoReference,
+ hasNoReferenceOperator,
kueryNode,
}),
},
diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.test.ts
index c502665468e6c..20ce3a2f46b2e 100644
--- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.test.ts
+++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.test.ts
@@ -195,17 +195,18 @@ describe('#getQueryParams', () => {
});
});
- describe('reference filter clause', () => {
- describe('`hasReference` parameter', () => {
- it('does not call `getReferencesFilter` when `hasReference` is not specified', () => {
- getQueryParams({
- registry,
- hasReference: undefined,
- });
-
- expect(getReferencesFilterMock).not.toHaveBeenCalled();
+ describe('reference/noreference filter clause', () => {
+ it('does not call `getReferencesFilter` when neither `hasReference` nor `hasNoReference` are specified', () => {
+ getQueryParams({
+ registry,
+ hasReference: undefined,
+ hasNoReference: undefined,
});
+ expect(getReferencesFilterMock).not.toHaveBeenCalled();
+ });
+
+ describe('`hasReference` parameter', () => {
it('calls `getReferencesFilter` with the correct parameters', () => {
const hasReference = { id: 'foo', type: 'bar' };
getQueryParams({
@@ -235,6 +236,38 @@ describe('#getQueryParams', () => {
expect(filters.some((filter) => filter.references_filter === true)).toBeDefined();
});
});
+
+ describe('`hasNoReference` parameter', () => {
+ it('calls `getReferencesFilter` with the correct parameters', () => {
+ const hasNoReference = { id: 'noFoo', type: 'bar' };
+ getQueryParams({
+ registry,
+ hasNoReference,
+ hasNoReferenceOperator: 'AND',
+ });
+
+ expect(getReferencesFilterMock).toHaveBeenCalledTimes(1);
+ expect(getReferencesFilterMock).toHaveBeenCalledWith({
+ must: false,
+ references: [hasNoReference],
+ operator: 'AND',
+ });
+ });
+
+ it('includes the return of `getReferencesFilter` in the `filter` clause', () => {
+ getReferencesFilterMock.mockReturnValue({ references_filter: true });
+
+ const hasNoReference = { id: 'noFoo', type: 'bar' };
+ const result = getQueryParams({
+ registry,
+ hasNoReference,
+ hasReferenceOperator: 'AND',
+ });
+
+ const filters: any[] = result.query.bool.filter;
+ expect(filters.some((filter) => filter.references_filter === true)).toBeDefined();
+ });
+ });
});
describe('type filter clauses', () => {
diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts
index 669f2a273569b..896b934c90b80 100644
--- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts
+++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts
@@ -7,6 +7,7 @@
*/
import * as esKuery from '@kbn/es-query';
+import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common';
type KueryNode = any;
@@ -123,11 +124,6 @@ function getClauseForType(
};
}
-export interface HasReferenceQueryParams {
- type: string;
- id: string;
-}
-
export type SearchOperator = 'AND' | 'OR';
interface QueryParams {
@@ -139,8 +135,10 @@ interface QueryParams {
defaultSearchOperator?: SearchOperator;
searchFields?: string[];
rootSearchFields?: string[];
- hasReference?: HasReferenceQueryParams | HasReferenceQueryParams[];
+ hasReference?: SavedObjectTypeIdTuple | SavedObjectTypeIdTuple[];
hasReferenceOperator?: SearchOperator;
+ hasNoReference?: SavedObjectTypeIdTuple | SavedObjectTypeIdTuple[];
+ hasNoReferenceOperator?: SearchOperator;
kueryNode?: KueryNode;
}
@@ -148,6 +146,13 @@ interface QueryParams {
const uniqNamespaces = (namespacesToNormalize?: string[]) =>
namespacesToNormalize ? Array.from(new Set(namespacesToNormalize)) : undefined;
+const toArray = (val: unknown) => {
+ if (typeof val === 'undefined') {
+ return val;
+ }
+ return !Array.isArray(val) ? [val] : val;
+};
+
/**
* Get the "query" related keys for the search body
*/
@@ -162,6 +167,8 @@ export function getQueryParams({
defaultSearchOperator,
hasReference,
hasReferenceOperator,
+ hasNoReference,
+ hasNoReferenceOperator,
kueryNode,
}: QueryParams) {
const types = getTypes(
@@ -169,9 +176,8 @@ export function getQueryParams({
typeToNamespacesMap ? Array.from(typeToNamespacesMap.keys()) : type
);
- if (hasReference && !Array.isArray(hasReference)) {
- hasReference = [hasReference];
- }
+ hasReference = toArray(hasReference);
+ hasNoReference = toArray(hasNoReference);
const bool: any = {
filter: [
@@ -184,6 +190,15 @@ export function getQueryParams({
}),
]
: []),
+ ...(hasNoReference?.length
+ ? [
+ getReferencesFilter({
+ references: hasNoReference,
+ operator: hasNoReferenceOperator,
+ must: false,
+ }),
+ ]
+ : []),
{
bool: {
should: types.map((shouldType) => {
diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.test.ts
index 9a042579c8e8f..127f3a94edd21 100644
--- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.test.ts
+++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.test.ts
@@ -20,29 +20,141 @@ describe('getReferencesFilter', () => {
},
});
- describe('when using the `OR` operator', () => {
- it('generates one `should` clause per type of reference', () => {
+ describe('for "must" match clauses', () => {
+ describe('when using the `OR` operator', () => {
+ it('generates one `should` clause per type of reference', () => {
+ const references = [
+ { type: 'foo', id: 'foo-1' },
+ { type: 'foo', id: 'foo-2' },
+ { type: 'foo', id: 'foo-3' },
+ { type: 'bar', id: 'bar-1' },
+ { type: 'bar', id: 'bar-2' },
+ ];
+ const clause = getReferencesFilter({
+ references,
+ operator: 'OR',
+ });
+
+ expect(clause).toEqual({
+ bool: {
+ should: [
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['foo-1', 'foo-2', 'foo-3'] } },
+ { term: { 'references.type': 'foo' } },
+ ]),
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['bar-1', 'bar-2'] } },
+ { term: { 'references.type': 'bar' } },
+ ]),
+ ],
+ minimum_should_match: 1,
+ },
+ });
+ });
+
+ it('does not include more than `maxTermsPerClause` per `terms` clauses', () => {
+ const references = [
+ { type: 'foo', id: 'foo-1' },
+ { type: 'foo', id: 'foo-2' },
+ { type: 'foo', id: 'foo-3' },
+ { type: 'foo', id: 'foo-4' },
+ { type: 'foo', id: 'foo-5' },
+ { type: 'bar', id: 'bar-1' },
+ { type: 'bar', id: 'bar-2' },
+ { type: 'bar', id: 'bar-3' },
+ { type: 'dolly', id: 'dolly-1' },
+ ];
+ const clause = getReferencesFilter({
+ references,
+ operator: 'OR',
+ maxTermsPerClause: 2,
+ });
+
+ expect(clause).toEqual({
+ bool: {
+ should: [
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['foo-1', 'foo-2'] } },
+ { term: { 'references.type': 'foo' } },
+ ]),
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['foo-3', 'foo-4'] } },
+ { term: { 'references.type': 'foo' } },
+ ]),
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['foo-5'] } },
+ { term: { 'references.type': 'foo' } },
+ ]),
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['bar-1', 'bar-2'] } },
+ { term: { 'references.type': 'bar' } },
+ ]),
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['bar-3'] } },
+ { term: { 'references.type': 'bar' } },
+ ]),
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['dolly-1'] } },
+ { term: { 'references.type': 'dolly' } },
+ ]),
+ ],
+ minimum_should_match: 1,
+ },
+ });
+ });
+ });
+
+ describe('when using the `AND` operator', () => {
+ it('generates one `must` clause per reference', () => {
+ const references = [
+ { type: 'foo', id: 'foo-1' },
+ { type: 'foo', id: 'foo-2' },
+ { type: 'bar', id: 'bar-1' },
+ ];
+
+ const clause = getReferencesFilter({
+ references,
+ operator: 'AND',
+ });
+
+ expect(clause).toEqual({
+ bool: {
+ must: references.map((ref) => ({
+ nested: {
+ path: 'references',
+ query: {
+ bool: {
+ must: [
+ { term: { 'references.id': ref.id } },
+ { term: { 'references.type': ref.type } },
+ ],
+ },
+ },
+ },
+ })),
+ },
+ });
+ });
+ });
+
+ it('defaults to using the `OR` operator', () => {
const references = [
{ type: 'foo', id: 'foo-1' },
- { type: 'foo', id: 'foo-2' },
- { type: 'foo', id: 'foo-3' },
{ type: 'bar', id: 'bar-1' },
- { type: 'bar', id: 'bar-2' },
];
const clause = getReferencesFilter({
references,
- operator: 'OR',
});
expect(clause).toEqual({
bool: {
should: [
nestedRefMustClauses([
- { terms: { 'references.id': ['foo-1', 'foo-2', 'foo-3'] } },
+ { terms: { 'references.id': ['foo-1'] } },
{ term: { 'references.type': 'foo' } },
]),
nestedRefMustClauses([
- { terms: { 'references.id': ['bar-1', 'bar-2'] } },
+ { terms: { 'references.id': ['bar-1'] } },
{ term: { 'references.type': 'bar' } },
]),
],
@@ -50,115 +162,156 @@ describe('getReferencesFilter', () => {
},
});
});
+ });
+
+ describe('for "must_not" match clauses', () => {
+ describe('when using the `OR` operator', () => {
+ it('generates one `must_not` clause per type of reference', () => {
+ const references = [
+ { type: 'foo', id: 'foo-1' },
+ { type: 'foo', id: 'foo-2' },
+ { type: 'foo', id: 'foo-3' },
+ { type: 'bar', id: 'bar-1' },
+ { type: 'bar', id: 'bar-2' },
+ ];
+ const clause = getReferencesFilter({
+ references,
+ operator: 'OR',
+ must: false,
+ });
+
+ expect(clause).toEqual({
+ bool: {
+ must_not: [
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['foo-1', 'foo-2', 'foo-3'] } },
+ { term: { 'references.type': 'foo' } },
+ ]),
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['bar-1', 'bar-2'] } },
+ { term: { 'references.type': 'bar' } },
+ ]),
+ ],
+ },
+ });
+ });
+
+ it('does not include more than `maxTermsPerClause` per `terms` clauses', () => {
+ const references = [
+ { type: 'foo', id: 'foo-1' },
+ { type: 'foo', id: 'foo-2' },
+ { type: 'foo', id: 'foo-3' },
+ { type: 'foo', id: 'foo-4' },
+ { type: 'foo', id: 'foo-5' },
+ { type: 'bar', id: 'bar-1' },
+ { type: 'bar', id: 'bar-2' },
+ { type: 'bar', id: 'bar-3' },
+ { type: 'dolly', id: 'dolly-1' },
+ ];
+ const clause = getReferencesFilter({
+ references,
+ operator: 'OR',
+ maxTermsPerClause: 2,
+ must: false,
+ });
+
+ expect(clause).toEqual({
+ bool: {
+ must_not: [
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['foo-1', 'foo-2'] } },
+ { term: { 'references.type': 'foo' } },
+ ]),
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['foo-3', 'foo-4'] } },
+ { term: { 'references.type': 'foo' } },
+ ]),
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['foo-5'] } },
+ { term: { 'references.type': 'foo' } },
+ ]),
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['bar-1', 'bar-2'] } },
+ { term: { 'references.type': 'bar' } },
+ ]),
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['bar-3'] } },
+ { term: { 'references.type': 'bar' } },
+ ]),
+ nestedRefMustClauses([
+ { terms: { 'references.id': ['dolly-1'] } },
+ { term: { 'references.type': 'dolly' } },
+ ]),
+ ],
+ },
+ });
+ });
+ });
- it('does not include mode than `maxTermsPerClause` per `terms` clauses', () => {
+ describe('when using the `AND` operator', () => {
+ it('generates one `must` clause per reference', () => {
+ const references = [
+ { type: 'foo', id: 'foo-1' },
+ { type: 'foo', id: 'foo-2' },
+ { type: 'bar', id: 'bar-1' },
+ ];
+
+ const clause = getReferencesFilter({
+ references,
+ operator: 'AND',
+ must: false,
+ });
+
+ expect(clause).toEqual({
+ bool: {
+ must_not: [
+ {
+ bool: {
+ must: references.map((ref) => ({
+ nested: {
+ path: 'references',
+ query: {
+ bool: {
+ must: [
+ { term: { 'references.id': ref.id } },
+ { term: { 'references.type': ref.type } },
+ ],
+ },
+ },
+ },
+ })),
+ },
+ },
+ ],
+ },
+ });
+ });
+ });
+
+ it('defaults to using the `OR` operator', () => {
const references = [
{ type: 'foo', id: 'foo-1' },
- { type: 'foo', id: 'foo-2' },
- { type: 'foo', id: 'foo-3' },
- { type: 'foo', id: 'foo-4' },
- { type: 'foo', id: 'foo-5' },
{ type: 'bar', id: 'bar-1' },
- { type: 'bar', id: 'bar-2' },
- { type: 'bar', id: 'bar-3' },
- { type: 'dolly', id: 'dolly-1' },
];
const clause = getReferencesFilter({
references,
- operator: 'OR',
- maxTermsPerClause: 2,
+ must: false,
});
expect(clause).toEqual({
bool: {
- should: [
- nestedRefMustClauses([
- { terms: { 'references.id': ['foo-1', 'foo-2'] } },
- { term: { 'references.type': 'foo' } },
- ]),
+ must_not: [
nestedRefMustClauses([
- { terms: { 'references.id': ['foo-3', 'foo-4'] } },
+ { terms: { 'references.id': ['foo-1'] } },
{ term: { 'references.type': 'foo' } },
]),
nestedRefMustClauses([
- { terms: { 'references.id': ['foo-5'] } },
- { term: { 'references.type': 'foo' } },
- ]),
- nestedRefMustClauses([
- { terms: { 'references.id': ['bar-1', 'bar-2'] } },
+ { terms: { 'references.id': ['bar-1'] } },
{ term: { 'references.type': 'bar' } },
]),
- nestedRefMustClauses([
- { terms: { 'references.id': ['bar-3'] } },
- { term: { 'references.type': 'bar' } },
- ]),
- nestedRefMustClauses([
- { terms: { 'references.id': ['dolly-1'] } },
- { term: { 'references.type': 'dolly' } },
- ]),
],
- minimum_should_match: 1,
- },
- });
- });
- });
-
- describe('when using the `AND` operator', () => {
- it('generates one `must` clause per reference', () => {
- const references = [
- { type: 'foo', id: 'foo-1' },
- { type: 'foo', id: 'foo-2' },
- { type: 'bar', id: 'bar-1' },
- ];
-
- const clause = getReferencesFilter({
- references,
- operator: 'AND',
- });
-
- expect(clause).toEqual({
- bool: {
- must: references.map((ref) => ({
- nested: {
- path: 'references',
- query: {
- bool: {
- must: [
- { term: { 'references.id': ref.id } },
- { term: { 'references.type': ref.type } },
- ],
- },
- },
- },
- })),
},
});
});
});
-
- it('defaults to using the `OR` operator', () => {
- const references = [
- { type: 'foo', id: 'foo-1' },
- { type: 'bar', id: 'bar-1' },
- ];
- const clause = getReferencesFilter({
- references,
- });
-
- expect(clause).toEqual({
- bool: {
- should: [
- nestedRefMustClauses([
- { terms: { 'references.id': ['foo-1'] } },
- { term: { 'references.type': 'foo' } },
- ]),
- nestedRefMustClauses([
- { terms: { 'references.id': ['bar-1'] } },
- { term: { 'references.type': 'bar' } },
- ]),
- ],
- minimum_should_match: 1,
- },
- });
- });
});
diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts
index b0849560d2e43..4dd6bc640f174 100644
--- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts
+++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts
@@ -6,35 +6,61 @@
* Side Public License, v 1.
*/
-import type { HasReferenceQueryParams, SearchOperator } from './query_params';
+import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common';
+
+import type { SearchOperator } from './query_params';
export function getReferencesFilter({
references,
operator = 'OR',
maxTermsPerClause = 1000,
+ must = true,
}: {
- references: HasReferenceQueryParams[];
+ references: SavedObjectTypeIdTuple[];
operator?: SearchOperator;
maxTermsPerClause?: number;
+ must?: boolean;
}) {
if (operator === 'AND') {
+ if (must) {
+ return {
+ bool: {
+ must: references.map(getNestedTermClauseForReference),
+ },
+ };
+ }
+
return {
bool: {
- must: references.map(getNestedTermClauseForReference),
+ must_not: [
+ {
+ bool: {
+ must: references.map(getNestedTermClauseForReference),
+ },
+ },
+ ],
},
};
} else {
+ if (must) {
+ return {
+ bool: {
+ should: getAggregatedTermsClauses(references, maxTermsPerClause),
+ minimum_should_match: 1,
+ },
+ };
+ }
+
return {
bool: {
- should: getAggregatedTermsClauses(references, maxTermsPerClause),
- minimum_should_match: 1,
+ must_not: getAggregatedTermsClauses(references, maxTermsPerClause),
},
};
}
}
const getAggregatedTermsClauses = (
- references: HasReferenceQueryParams[],
+ references: SavedObjectTypeIdTuple[],
maxTermsPerClause: number
) => {
const refTypeToIds = references.reduce((map, { type, id }) => {
@@ -58,7 +84,7 @@ const createChunks = (array: T[], chunkSize: number): T[][] => {
return chunks;
};
-export const getNestedTermClauseForReference = (reference: HasReferenceQueryParams) => {
+export const getNestedTermClauseForReference = (reference: SavedObjectTypeIdTuple) => {
return {
nested: {
path: 'references',
diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.test.ts
index d1ed7251b2414..84ef7c232d775 100644
--- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.test.ts
+++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.test.ts
@@ -49,7 +49,7 @@ describe('getSearchDsl', () => {
});
describe('passes control', () => {
- it('passes (mappings, schema, namespaces, type, typeToNamespacesMap, search, searchFields, rootSearchFields, hasReference, hasReferenceOperator) to getQueryParams', () => {
+ it('passes (mappings, schema, namespaces, type, typeToNamespacesMap, search, searchFields, rootSearchFields, hasReference, hasReferenceOperator, hasNoReference, hasNoReferenceOperator) to getQueryParams', () => {
const opts = {
namespaces: ['foo-namespace'],
type: 'foo',
@@ -63,6 +63,11 @@ describe('getSearchDsl', () => {
id: '1',
},
hasReferenceOperator: 'AND' as queryParamsNS.SearchOperator,
+ hasNoReference: {
+ type: 'noBar',
+ id: '1',
+ },
+ hasNoReferenceOperator: 'AND' as queryParamsNS.SearchOperator,
};
getSearchDsl(mappings, registry, opts);
@@ -78,6 +83,8 @@ describe('getSearchDsl', () => {
defaultSearchOperator: opts.defaultSearchOperator,
hasReference: opts.hasReference,
hasReferenceOperator: opts.hasReferenceOperator,
+ hasNoReference: opts.hasNoReference,
+ hasNoReferenceOperator: opts.hasNoReferenceOperator,
});
});
diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts
index 980bf800755b9..381f20069d25a 100644
--- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts
+++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts
@@ -12,7 +12,8 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { SavedObjectsPitParams } from '@kbn/core-saved-objects-api-server';
import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server';
import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal';
-import { getQueryParams, HasReferenceQueryParams, SearchOperator } from './query_params';
+import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common';
+import { getQueryParams, SearchOperator } from './query_params';
import { getPitParams } from './pit_params';
import { getSortingParams } from './sorting_params';
@@ -30,8 +31,10 @@ interface GetSearchDslOptions {
namespaces?: string[];
pit?: SavedObjectsPitParams;
typeToNamespacesMap?: Map;
- hasReference?: HasReferenceQueryParams | HasReferenceQueryParams[];
+ hasReference?: SavedObjectTypeIdTuple | SavedObjectTypeIdTuple[];
hasReferenceOperator?: SearchOperator;
+ hasNoReference?: SavedObjectTypeIdTuple | SavedObjectTypeIdTuple[];
+ hasNoReferenceOperator?: SearchOperator;
kueryNode?: KueryNode;
}
@@ -54,6 +57,8 @@ export function getSearchDsl(
typeToNamespacesMap,
hasReference,
hasReferenceOperator,
+ hasNoReference,
+ hasNoReferenceOperator,
kueryNode,
} = options;
@@ -77,6 +82,8 @@ export function getSearchDsl(
defaultSearchOperator,
hasReference,
hasReferenceOperator,
+ hasNoReference,
+ hasNoReferenceOperator,
kueryNode,
}),
...getSortingParams(mappings, type, sortField, sortOrder),
diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts
index 49042029f334b..a50506c96c8e5 100644
--- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts
+++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts
@@ -66,11 +66,23 @@ export interface SavedObjectsFindOptions {
* Use `hasReferenceOperator` to specify the operator to use when searching for multiple references.
*/
hasReference?: SavedObjectsFindOptionsReference | SavedObjectsFindOptionsReference[];
+
/**
* The operator to use when searching by multiple references using the `hasReference` option. Defaults to `OR`
*/
hasReferenceOperator?: 'AND' | 'OR';
+ /**
+ * Search for documents *not* having a reference to the specified objects.
+ * Use `hasNoReferenceOperator` to specify the operator to use when searching for multiple references.
+ */
+ hasNoReference?: SavedObjectsFindOptionsReference | SavedObjectsFindOptionsReference[];
+
+ /**
+ * The operator to use when searching by multiple references using the `hasNoReference` option. Defaults to `OR`
+ */
+ hasNoReferenceOperator?: 'AND' | 'OR';
+
/**
* The search operator to use with the provided filter. Defaults to `OR`
*/
diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts
index 6c2966ee9775f..7825b09cf29bd 100644
--- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts
+++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts
@@ -612,6 +612,7 @@ describe('SavedObjectsClient', () => {
defaultSearchOperator: 'OR' as const,
fields: ['title'],
hasReference: { id: '1', type: 'reference' },
+ hasNoReference: { id: '1', type: 'reference' },
page: 10,
perPage: 100,
search: 'what is the meaning of life?|life',
@@ -633,6 +634,7 @@ describe('SavedObjectsClient', () => {
"fields": Array [
"title",
],
+ "has_no_reference": "{\\"id\\":\\"1\\",\\"type\\":\\"reference\\"}",
"has_reference": "{\\"id\\":\\"1\\",\\"type\\":\\"reference\\"}",
"page": 10,
"per_page": 100,
diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts
index dd2feed58123f..1fd111186f551 100644
--- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts
+++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts
@@ -292,6 +292,8 @@ export class SavedObjectsClient implements SavedObjectsClientContract {
fields: 'fields',
hasReference: 'has_reference',
hasReferenceOperator: 'has_reference_operator',
+ hasNoReference: 'has_no_reference',
+ hasNoReferenceOperator: 'has_no_reference_operator',
page: 'page',
perPage: 'per_page',
search: 'search',
@@ -315,6 +317,9 @@ export class SavedObjectsClient implements SavedObjectsClientContract {
if (query.has_reference) {
query.has_reference = JSON.stringify(query.has_reference);
}
+ if (query.has_no_reference) {
+ query.has_no_reference = JSON.stringify(query.has_no_reference);
+ }
// `aggs` is a structured object. we need to stringify it before sending it, as `fetch`
// is not doing it implicitly.
diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts
index 4587cb1ebeb09..983b31caf7a2b 100644
--- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts
+++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts
@@ -45,6 +45,10 @@ export const registerFindRoute = (
schema.oneOf([referenceSchema, schema.arrayOf(referenceSchema)])
),
has_reference_operator: searchOperatorSchema,
+ has_no_reference: schema.maybe(
+ schema.oneOf([referenceSchema, schema.arrayOf(referenceSchema)])
+ ),
+ has_no_reference_operator: searchOperatorSchema,
fields: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])),
filter: schema.maybe(schema.string()),
aggs: schema.maybe(schema.string()),
@@ -88,6 +92,8 @@ export const registerFindRoute = (
sortField: query.sort_field,
hasReference: query.has_reference,
hasReferenceOperator: query.has_reference_operator,
+ hasNoReference: query.has_no_reference,
+ hasNoReferenceOperator: query.has_no_reference_operator,
fields: typeof query.fields === 'string' ? [query.fields] : query.fields,
filter: query.filter,
aggs,
diff --git a/src/core/server/integration_tests/saved_objects/routes/find.test.ts b/src/core/server/integration_tests/saved_objects/routes/find.test.ts
index ab3ca6c459dae..2c7b1c9838b50 100644
--- a/src/core/server/integration_tests/saved_objects/routes/find.test.ts
+++ b/src/core/server/integration_tests/saved_objects/routes/find.test.ts
@@ -123,6 +123,7 @@ describe('GET /api/saved_objects/_find', () => {
type: ['foo', 'bar'],
defaultSearchOperator: 'OR',
hasReferenceOperator: 'OR',
+ hasNoReferenceOperator: 'OR',
});
});
@@ -213,6 +214,73 @@ describe('GET /api/saved_objects/_find', () => {
);
});
+ it('accepts the query parameter has_no_reference as an object', async () => {
+ const references = querystring.escape(
+ JSON.stringify({
+ id: '1',
+ type: 'reference',
+ })
+ );
+ await supertest(httpSetup.server.listener)
+ .get(`/api/saved_objects/_find?type=foo&has_no_reference=${references}`)
+ .expect(200);
+
+ expect(savedObjectsClient.find).toHaveBeenCalledTimes(1);
+
+ const options = savedObjectsClient.find.mock.calls[0][0];
+ expect(options.hasNoReference).toEqual({
+ id: '1',
+ type: 'reference',
+ });
+ });
+
+ it('accepts the query parameter has_no_reference as an array', async () => {
+ const references = querystring.escape(
+ JSON.stringify([
+ {
+ id: '1',
+ type: 'reference',
+ },
+ {
+ id: '2',
+ type: 'reference',
+ },
+ ])
+ );
+ await supertest(httpSetup.server.listener)
+ .get(`/api/saved_objects/_find?type=foo&has_no_reference=${references}`)
+ .expect(200);
+
+ expect(savedObjectsClient.find).toHaveBeenCalledTimes(1);
+
+ const options = savedObjectsClient.find.mock.calls[0][0];
+ expect(options.hasNoReference).toEqual([
+ {
+ id: '1',
+ type: 'reference',
+ },
+ {
+ id: '2',
+ type: 'reference',
+ },
+ ]);
+ });
+
+ it('accepts the query parameter has_no_reference_operator', async () => {
+ await supertest(httpSetup.server.listener)
+ .get('/api/saved_objects/_find?type=foo&has_no_reference_operator=AND')
+ .expect(200);
+
+ expect(savedObjectsClient.find).toHaveBeenCalledTimes(1);
+
+ const options = savedObjectsClient.find.mock.calls[0][0];
+ expect(options).toEqual(
+ expect.objectContaining({
+ hasNoReferenceOperator: 'AND',
+ })
+ );
+ });
+
it('accepts the query parameter search_fields', async () => {
await supertest(httpSetup.server.listener)
.get('/api/saved_objects/_find?type=foo&search_fields=title')
diff --git a/test/api_integration/apis/saved_objects/find.ts b/test/api_integration/apis/saved_objects/find.ts
index 4afcc4f162a62..5c11b6f74d7ae 100644
--- a/test/api_integration/apis/saved_objects/find.ts
+++ b/test/api_integration/apis/saved_objects/find.ts
@@ -338,6 +338,131 @@ export default function ({ getService }: FtrProviderContext) {
});
});
+ describe('`has_no_reference` and `has_no_reference_operator` parameters', () => {
+ before(async () => {
+ await kibanaServer.importExport.load(
+ 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json',
+ { space: SPACE_ID }
+ );
+ });
+ after(async () => {
+ await kibanaServer.importExport.unload(
+ 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json',
+ { space: SPACE_ID }
+ );
+ });
+
+ it('search for objects not containing a reference', async () => {
+ await supertest
+ .get(`/s/${SPACE_ID}/api/saved_objects/_find`)
+ .query({
+ type: 'visualization',
+ has_no_reference: JSON.stringify({ type: 'ref-type', id: 'ref-1' }),
+ })
+ .expect(200)
+ .then((resp) => {
+ const objects = resp.body.saved_objects;
+ const ids = objects.map((obj: SavedObject) => obj.id);
+ expect(ids).to.contain('only-ref-2');
+ expect(ids).to.contain('only-ref-3');
+ expect(ids).not.to.contain('only-ref-1');
+ expect(ids).not.to.contain('ref-1-and-ref-2');
+ });
+ });
+
+ it('search for multiple references with OR operator', async () => {
+ await supertest
+ .get(`/s/${SPACE_ID}/api/saved_objects/_find`)
+ .query({
+ type: 'visualization',
+ has_no_reference: JSON.stringify([
+ { type: 'ref-type', id: 'ref-1' },
+ { type: 'ref-type', id: 'ref-2' },
+ ]),
+ has_no_reference_operator: 'OR',
+ })
+ .expect(200)
+ .then((resp) => {
+ const objects = resp.body.saved_objects;
+ const ids = objects.map((obj: SavedObject) => obj.id);
+
+ expect(ids).to.contain('only-ref-3');
+ expect(ids).not.to.contain('only-ref-1');
+ expect(ids).not.to.contain('only-ref-2');
+ expect(ids).not.to.contain('ref-1-and-ref-2');
+ });
+ });
+
+ it('search for multiple references with AND operator', async () => {
+ await supertest
+ .get(`/s/${SPACE_ID}/api/saved_objects/_find`)
+ .query({
+ type: 'visualization',
+ has_no_reference: JSON.stringify([
+ { type: 'ref-type', id: 'ref-1' },
+ { type: 'ref-type', id: 'ref-2' },
+ ]),
+ has_no_reference_operator: 'AND',
+ })
+ .expect(200)
+ .then((resp) => {
+ const objects = resp.body.saved_objects;
+ const ids = objects.map((obj: SavedObject) => obj.id);
+ expect(ids).to.contain('only-ref-1');
+ expect(ids).to.contain('only-ref-2');
+ expect(ids).to.contain('only-ref-3');
+ expect(ids).not.to.contain('ref-1-and-ref-2');
+ });
+ });
+ });
+
+ describe('with both `has_reference` and `has_no_reference` parameters', () => {
+ before(async () => {
+ await kibanaServer.importExport.load(
+ 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json',
+ { space: SPACE_ID }
+ );
+ });
+ after(async () => {
+ await kibanaServer.importExport.unload(
+ 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json',
+ { space: SPACE_ID }
+ );
+ });
+
+ it('search for objects containing a reference and excluding another reference', async () => {
+ await supertest
+ .get(`/s/${SPACE_ID}/api/saved_objects/_find`)
+ .query({
+ type: 'visualization',
+ has_reference: JSON.stringify({ type: 'ref-type', id: 'ref-1' }),
+ has_no_reference: JSON.stringify({ type: 'ref-type', id: 'ref-2' }),
+ })
+ .expect(200)
+ .then((resp) => {
+ const objects = resp.body.saved_objects;
+ const ids = objects.map((obj: SavedObject) => obj.id);
+ expect(ids).to.eql(['only-ref-1']);
+ });
+ });
+
+ it('search for objects with same reference passed to `has_reference` and `has_no_reference`', async () => {
+ await supertest
+ .get(`/s/${SPACE_ID}/api/saved_objects/_find`)
+ .query({
+ type: 'visualization',
+ has_reference: JSON.stringify({ type: 'ref-type', id: 'ref-1' }),
+ has_no_reference: JSON.stringify({ type: 'ref-type', id: 'ref-1' }),
+ })
+ .expect(200)
+ .then((resp) => {
+ const objects = resp.body.saved_objects;
+ const ids = objects.map((obj: SavedObject) => obj.id);
+ expect(ids).to.eql([]);
+ });
+ });
+ });
+
describe('searching for special characters', () => {
before(async () => {
await kibanaServer.importExport.load(
From b66d12a40c5aed0335d76b417c73cb3bb946764b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?=
Date: Tue, 4 Oct 2022 13:26:50 +0200
Subject: [PATCH 05/27] [Enterprise Search] Add ml doc links (#141921)
* Add documentation links for ml inference card and modal
* Fix link
---
packages/kbn-doc-links/src/get_doc_links.ts | 1 +
packages/kbn-doc-links/src/types.ts | 1 +
.../pipelines/ml_inference/configure_pipeline.tsx | 8 +++-----
.../search_index/pipelines/ml_inference/no_models.tsx | 5 +++--
.../components/search_index/pipelines/pipelines.tsx | 2 +-
.../public/applications/shared/doc_links/doc_links.ts | 3 +++
6 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts
index 8ef5a68a3f98c..445bf9458d457 100644
--- a/packages/kbn-doc-links/src/get_doc_links.ts
+++ b/packages/kbn-doc-links/src/get_doc_links.ts
@@ -130,6 +130,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => {
crawlerGettingStarted: `${ENTERPRISE_SEARCH_DOCS}crawler-getting-started.html`,
crawlerManaging: `${ENTERPRISE_SEARCH_DOCS}crawler-managing.html`,
crawlerOverview: `${ENTERPRISE_SEARCH_DOCS}crawler.html`,
+ deployTrainedModels: `${MACHINE_LEARNING_DOCS}ml-nlp-deploy-models.html`,
documentLevelSecurity: `${ELASTICSEARCH_DOCS}document-level-security.html`,
ingestPipelines: `${ENTERPRISE_SEARCH_DOCS}ingest-pipelines.html`,
languageAnalyzers: `${ELASTICSEARCH_DOCS}analysis-lang-analyzer.html`,
diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts
index aed1b552bdb30..d9902a7b11de3 100644
--- a/packages/kbn-doc-links/src/types.ts
+++ b/packages/kbn-doc-links/src/types.ts
@@ -115,6 +115,7 @@ export interface DocLinks {
readonly crawlerGettingStarted: string;
readonly crawlerManaging: string;
readonly crawlerOverview: string;
+ readonly deployTrainedModels: string;
readonly documentLevelSecurity: string;
readonly ingestPipelines: string;
readonly languageAnalyzers: string;
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx
index b1d8fd4d074a8..bd895dcf45704 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx
@@ -23,6 +23,8 @@ import {
import { i18n } from '@kbn/i18n';
+import { docLinks } from '../../../../../shared/doc_links';
+
import { MLInferenceLogic } from './ml_inference_logic';
export const ConfigurePipeline: React.FC = () => {
@@ -50,11 +52,7 @@ export const ConfigurePipeline: React.FC = () => {
}
)}
-
+
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.docsLink',
{
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx
index 084fb4244cb7a..66ffbe45c1777 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx
@@ -14,6 +14,8 @@ import { i18n } from '@kbn/i18n';
import noMlModelsGraphicDark from '../../../../../../assets/images/no_ml_models_dark.svg';
import noMlModelsGraphicLight from '../../../../../../assets/images/no_ml_models_light.svg';
+import { docLinks } from '../../../../../shared/doc_links';
+
export const NoModelsPanel: React.FC = () => {
const { colorMode } = useEuiTheme();
@@ -43,8 +45,7 @@ export const NoModelsPanel: React.FC = () => {
>
}
footer={
- // TODO: insert correct docsLink here
-
+
{i18n.translate(
'xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.emptyPrompt.docsLink',
{
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx
index 07be63b54f3b5..9cab24190a2de 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx
@@ -87,7 +87,7 @@ export const SearchIndexPipelines: React.FC = () => {
+
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.mlInferencePipelines.docLink',
{
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts
index 17ee2230b2fb7..975e7981829f2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts
@@ -65,6 +65,7 @@ class DocLinks {
public crawlerGettingStarted: string;
public crawlerManaging: string;
public crawlerOverview: string;
+ public deployTrainedModels: string;
public documentLevelSecurity: string;
public elasticsearchCreateIndex: string;
public elasticsearchGettingStarted: string;
@@ -178,6 +179,7 @@ class DocLinks {
this.crawlerGettingStarted = '';
this.crawlerManaging = '';
this.crawlerOverview = '';
+ this.deployTrainedModels = '';
this.documentLevelSecurity = '';
this.elasticsearchCreateIndex = '';
this.elasticsearchGettingStarted = '';
@@ -293,6 +295,7 @@ class DocLinks {
this.crawlerGettingStarted = docLinks.links.enterpriseSearch.crawlerGettingStarted;
this.crawlerManaging = docLinks.links.enterpriseSearch.crawlerManaging;
this.crawlerOverview = docLinks.links.enterpriseSearch.crawlerOverview;
+ this.deployTrainedModels = docLinks.links.enterpriseSearch.deployTrainedModels;
this.documentLevelSecurity = docLinks.links.enterpriseSearch.documentLevelSecurity;
this.elasticsearchCreateIndex = docLinks.links.elasticsearch.createIndex;
this.elasticsearchGettingStarted = docLinks.links.elasticsearch.gettingStarted;
From 4753d7c170ea47e3bc178c2b48fd6507a150594d Mon Sep 17 00:00:00 2001
From: Walter Rafelsberger
Date: Tue, 4 Oct 2022 13:50:55 +0200
Subject: [PATCH 06/27] [ML] Explain Log Rate Spikes: Fix error handling.
(#142047)
- Fixes error handling that before was not providing enough information for debugging purposes and support. This will now output more fine grained error information to the Kibana server log. The analysis is now more resilient to errors for individual queries. For example, we don't stop the analysis anymore if individual queries for p-values or histograms fail.
- Moves the error callout above all other possible elements like empty prompts when the analysis doesn't return results.
---
.../api/explain_log_rate_spikes/actions.ts | 10 +
.../api/explain_log_rate_spikes/index.ts | 1 +
.../explain_log_rate_spikes_analysis.tsx | 54 +-
.../server/routes/explain_log_rate_spikes.ts | 556 +++++++++++-------
.../queries/fetch_change_point_p_values.ts | 16 +-
.../routes/queries/fetch_frequent_items.ts | 23 +-
.../apis/aiops/explain_log_rate_spikes.ts | 4 +-
7 files changed, 405 insertions(+), 259 deletions(-)
diff --git a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts
index e050946a489be..7c4e3a47f8b79 100644
--- a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts
+++ b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts
@@ -18,6 +18,7 @@ export const API_ACTION_NAME = {
ADD_CHANGE_POINTS_GROUP: 'add_change_point_group',
ADD_CHANGE_POINTS_GROUP_HISTOGRAM: 'add_change_point_group_histogram',
ADD_ERROR: 'add_error',
+ PING: 'ping',
RESET: 'reset',
UPDATE_LOADING_STATE: 'update_loading_state',
} as const;
@@ -89,6 +90,14 @@ export function addErrorAction(payload: ApiActionAddError['payload']): ApiAction
};
}
+interface ApiActionPing {
+ type: typeof API_ACTION_NAME.PING;
+}
+
+export function pingAction(): ApiActionPing {
+ return { type: API_ACTION_NAME.PING };
+}
+
interface ApiActionReset {
type: typeof API_ACTION_NAME.RESET;
}
@@ -121,5 +130,6 @@ export type AiopsExplainLogRateSpikesApiAction =
| ApiActionAddChangePointsHistogram
| ApiActionAddChangePointsGroupHistogram
| ApiActionAddError
+ | ApiActionPing
| ApiActionReset
| ApiActionUpdateLoadingState;
diff --git a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts
index 5628b509980ad..c092b34c8b2b6 100644
--- a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts
+++ b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts
@@ -11,6 +11,7 @@ export {
addChangePointsGroupHistogramAction,
addChangePointsHistogramAction,
addErrorAction,
+ pingAction,
resetAction,
updateLoadingStateAction,
API_ACTION_NAME,
diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx
index 2425161615915..9949ec537b77a 100644
--- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx
+++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx
@@ -172,6 +172,33 @@ export const ExplainLogRateSpikesAnalysis: FC
onCancel={cancel}
shouldRerunAnalysis={shouldRerunAnalysis}
/>
+ {errors.length > 0 ? (
+ <>
+
+
+ {errors.length === 1 ? (
+ {errors[0]}
+ ) : (
+
+ {errors.map((e, i) => (
+ - {e}
+ ))}
+
+ )}
+
+
+
+ >
+ ) : null}
{showSpikeAnalysisTable && foundGroups && (
}
/>
)}
- {errors.length > 0 && (
- <>
-
-
- {errors.length === 1 ? (
- {errors[0]}
- ) : (
-
- {errors.map((e, i) => (
- - {e}
- ))}
-
- )}
-
-
-
- >
- )}
{showSpikeAnalysisTable && groupResults && foundGroups ? (
{
+ logInfoMessage('aborted$ subscription trigger.');
shouldStop = true;
controller.abort();
});
request.events.completed$.subscribe(() => {
+ logInfoMessage('completed$ subscription trigger.');
shouldStop = true;
controller.abort();
});
- const { end, push, responseWithHeaders } = streamFactory(
- request.headers,
- logger,
- true
- );
+ const {
+ end: streamEnd,
+ push,
+ responseWithHeaders,
+ } = streamFactory(request.headers, logger, true);
+
+ function pushPing() {
+ push(pingAction());
+ }
+
+ const pingInterval = setInterval(pushPing, 1000);
+
+ function end() {
+ logInfoMessage('Ending analysis.');
+ clearInterval(pingInterval);
+ streamEnd();
+ }
function endWithUpdatedLoadingState() {
push(
@@ -114,9 +138,16 @@ export const defineExplainLogRateSpikesRoute = (
end();
}
+ function pushError(m: string) {
+ logInfoMessage('Push error.');
+ push(addErrorAction(m));
+ }
+
// Async IIFE to run the analysis while not blocking returning `responseWithHeaders`.
(async () => {
+ logInfoMessage('Reset.');
push(resetAction());
+ logInfoMessage('Load field candidates.');
push(
updateLoadingStateAction({
ccsWarning: false,
@@ -134,7 +165,8 @@ export const defineExplainLogRateSpikesRoute = (
try {
fieldCandidates = await fetchFieldCandidates(client, request.body);
} catch (e) {
- push(addErrorAction(e.toString()));
+ logger.error(`Failed to fetch field candidates, got: \n${e.toString()}`);
+ pushError(`Failed to fetch field candidates.`);
end();
return;
}
@@ -168,17 +200,33 @@ export const defineExplainLogRateSpikesRoute = (
const changePoints: ChangePoint[] = [];
const fieldsToSample = new Set();
const chunkSize = 10;
+ let chunkCount = 0;
const fieldCandidatesChunks = chunk(fieldCandidates, chunkSize);
+ logInfoMessage('Fetch p-values.');
+
for (const fieldCandidatesChunk of fieldCandidatesChunks) {
+ chunkCount++;
+ logInfoMessage(`Fetch p-values. Chunk ${chunkCount} of ${fieldCandidatesChunks.length}`);
let pValues: Awaited>;
try {
- pValues = await fetchChangePointPValues(client, request.body, fieldCandidatesChunk);
+ pValues = await fetchChangePointPValues(
+ client,
+ request.body,
+ fieldCandidatesChunk,
+ logger,
+ pushError
+ );
} catch (e) {
- push(addErrorAction(e.toString()));
- end();
- return;
+ logger.error(
+ `Failed to fetch p-values for ${JSON.stringify(
+ fieldCandidatesChunk
+ )}, got: \n${e.toString()}`
+ );
+ pushError(`Failed to fetch p-values for ${JSON.stringify(fieldCandidatesChunk)}.`);
+ // Still continue the analysis even if chunks of p-value queries fail.
+ continue;
}
if (pValues.length > 0) {
@@ -210,12 +258,15 @@ export const defineExplainLogRateSpikesRoute = (
);
if (shouldStop) {
+ logInfoMessage('shouldStop fetching p-values.');
+
end();
return;
}
}
if (changePoints?.length === 0) {
+ logInfoMessage('Stopping analysis, did not find change points.');
endWithUpdatedLoadingState();
return;
}
@@ -224,16 +275,27 @@ export const defineExplainLogRateSpikesRoute = (
{ fieldName: request.body.timeFieldName, type: KBN_FIELD_TYPES.DATE },
];
- const [overallTimeSeries] = (await fetchHistogramsForFields(
- client,
- request.body.index,
- { match_all: {} },
- // fields
- histogramFields,
- // samplerShardSize
- -1,
- undefined
- )) as [NumericChartData];
+ logInfoMessage('Fetch overall histogram.');
+
+ let overallTimeSeries: NumericChartData | undefined;
+ try {
+ overallTimeSeries = (
+ (await fetchHistogramsForFields(
+ client,
+ request.body.index,
+ { match_all: {} },
+ // fields
+ histogramFields,
+ // samplerShardSize
+ -1,
+ undefined
+ )) as [NumericChartData]
+ )[0];
+ } catch (e) {
+ logger.error(`Failed to fetch the overall histogram data, got: \n${e.toString()}`);
+ pushError(`Failed to fetch overall histogram data.`);
+ // Still continue the analysis even if loading the overall histogram fails.
+ }
function pushHistogramDataLoadingState() {
push(
@@ -251,6 +313,8 @@ export const defineExplainLogRateSpikesRoute = (
}
if (groupingEnabled) {
+ logInfoMessage('Group results.');
+
push(
updateLoadingStateAction({
ccsWarning: false,
@@ -283,208 +347,242 @@ export const defineExplainLogRateSpikesRoute = (
(g) => g.group.length > 1
);
- const { fields, df } = await fetchFrequentItems(
- client,
- request.body.index,
- JSON.parse(request.body.searchQuery) as estypes.QueryDslQueryContainer,
- deduplicatedChangePoints,
- request.body.timeFieldName,
- request.body.deviationMin,
- request.body.deviationMax
- );
-
- // The way the `frequent_items` aggregations works could return item sets that include
- // field/value pairs that are not part of the original list of significant change points.
- // This cleans up groups and removes those unrelated field/value pairs.
- const filteredDf = df
- .map((fi) => {
- fi.set = Object.entries(fi.set).reduce(
- (set, [field, value]) => {
- if (
- changePoints.some((cp) => cp.fieldName === field && cp.fieldValue === value)
- ) {
- set[field] = value;
+ try {
+ const { fields, df } = await fetchFrequentItems(
+ client,
+ request.body.index,
+ JSON.parse(request.body.searchQuery) as estypes.QueryDslQueryContainer,
+ deduplicatedChangePoints,
+ request.body.timeFieldName,
+ request.body.deviationMin,
+ request.body.deviationMax,
+ logger,
+ pushError
+ );
+
+ if (fields.length > 0 && df.length > 0) {
+ // The way the `frequent_items` aggregations works could return item sets that include
+ // field/value pairs that are not part of the original list of significant change points.
+ // This cleans up groups and removes those unrelated field/value pairs.
+ const filteredDf = df
+ .map((fi) => {
+ fi.set = Object.entries(fi.set).reduce(
+ (set, [field, value]) => {
+ if (
+ changePoints.some((cp) => cp.fieldName === field && cp.fieldValue === value)
+ ) {
+ set[field] = value;
+ }
+ return set;
+ },
+ {}
+ );
+ fi.size = Object.keys(fi.set).length;
+ return fi;
+ })
+ .filter((fi) => fi.size > 1);
+
+ // `frequent_items` returns lot of different small groups of field/value pairs that co-occur.
+ // The following steps analyse these small groups, identify overlap between these groups,
+ // and then summarize them in larger groups where possible.
+
+ // Get a tree structure based on `frequent_items`.
+ const { root } = getSimpleHierarchicalTree(filteredDf, true, false, fields);
+
+ // Each leave of the tree will be a summarized group of co-occuring field/value pairs.
+ const treeLeaves = getSimpleHierarchicalTreeLeaves(root, []);
+
+ // To be able to display a more cleaned up results table in the UI, we identify field/value pairs
+ // that occur in multiple groups. This will allow us to highlight field/value pairs that are
+ // unique to a group in a better way. This step will also re-add duplicates we identified in the
+ // beginning and didn't pass on to the `frequent_items` agg.
+ const fieldValuePairCounts = getFieldValuePairCounts(treeLeaves);
+ const changePointGroups = markDuplicates(treeLeaves, fieldValuePairCounts).map(
+ (g) => {
+ const group = [...g.group];
+
+ for (const groupItem of g.group) {
+ const { duplicate } = groupItem;
+ const duplicates = groupedChangePoints.find((d) =>
+ d.group.some(
+ (dg) =>
+ dg.fieldName === groupItem.fieldName &&
+ dg.fieldValue === groupItem.fieldValue
+ )
+ );
+
+ if (duplicates !== undefined) {
+ group.push(
+ ...duplicates.group.map((d) => {
+ return {
+ fieldName: d.fieldName,
+ fieldValue: d.fieldValue,
+ duplicate,
+ };
+ })
+ );
+ }
}
- return set;
- },
- {}
- );
- fi.size = Object.keys(fi.set).length;
- return fi;
- })
- .filter((fi) => fi.size > 1);
-
- // `frequent_items` returns lot of different small groups of field/value pairs that co-occur.
- // The following steps analyse these small groups, identify overlap between these groups,
- // and then summarize them in larger groups where possible.
-
- // Get a tree structure based on `frequent_items`.
- const { root } = getSimpleHierarchicalTree(filteredDf, true, false, fields);
-
- // Each leave of the tree will be a summarized group of co-occuring field/value pairs.
- const treeLeaves = getSimpleHierarchicalTreeLeaves(root, []);
-
- // To be able to display a more cleaned up results table in the UI, we identify field/value pairs
- // that occur in multiple groups. This will allow us to highlight field/value pairs that are
- // unique to a group in a better way. This step will also re-add duplicates we identified in the
- // beginning and didn't pass on to the `frequent_items` agg.
- const fieldValuePairCounts = getFieldValuePairCounts(treeLeaves);
- const changePointGroups = markDuplicates(treeLeaves, fieldValuePairCounts).map((g) => {
- const group = [...g.group];
-
- for (const groupItem of g.group) {
- const { duplicate } = groupItem;
- const duplicates = groupedChangePoints.find((d) =>
- d.group.some(
- (dg) =>
- dg.fieldName === groupItem.fieldName && dg.fieldValue === groupItem.fieldValue
- )
- );
-
- if (duplicates !== undefined) {
- group.push(
- ...duplicates.group.map((d) => {
- return {
- fieldName: d.fieldName,
- fieldValue: d.fieldValue,
- duplicate,
- };
- })
- );
- }
- }
- return {
- ...g,
- group,
- };
- });
-
- // Some field/value pairs might not be part of the `frequent_items` result set, for example
- // because they don't co-occur with other field/value pairs or because of the limits we set on the query.
- // In this next part we identify those missing pairs and add them as individual groups.
- const missingChangePoints = deduplicatedChangePoints.filter((cp) => {
- return !changePointGroups.some((cpg) => {
- return cpg.group.some(
- (d) => d.fieldName === cp.fieldName && d.fieldValue === cp.fieldValue
+ return {
+ ...g,
+ group,
+ };
+ }
);
- });
- });
- changePointGroups.push(
- ...missingChangePoints.map(({ fieldName, fieldValue, doc_count: docCount, pValue }) => {
- const duplicates = groupedChangePoints.find((d) =>
- d.group.some((dg) => dg.fieldName === fieldName && dg.fieldValue === fieldValue)
+ // Some field/value pairs might not be part of the `frequent_items` result set, for example
+ // because they don't co-occur with other field/value pairs or because of the limits we set on the query.
+ // In this next part we identify those missing pairs and add them as individual groups.
+ const missingChangePoints = deduplicatedChangePoints.filter((cp) => {
+ return !changePointGroups.some((cpg) => {
+ return cpg.group.some(
+ (d) => d.fieldName === cp.fieldName && d.fieldValue === cp.fieldValue
+ );
+ });
+ });
+
+ changePointGroups.push(
+ ...missingChangePoints.map(
+ ({ fieldName, fieldValue, doc_count: docCount, pValue }) => {
+ const duplicates = groupedChangePoints.find((d) =>
+ d.group.some(
+ (dg) => dg.fieldName === fieldName && dg.fieldValue === fieldValue
+ )
+ );
+ if (duplicates !== undefined) {
+ return {
+ id: `${stringHash(
+ JSON.stringify(
+ duplicates.group.map((d) => ({
+ fieldName: d.fieldName,
+ fieldValue: d.fieldValue,
+ }))
+ )
+ )}`,
+ group: duplicates.group.map((d) => ({
+ fieldName: d.fieldName,
+ fieldValue: d.fieldValue,
+ duplicate: false,
+ })),
+ docCount,
+ pValue,
+ };
+ } else {
+ return {
+ id: `${stringHash(JSON.stringify({ fieldName, fieldValue }))}`,
+ group: [
+ {
+ fieldName,
+ fieldValue,
+ duplicate: false,
+ },
+ ],
+ docCount,
+ pValue,
+ };
+ }
+ }
+ )
);
- if (duplicates !== undefined) {
- return {
- id: `${stringHash(
- JSON.stringify(
- duplicates.group.map((d) => ({
- fieldName: d.fieldName,
- fieldValue: d.fieldValue,
- }))
- )
- )}`,
- group: duplicates.group.map((d) => ({
- fieldName: d.fieldName,
- fieldValue: d.fieldValue,
- duplicate: false,
- })),
- docCount,
- pValue,
- };
- } else {
- return {
- id: `${stringHash(JSON.stringify({ fieldName, fieldValue }))}`,
- group: [
- {
- fieldName,
- fieldValue,
- duplicate: false,
- },
- ],
- docCount,
- pValue,
- };
- }
- })
- );
-
- // Finally, we'll find out if there's at least one group with at least two items,
- // only then will we return the groups to the clients and make the grouping option available.
- const maxItems = Math.max(...changePointGroups.map((g) => g.group.length));
- if (maxItems > 1) {
- push(addChangePointsGroupAction(changePointGroups));
- }
+ // Finally, we'll find out if there's at least one group with at least two items,
+ // only then will we return the groups to the clients and make the grouping option available.
+ const maxItems = Math.max(...changePointGroups.map((g) => g.group.length));
- loaded += PROGRESS_STEP_GROUPING;
+ if (maxItems > 1) {
+ push(addChangePointsGroupAction(changePointGroups));
+ }
- pushHistogramDataLoadingState();
+ loaded += PROGRESS_STEP_GROUPING;
- if (changePointGroups) {
- await asyncForEach(changePointGroups, async (cpg, index) => {
- const histogramQuery = {
- bool: {
- filter: cpg.group.map((d) => ({
- term: { [d.fieldName]: d.fieldValue },
- })),
- },
- };
+ pushHistogramDataLoadingState();
- const [cpgTimeSeries] = (await fetchHistogramsForFields(
- client,
- request.body.index,
- histogramQuery,
- // fields
- [
- {
- fieldName: request.body.timeFieldName,
- type: KBN_FIELD_TYPES.DATE,
- interval: overallTimeSeries.interval,
- min: overallTimeSeries.stats[0],
- max: overallTimeSeries.stats[1],
- },
- ],
- // samplerShardSize
- -1,
- undefined
- )) as [NumericChartData];
+ logInfoMessage('Fetch group histograms.');
- const histogram =
- overallTimeSeries.data.map((o, i) => {
- const current = cpgTimeSeries.data.find(
- (d1) => d1.key_as_string === o.key_as_string
- ) ?? {
- doc_count: 0,
- };
- return {
- key: o.key,
- key_as_string: o.key_as_string ?? '',
- doc_count_change_point: current.doc_count,
- doc_count_overall: Math.max(0, o.doc_count - current.doc_count),
+ await asyncForEach(changePointGroups, async (cpg) => {
+ if (overallTimeSeries !== undefined) {
+ const histogramQuery = {
+ bool: {
+ filter: cpg.group.map((d) => ({
+ term: { [d.fieldName]: d.fieldValue },
+ })),
+ },
};
- }) ?? [];
- push(
- addChangePointsGroupHistogramAction([
- {
- id: cpg.id,
- histogram,
- },
- ])
- );
- });
+ let cpgTimeSeries: NumericChartData;
+ try {
+ cpgTimeSeries = (
+ (await fetchHistogramsForFields(
+ client,
+ request.body.index,
+ histogramQuery,
+ // fields
+ [
+ {
+ fieldName: request.body.timeFieldName,
+ type: KBN_FIELD_TYPES.DATE,
+ interval: overallTimeSeries.interval,
+ min: overallTimeSeries.stats[0],
+ max: overallTimeSeries.stats[1],
+ },
+ ],
+ // samplerShardSize
+ -1,
+ undefined
+ )) as [NumericChartData]
+ )[0];
+ } catch (e) {
+ logger.error(
+ `Failed to fetch the histogram data for group #${
+ cpg.id
+ }, got: \n${e.toString()}`
+ );
+ pushError(`Failed to fetch the histogram data for group #${cpg.id}.`);
+ return;
+ }
+ const histogram =
+ overallTimeSeries.data.map((o, i) => {
+ const current = cpgTimeSeries.data.find(
+ (d1) => d1.key_as_string === o.key_as_string
+ ) ?? {
+ doc_count: 0,
+ };
+ return {
+ key: o.key,
+ key_as_string: o.key_as_string ?? '',
+ doc_count_change_point: current.doc_count,
+ doc_count_overall: Math.max(0, o.doc_count - current.doc_count),
+ };
+ }) ?? [];
+
+ push(
+ addChangePointsGroupHistogramAction([
+ {
+ id: cpg.id,
+ histogram,
+ },
+ ])
+ );
+ }
+ });
+ }
+ } catch (e) {
+ logger.error(
+ `Failed to transform field/value pairs into groups, got: \n${e.toString()}`
+ );
+ pushError(`Failed to transform field/value pairs into groups.`);
}
}
loaded += PROGRESS_STEP_HISTOGRAMS_GROUPS;
+ logInfoMessage('Fetch field/value histograms.');
+
// time series filtered by fields
- if (changePoints) {
- await asyncForEach(changePoints, async (cp, index) => {
- if (changePoints) {
+ if (changePoints && overallTimeSeries !== undefined) {
+ await asyncForEach(changePoints, async (cp) => {
+ if (overallTimeSeries !== undefined) {
const histogramQuery = {
bool: {
filter: [
@@ -495,24 +593,40 @@ export const defineExplainLogRateSpikesRoute = (
},
};
- const [cpTimeSeries] = (await fetchHistogramsForFields(
- client,
- request.body.index,
- histogramQuery,
- // fields
- [
- {
- fieldName: request.body.timeFieldName,
- type: KBN_FIELD_TYPES.DATE,
- interval: overallTimeSeries.interval,
- min: overallTimeSeries.stats[0],
- max: overallTimeSeries.stats[1],
- },
- ],
- // samplerShardSize
- -1,
- undefined
- )) as [NumericChartData];
+ let cpTimeSeries: NumericChartData;
+
+ try {
+ cpTimeSeries = (
+ (await fetchHistogramsForFields(
+ client,
+ request.body.index,
+ histogramQuery,
+ // fields
+ [
+ {
+ fieldName: request.body.timeFieldName,
+ type: KBN_FIELD_TYPES.DATE,
+ interval: overallTimeSeries.interval,
+ min: overallTimeSeries.stats[0],
+ max: overallTimeSeries.stats[1],
+ },
+ ],
+ // samplerShardSize
+ -1,
+ undefined
+ )) as [NumericChartData]
+ )[0];
+ } catch (e) {
+ logger.error(
+ `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${
+ cp.fieldValue
+ }", got: \n${e.toString()}`
+ );
+ pushError(
+ `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${cp.fieldValue}".`
+ );
+ return;
+ }
const histogram =
overallTimeSeries.data.map((o, i) => {
diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts
index 03242a4bc8ae5..0fb7f90c89c12 100644
--- a/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts
+++ b/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts
@@ -8,6 +8,7 @@ import { uniqBy } from 'lodash';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { ElasticsearchClient } from '@kbn/core/server';
+import type { Logger } from '@kbn/logging';
import { ChangePoint } from '@kbn/ml-agg-utils';
import { SPIKE_ANALYSIS_THRESHOLD } from '../../../common/constants';
import type { AiopsExplainLogRateSpikesSchema } from '../../../common/api/explain_log_rate_spikes';
@@ -92,7 +93,9 @@ interface Aggs extends estypes.AggregationsSignificantLongTermsAggregate {
export const fetchChangePointPValues = async (
esClient: ElasticsearchClient,
params: AiopsExplainLogRateSpikesSchema,
- fieldNames: string[]
+ fieldNames: string[],
+ logger: Logger,
+ emitError: (m: string) => void
): Promise => {
const result: ChangePoint[] = [];
@@ -101,7 +104,16 @@ export const fetchChangePointPValues = async (
const resp = await esClient.search(request);
if (resp.aggregations === undefined) {
- throw new Error('fetchChangePoint failed, did not return aggregations.');
+ logger.error(
+ `Failed to fetch p-value aggregation for fieldName "${fieldName}", got: \n${JSON.stringify(
+ resp,
+ null,
+ 2
+ )}`
+ );
+ emitError(`Failed to fetch p-value aggregation for fieldName "${fieldName}".`);
+ // Still continue the analysis even if individual p-value queries fail.
+ continue;
}
const overallResult = resp.aggregations.change_point_p_value;
diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts
index 055c22397064f..c9444aaca22af 100644
--- a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts
+++ b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts
@@ -10,6 +10,7 @@ import { uniq, uniqWith, pick, isEqual } from 'lodash';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
+import type { Logger } from '@kbn/logging';
import type { ChangePoint, FieldValuePair } from '@kbn/ml-agg-utils';
interface FrequentItemsAggregation extends estypes.AggregationsSamplerAggregation {
@@ -53,9 +54,11 @@ export async function fetchFrequentItems(
changePoints: ChangePoint[],
timeFieldName: string,
deviationMin: number,
- deviationMax: number
+ deviationMax: number,
+ logger: Logger,
+ emitError: (m: string) => void
) {
- // get unique fields that are left
+ // get unique fields from change points
const fields = [...new Set(changePoints.map((t) => t.fieldName))];
// TODO add query params
@@ -91,6 +94,8 @@ export async function fetchFrequentItems(
sampleProbability = Math.min(0.5, minDocCount / totalDocCount);
}
+ logger.debug(`frequent_items sample probability: ${sampleProbability}`);
+
// frequent items can be slow, so sample and use 10% min_support
const aggs: Record = {
sample: {
@@ -103,7 +108,7 @@ export async function fetchFrequentItems(
frequent_items: {
minimum_set_size: 2,
size: 200,
- minimum_support: 0.01,
+ minimum_support: 0.1,
fields: aggFields,
},
},
@@ -125,12 +130,18 @@ export async function fetchFrequentItems(
{ maxRetries: 0 }
);
- const totalDocCountFi = (body.hits.total as estypes.SearchTotalHits).value;
-
if (body.aggregations === undefined) {
- throw new Error('fetchFrequentItems failed, did not return aggregations.');
+ logger.error(`Failed to fetch frequent_items, got: \n${JSON.stringify(body, null, 2)}`);
+ emitError(`Failed to fetch frequent_items.`);
+ return {
+ fields: [],
+ df: [],
+ totalDocCount: 0,
+ };
}
+ const totalDocCountFi = (body.hits.total as estypes.SearchTotalHits).value;
+
const shape = body.aggregations.sample.fi.buckets.length;
let maximum = shape;
if (maximum > 50000) {
diff --git a/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts b/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts
index b4bdf9f50beb5..a2e1f158a73e2 100644
--- a/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts
+++ b/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts
@@ -222,9 +222,7 @@ export default ({ getService }: FtrProviderContext) => {
const errorActions = data.filter((d) => d.type === expected.errorFilter);
expect(errorActions.length).to.be(1);
- expect(errorActions[0].payload).to.be(
- 'ResponseError: index_not_found_exception: [index_not_found_exception] Reason: no such index [does_not_exist]'
- );
+ expect(errorActions[0].payload).to.be('Failed to fetch field candidates.');
});
});
};
From 87dc1fa82f4654defcac1834bd722d6330e91036 Mon Sep 17 00:00:00 2001
From: Ashokaditya <1849116+ashokaditya@users.noreply.github.com>
Date: Tue, 4 Oct 2022 13:57:00 +0200
Subject: [PATCH 07/27] [Security Solution][Endpoint][Response Actions] Add
license check to actions log management RBAC (#142482)
* Add license check to actions log management RBAC
fixes elastic/security-team/issues/5118
refs elastic/kibana/pull/142470
* useUSerPrivileges instead
review changes (@paul-tavares)
* Don't register route if no access
review changes (@paul-tavares)
* reset mocked privilege
review changes (@paul-tavares)
---
.../common/endpoint/service/authz/authz.ts | 2 +-
.../common/components/navigation/types.ts | 2 +-
.../index.test.tsx | 28 +++++++++++++++++++
.../use_navigation_items.tsx | 8 +++++-
.../public/management/links.test.ts | 19 ++++++++++++-
.../public/management/links.ts | 17 +++++++----
.../public/management/pages/index.tsx | 13 +++++----
7 files changed, 75 insertions(+), 14 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts
index dde2a7f92b1e0..d25fd440d1c24 100644
--- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts
@@ -160,7 +160,7 @@ export const calculateEndpointAuthz = (
canWritePolicyManagement,
canReadPolicyManagement,
canWriteActionsLogManagement,
- canReadActionsLogManagement,
+ canReadActionsLogManagement: canReadActionsLogManagement && isPlatinumPlusLicense,
// Response Actions
canIsolateHost: canIsolateHost && isPlatinumPlusLicense,
canUnIsolateHost: canIsolateHost,
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts
index ebfae21d5a5e5..5a2d192b9fd48 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts
@@ -68,7 +68,6 @@ export interface NavTab {
}
export const securityNavKeys = [
SecurityPageName.alerts,
- SecurityPageName.responseActionsHistory,
SecurityPageName.blocklist,
SecurityPageName.detectionAndResponse,
SecurityPageName.case,
@@ -81,6 +80,7 @@ export const securityNavKeys = [
SecurityPageName.hosts,
SecurityPageName.network,
SecurityPageName.overview,
+ SecurityPageName.responseActionsHistory,
SecurityPageName.rules,
SecurityPageName.timelines,
SecurityPageName.trustedApps,
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx
index 1055c98835d56..5a99df01e5328 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx
@@ -17,6 +17,7 @@ import { TestProviders } from '../../../mock';
import { CASES_FEATURE_ID } from '../../../../../common/constants';
import { useCanSeeHostIsolationExceptionsMenu } from '../../../../management/pages/host_isolation_exceptions/view/hooks';
import { useTourContext } from '../../guided_onboarding';
+import { useUserPrivileges } from '../../user_privileges';
import {
noCasesPermissions,
readCasesCapabilities,
@@ -38,6 +39,9 @@ jest.mock('../../../hooks/use_experimental_features');
jest.mock('../../../utils/route/use_route_spy');
jest.mock('../../../../management/pages/host_isolation_exceptions/view/hooks');
jest.mock('../../guided_onboarding');
+jest.mock('../../user_privileges');
+
+const mockUseUserPrivileges = useUserPrivileges as jest.Mock;
describe('useSecuritySolutionNavigation', () => {
const mockRouteSpy = [
@@ -56,6 +60,9 @@ describe('useSecuritySolutionNavigation', () => {
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false);
(useRouteSpy as jest.Mock).mockReturnValue(mockRouteSpy);
(useCanSeeHostIsolationExceptionsMenu as jest.Mock).mockReturnValue(true);
+ mockUseUserPrivileges.mockImplementation(() => ({
+ endpointPrivileges: { canReadActionsLogManagement: true },
+ }));
(useTourContext as jest.Mock).mockReturnValue({ isTourShown: false });
const cases = mockCasesContract();
@@ -83,6 +90,10 @@ describe('useSecuritySolutionNavigation', () => {
});
});
+ afterEach(() => {
+ mockUseUserPrivileges.mockReset();
+ });
+
it('should create navigation config', async () => {
const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(
() => useSecuritySolutionNavigation(),
@@ -117,6 +128,23 @@ describe('useSecuritySolutionNavigation', () => {
).toBeUndefined();
});
+ it('should omit response actions history if hook reports false', () => {
+ mockUseUserPrivileges.mockImplementation(() => ({
+ endpointPrivileges: { canReadActionsLogManagement: false },
+ }));
+ const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(
+ () => useSecuritySolutionNavigation(),
+ { wrapper: TestProviders }
+ );
+ const items = result.current?.items;
+ expect(items).toBeDefined();
+ expect(
+ items!
+ .find((item) => item.id === 'manage')
+ ?.items?.find((item) => item.id === 'response_actions_history')
+ ).toBeUndefined();
+ });
+
describe('Permission gated routes', () => {
describe('cases', () => {
it('should display the cases navigation item when the user has read permissions', () => {
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx
index dc15e371ba630..a4364c8564529 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx
@@ -21,6 +21,7 @@ import { SecurityPageName } from '../../../../../common/constants';
import { useCanSeeHostIsolationExceptionsMenu } from '../../../../management/pages/host_isolation_exceptions/view/hooks';
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
import { useGlobalQueryString } from '../../../utils/global_query_string';
+import { useUserPrivileges } from '../../user_privileges';
export const usePrimaryNavigationItems = ({
navTabs,
@@ -71,6 +72,8 @@ export const usePrimaryNavigationItems = ({
function usePrimaryNavigationItemsToDisplay(navTabs: Record) {
const hasCasesReadPermissions = useGetUserCasesPermissions().read;
const canSeeHostIsolationExceptions = useCanSeeHostIsolationExceptionsMenu();
+ const canSeeResponseActionsHistory =
+ useUserPrivileges().endpointPrivileges.canReadActionsLogManagement;
const isPolicyListEnabled = useIsExperimentalFeatureEnabled('policyListEnabled');
const uiCapabilities = useKibana().services.application.capabilities;
@@ -138,7 +141,9 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) {
? [navTabs[SecurityPageName.hostIsolationExceptions]]
: []),
navTabs[SecurityPageName.blocklist],
- navTabs[SecurityPageName.responseActionsHistory],
+ ...(canSeeResponseActionsHistory
+ ? [navTabs[SecurityPageName.responseActionsHistory]]
+ : []),
navTabs[SecurityPageName.cloudSecurityPostureBenchmarks],
],
},
@@ -156,6 +161,7 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) {
navTabs,
hasCasesReadPermissions,
canSeeHostIsolationExceptions,
+ canSeeResponseActionsHistory,
isPolicyListEnabled,
]
);
diff --git a/x-pack/plugins/security_solution/public/management/links.test.ts b/x-pack/plugins/security_solution/public/management/links.test.ts
index 09c47bc70095c..c8166563428ab 100644
--- a/x-pack/plugins/security_solution/public/management/links.test.ts
+++ b/x-pack/plugins/security_solution/public/management/links.test.ts
@@ -80,13 +80,30 @@ describe('links', () => {
expect(filteredLinks).toEqual(links);
});
+ it('it returns all but response actions history when no access privilege to either response actions history or HIE but have at least one HIE entry', async () => {
+ fakeHttpServices.get.mockResolvedValue({ total: 1 });
+ const filteredLinks = await getManagementFilteredLinks(
+ coreMockStarted,
+ getPlugins(['superuser'])
+ );
+ (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false);
+ expect(filteredLinks).toEqual({
+ ...links,
+ links: links.links?.filter((link) => link.id !== SecurityPageName.responseActionsHistory),
+ });
+ });
+
it('it returns filtered links when not having isolation permissions and no host isolation exceptions entry', async () => {
fakeHttpServices.get.mockResolvedValue({ total: 0 });
(licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false);
const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins([]));
expect(filteredLinks).toEqual({
...links,
- links: links.links?.filter((link) => link.id !== SecurityPageName.hostIsolationExceptions),
+ links: links.links?.filter(
+ (link) =>
+ link.id !== SecurityPageName.hostIsolationExceptions &&
+ link.id !== SecurityPageName.responseActionsHistory
+ ),
});
});
});
diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts
index 03cfee736def3..12a904201a9c5 100644
--- a/x-pack/plugins/security_solution/public/management/links.ts
+++ b/x-pack/plugins/security_solution/public/management/links.ts
@@ -226,7 +226,7 @@ export const links: LinkItem = {
],
};
-const getFilteredLinks = (linkIds: SecurityPageName[]) => ({
+const excludeLinks = (linkIds: SecurityPageName[]) => ({
...links,
links: links.links?.filter((link) => !linkIds.includes(link.id)),
});
@@ -249,19 +249,26 @@ export const getManagementFilteredLinks = async (
)
: getEndpointAuthzInitialState();
if (!privileges.canAccessEndpointManagement) {
- return getFilteredLinks([SecurityPageName.hostIsolationExceptions]);
+ return excludeLinks([
+ SecurityPageName.hostIsolationExceptions,
+ SecurityPageName.responseActionsHistory,
+ ]);
}
- if (!privileges.canIsolateHost) {
+ if (!privileges.canIsolateHost || !privileges.canReadActionsLogManagement) {
const hostIsolationExceptionsApiClientInstance = HostIsolationExceptionsApiClient.getInstance(
core.http
);
const summaryResponse = await hostIsolationExceptionsApiClientInstance.summary();
if (!summaryResponse.total) {
- return getFilteredLinks([SecurityPageName.hostIsolationExceptions]);
+ return excludeLinks([
+ SecurityPageName.hostIsolationExceptions,
+ SecurityPageName.responseActionsHistory,
+ ]);
}
+ return excludeLinks([SecurityPageName.responseActionsHistory]);
}
} catch {
- return getFilteredLinks([SecurityPageName.hostIsolationExceptions]);
+ return excludeLinks([SecurityPageName.hostIsolationExceptions]);
}
return links;
diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx
index dd06a838a26cb..590b3786ece15 100644
--- a/x-pack/plugins/security_solution/public/management/pages/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx
@@ -76,7 +76,8 @@ const ResponseActionsTelemetry = () => (
);
export const ManagementContainer = memo(() => {
- const { loading, canAccessEndpointManagement } = useUserPrivileges().endpointPrivileges;
+ const { loading, canAccessEndpointManagement, canReadActionsLogManagement } =
+ useUserPrivileges().endpointPrivileges;
// Lets wait until we can verify permissions
if (loading) {
@@ -103,10 +104,12 @@ export const ManagementContainer = memo(() => {
component={HostIsolationExceptionsTelemetry}
/>
-
+ {canReadActionsLogManagement && (
+
+ )}
From 8be7668d208073a80812397050298a6954c51a92 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20R=C3=BChsen?=
Date: Tue, 4 Oct 2022 13:59:28 +0200
Subject: [PATCH 08/27] [Profiling] Show Top 1000 functions (#142391)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/profiling/public/components/topn_functions.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/profiling/public/components/topn_functions.tsx b/x-pack/plugins/profiling/public/components/topn_functions.tsx
index 3ad540983d903..4d8522913a245 100644
--- a/x-pack/plugins/profiling/public/components/topn_functions.tsx
+++ b/x-pack/plugins/profiling/public/components/topn_functions.tsx
@@ -219,7 +219,7 @@ export const TopNFunctionsTable = ({
: row[sortField];
},
[sortDirection]
- ).slice(0, 100);
+ );
return (
<>
From 8e770bb6080b99b5437b22b22fc0d07c68e4c504 Mon Sep 17 00:00:00 2001
From: Uladzislau Lasitsa
Date: Tue, 4 Oct 2022 15:00:59 +0300
Subject: [PATCH 09/27] [TSVB][Lens]Fix conversion from static value in
timeseries to reference line in lens (#142453)
* Fix conversion static value in timeseries to reference line in lens
* Doesn't allow convert static value with split
* Fix condition
* Ignore axis position from model for top n
* Added tests
Co-authored-by: Stratoula Kalafateli
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../lib/configurations/xy/layers.test.ts | 131 ++++++++++++------
.../lib/configurations/xy/layers.ts | 37 +++--
.../convert_to_lens/timeseries/index.test.ts | 13 ++
.../convert_to_lens/timeseries/index.ts | 12 +-
.../public/convert_to_lens/top_n/index.ts | 2 +-
5 files changed, 138 insertions(+), 57 deletions(-)
diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts
index 6c94971397d3e..46e9d9e1fae2a 100644
--- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts
+++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts
@@ -24,6 +24,33 @@ jest.mock('uuid', () => ({
v4: () => 'test-id',
}));
+const mockedIndices = [
+ {
+ id: 'test',
+ title: 'test',
+ timeFieldName: 'test_field',
+ getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }),
+ },
+] as unknown as DataView[];
+
+const indexPatternsService = {
+ getDefault: jest.fn(() =>
+ Promise.resolve({
+ id: 'default',
+ title: 'index',
+ getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }),
+ })
+ ),
+ get: jest.fn((id) => Promise.resolve({ ...mockedIndices[0], id })),
+ find: jest.fn((search: string, size: number) => {
+ if (size !== 1) {
+ // shouldn't request more than one data view since there is a significant performance penalty
+ throw new Error('trying to fetch too many data views');
+ }
+ return Promise.resolve(mockedIndices || []);
+ }),
+} as unknown as DataViewsPublicPluginStart;
+
describe('getLayers', () => {
const dataSourceLayers: Record = [
{
@@ -331,10 +358,16 @@ describe('getLayers', () => {
series: [createSeries({ metrics: staticValueMetric })],
});
- test.each<[string, [Record, Panel], Array>]>([
+ test.each<
+ [
+ string,
+ [Record, Panel, DataViewsPublicPluginStart, boolean],
+ Array>
+ ]
+ >([
[
'data layer if columns do not include static column',
- [dataSourceLayers, panel],
+ [dataSourceLayers, panel, indexPatternsService, false],
[
{
layerType: 'data',
@@ -353,9 +386,30 @@ describe('getLayers', () => {
},
],
],
+ [
+ 'data layer with "left" axisMode if isSingleAxis is provided',
+ [dataSourceLayers, panel, indexPatternsService, true],
+ [
+ {
+ layerType: 'data',
+ accessors: ['column-id-1'],
+ xAccessor: 'column-id-2',
+ splitAccessor: 'column-id-3',
+ seriesType: 'area',
+ layerId: 'test-layer-1',
+ yConfig: [
+ {
+ forAccessor: 'column-id-1',
+ axisMode: 'left',
+ color: '#68BC00',
+ },
+ ],
+ },
+ ],
+ ],
[
'reference line layer if columns include static column',
- [dataSourceLayersWithStatic, panelWithStaticValue],
+ [dataSourceLayersWithStatic, panelWithStaticValue, indexPatternsService, false],
[
{
layerType: 'referenceLine',
@@ -364,9 +418,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
- axisMode: 'right',
+ axisMode: 'left',
color: '#68BC00',
fill: 'below',
+ lineWidth: 1,
},
],
},
@@ -374,7 +429,7 @@ describe('getLayers', () => {
],
[
'correct colors if columns include percentile columns',
- [dataSourceLayersWithPercentile, panelWithPercentileMetric],
+ [dataSourceLayersWithPercentile, panelWithPercentileMetric, indexPatternsService, false],
[
{
yConfig: [
@@ -394,7 +449,12 @@ describe('getLayers', () => {
],
[
'correct colors if columns include percentile rank columns',
- [dataSourceLayersWithPercentileRank, panelWithPercentileRankMetric],
+ [
+ dataSourceLayersWithPercentileRank,
+ panelWithPercentileRankMetric,
+ indexPatternsService,
+ false,
+ ],
[
{
yConfig: [
@@ -414,7 +474,7 @@ describe('getLayers', () => {
],
[
'annotation layer gets correct params and converts color, extraFields and icons',
- [dataSourceLayersWithStatic, panelWithSingleAnnotation],
+ [dataSourceLayersWithStatic, panelWithSingleAnnotation, indexPatternsService, false],
[
{
layerType: 'referenceLine',
@@ -423,9 +483,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
- axisMode: 'right',
+ axisMode: 'left',
color: '#68BC00',
fill: 'below',
+ lineWidth: 1,
},
],
},
@@ -459,7 +520,12 @@ describe('getLayers', () => {
],
[
'annotation layer should gets correct default params',
- [dataSourceLayersWithStatic, panelWithSingleAnnotationWithoutQueryStringAndTimefield],
+ [
+ dataSourceLayersWithStatic,
+ panelWithSingleAnnotationWithoutQueryStringAndTimefield,
+ indexPatternsService,
+ false,
+ ],
[
{
layerType: 'referenceLine',
@@ -468,9 +534,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
- axisMode: 'right',
+ axisMode: 'left',
color: '#68BC00',
fill: 'below',
+ lineWidth: 1,
},
],
},
@@ -504,7 +571,7 @@ describe('getLayers', () => {
],
[
'multiple annotations with different data views create separate layers',
- [dataSourceLayersWithStatic, panelWithMultiAnnotations],
+ [dataSourceLayersWithStatic, panelWithMultiAnnotations, indexPatternsService, false],
[
{
layerType: 'referenceLine',
@@ -513,9 +580,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
- axisMode: 'right',
+ axisMode: 'left',
color: '#68BC00',
fill: 'below',
+ lineWidth: 1,
},
],
},
@@ -598,7 +666,12 @@ describe('getLayers', () => {
],
[
'annotation layer gets correct dataView when none is defined',
- [dataSourceLayersWithStatic, panelWithSingleAnnotationDefaultDataView],
+ [
+ dataSourceLayersWithStatic,
+ panelWithSingleAnnotationDefaultDataView,
+ indexPatternsService,
+ false,
+ ],
[
{
layerType: 'referenceLine',
@@ -607,9 +680,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
- axisMode: 'right',
+ axisMode: 'left',
color: '#68BC00',
fill: 'below',
+ lineWidth: 1,
},
],
},
@@ -642,34 +716,7 @@ describe('getLayers', () => {
],
],
])('should return %s', async (_, input, expected) => {
- const layers = await getLayers(...input, indexPatternsService as DataViewsPublicPluginStart);
+ const layers = await getLayers(...input);
expect(layers).toEqual(expected.map(expect.objectContaining));
});
});
-
-const mockedIndices = [
- {
- id: 'test',
- title: 'test',
- timeFieldName: 'test_field',
- getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }),
- },
-] as unknown as DataView[];
-
-const indexPatternsService = {
- getDefault: jest.fn(() =>
- Promise.resolve({
- id: 'default',
- title: 'index',
- getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }),
- })
- ),
- get: jest.fn((id) => Promise.resolve({ ...mockedIndices[0], id })),
- find: jest.fn((search: string, size: number) => {
- if (size !== 1) {
- // shouldn't request more than one data view since there is a significant performance penalty
- throw new Error('trying to fetch too many data views');
- }
- return Promise.resolve(mockedIndices || []);
- }),
-} as unknown as DataViewsPublicPluginStart;
diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts
index ec0e24e2db873..8784c2952807d 100644
--- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts
+++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts
@@ -24,7 +24,7 @@ import { getDefaultQueryLanguage } from '../../../../application/components/lib/
import { fetchIndexPattern } from '../../../../../common/index_patterns_utils';
import { ICON_TYPES_MAP } from '../../../../application/visualizations/constants';
import { SUPPORTED_METRICS } from '../../metrics';
-import type { Annotation, Metric, Panel } from '../../../../../common/types';
+import type { Annotation, Metric, Panel, Series } from '../../../../../common/types';
import { getSeriesAgg } from '../../series';
import {
isPercentileRanksColumnWithMeta,
@@ -44,6 +44,10 @@ function getPalette(palette: PaletteOutput): PaletteOutput {
: palette;
}
+function getAxisMode(series: Series, model: Panel): YAxisMode {
+ return (series.separate_axis ? series.axis_position : model.axis_position) as YAxisMode;
+}
+
function getColor(
metricColumn: Column,
metric: Metric,
@@ -69,7 +73,8 @@ function nonNullable(value: T): value is NonNullable {
export const getLayers = async (
dataSourceLayers: Record,
model: Panel,
- dataViews: DataViewsPublicPluginStart
+ dataViews: DataViewsPublicPluginStart,
+ isSingleAxis: boolean = false
): Promise => {
const nonAnnotationsLayers: XYLayerConfig[] = Object.keys(dataSourceLayers).map((key) => {
const series = model.series[parseInt(key, 10)];
@@ -84,13 +89,13 @@ export const getLayers = async (
const metricColumns = dataSourceLayer.columns.filter(
(l) => !l.isBucketed && l.columnId !== referenceColumnId
);
- const isReferenceLine = metrics.length === 1 && metrics[0].type === 'static';
+ const isReferenceLine =
+ metricColumns.length === 1 && metricColumns[0].operationType === 'static_value';
const splitAccessor = dataSourceLayer.columns.find(
(column) => column.isBucketed && column.isSplit
)?.columnId;
const chartType = getChartType(series, model.type);
const commonProps = {
- seriesType: chartType,
layerId: dataSourceLayer.layerId,
accessors: metricColumns.map((metricColumn) => {
return metricColumn.columnId;
@@ -102,19 +107,19 @@ export const getLayers = async (
return {
forAccessor: metricColumn.columnId,
color: getColor(metricColumn, metric!, series.color, splitAccessor),
- axisMode: (series.separate_axis
- ? series.axis_position
- : model.axis_position) as YAxisMode,
+ axisMode: isReferenceLine // reference line should be assigned to axis with real data
+ ? model.series.some((s) => s.id !== series.id && getAxisMode(s, model) === 'right')
+ ? 'right'
+ : 'left'
+ : isSingleAxis
+ ? 'left'
+ : getAxisMode(series, model),
...(isReferenceLine && {
- fill: chartType === 'area' ? FillTypes.BELOW : FillTypes.NONE,
+ fill: chartType.includes('area') ? FillTypes.BELOW : FillTypes.NONE,
+ lineWidth: series.line_width,
}),
};
}),
- xAccessor: dataSourceLayer.columns.find((column) => column.isBucketed && !column.isSplit)
- ?.columnId,
- splitAccessor,
- collapseFn: seriesAgg,
- palette: getPalette(series.palette as PaletteOutput),
};
if (isReferenceLine) {
return {
@@ -123,8 +128,14 @@ export const getLayers = async (
};
} else {
return {
+ seriesType: chartType,
layerType: 'data',
...commonProps,
+ xAccessor: dataSourceLayer.columns.find((column) => column.isBucketed && !column.isSplit)
+ ?.columnId,
+ splitAccessor,
+ collapseFn: seriesAgg,
+ palette: getPalette(series.palette as PaletteOutput),
};
}
});
diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts
index 50aa1a6c6f7f4..c81db38e05384 100644
--- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts
+++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts
@@ -112,6 +112,19 @@ describe('convertToLens', () => {
expect(mockGetBucketsColumns).toBeCalledTimes(1);
});
+ test('should return null for static value with buckets', async () => {
+ mockGetBucketsColumns.mockReturnValue([{}]);
+ mockGetMetricsColumns.mockReturnValue([
+ {
+ operationType: 'static_value',
+ },
+ ]);
+ const result = await convertToLens(model);
+ expect(result).toBeNull();
+ expect(mockGetMetricsColumns).toBeCalledTimes(1);
+ expect(mockGetBucketsColumns).toBeCalledTimes(1);
+ });
+
test('should return state for valid model', async () => {
const result = await convertToLens(model);
expect(result).toBeDefined();
diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts
index 8cbbbf0f9e739..ef678fcc2dab4 100644
--- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts
+++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts
@@ -98,11 +98,21 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model: Panel
return null;
}
+ const isReferenceLine =
+ metricsColumns.length === 1 && metricsColumns[0].operationType === 'static_value';
+
+ // only static value without split is supported
+ if (isReferenceLine && bucketsColumns.length) {
+ return null;
+ }
+
const layerId = uuid();
extendedLayers[layerIdx] = {
indexPatternId,
layerId,
- columns: [...metricsColumns, dateHistogramColumn, ...bucketsColumns],
+ columns: isReferenceLine
+ ? [...metricsColumns]
+ : [...metricsColumns, dateHistogramColumn, ...bucketsColumns],
columnOrder: [],
};
}
diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts
index 020aaec28f573..130646f72f127 100644
--- a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts
+++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts
@@ -86,7 +86,7 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeR
};
}
- const configLayers = await getLayers(extendedLayers, model, dataViews);
+ const configLayers = await getLayers(extendedLayers, model, dataViews, true);
if (configLayers === null) {
return null;
}
From bcfa351f06037532dbc7d9ca6f06eaa5193a60d2 Mon Sep 17 00:00:00 2001
From: Byron Hulcher
Date: Tue, 4 Oct 2022 08:17:17 -0400
Subject: [PATCH 10/27] Allow null description field value to round trip from
server (#142540)
---
.../update_connector_name_and_description_api_logic.ts | 8 +++-----
.../connector_name_and_description.tsx | 2 +-
.../server/routes/enterprise_search/connectors.ts | 4 ++--
3 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_name_and_description_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_name_and_description_api_logic.ts
index caf19f80f040a..639bd56208546 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_name_and_description_api_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_name_and_description_api_logic.ts
@@ -16,17 +16,15 @@ export type PutConnectorNameAndDescriptionArgs = Partial<
indexName: string;
};
-export type PutConnectorNameAndDescriptionResponse = Partial<
- Pick
-> & {
+export type PutConnectorNameAndDescriptionResponse = Pick & {
indexName: string;
};
export const putConnectorNameAndDescription = async ({
connectorId,
- description,
+ description = null,
indexName,
- name,
+ name = '',
}: PutConnectorNameAndDescriptionArgs) => {
const route = `/internal/enterprise_search/connectors/${connectorId}/name_and_description`;
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_name_and_description/connector_name_and_description.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_name_and_description/connector_name_and_description.tsx
index d75482e25e784..9f6c96ce75e0a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_name_and_description/connector_name_and_description.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_name_and_description/connector_name_and_description.tsx
@@ -65,7 +65,7 @@ export const ConnectorNameAndDescription: React.FC = () => {
title: NAME_LABEL,
},
{
- description: description ?? '--',
+ description: description || '--',
title: DESCRIPTION_LABEL,
},
]}
diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts
index 0aaf30ef126d4..9663b216ec91c 100644
--- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts
@@ -254,8 +254,8 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
connectorId: schema.string(),
}),
body: schema.object({
- name: schema.maybe(schema.string()),
- description: schema.maybe(schema.string()),
+ name: schema.string(),
+ description: schema.nullable(schema.string()),
}),
},
},
From 24ce456ec1ebd7713dcfccd569f0397a6e393999 Mon Sep 17 00:00:00 2001
From: doakalexi <109488926+doakalexi@users.noreply.github.com>
Date: Tue, 4 Oct 2022 08:22:38 -0400
Subject: [PATCH 11/27] Fixing flaky test (#142498)
---
.../group2/tests/telemetry/alerting_and_actions_telemetry.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts
index c89e5b48b236b..b4cb36ab59d85 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts
@@ -235,8 +235,9 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F
// number of action executions broken down by connector type
expect(telemetry.count_actions_executions_by_type_per_day['test.throw'] > 0).to.be(true);
- // average execution time - just checking for non-zero as we can't set an exact number
- expect(telemetry.avg_execution_time_per_day > 0).to.be(true);
+ // average execution time - just checking for a positive number as we can't set an exact number
+ // if the time is less than 1ms it will round down to 0
+ expect(telemetry.avg_execution_time_per_day >= 0).to.be(true);
// average execution time broken down by rule type
expect(telemetry.avg_execution_time_by_type_per_day['test.throw'] > 0).to.be(true);
From 6f8e758f113d7f22cc1df1ec4449b3d0f01da50d Mon Sep 17 00:00:00 2001
From: Ying Mao
Date: Tue, 4 Oct 2022 08:25:32 -0400
Subject: [PATCH 12/27] =?UTF-8?q?Fixing=20Failing=20test:=20X-Pack=20Alert?=
=?UTF-8?q?ing=20API=20Integration=20Tests.x-pack/test/alerting=5Fapi=5Fin?=
=?UTF-8?q?tegration/security=5Fand=5Fspaces/group1/tests/alerting/disable?=
=?UTF-8?q?=C2=B7ts=20-=20alerting=20api=20integration=20security=20and=20?=
=?UTF-8?q?spaces=20enabled=20Alerts=20-=20Group=201=20alerts=20disable=20?=
=?UTF-8?q?superuser=20at=20space1=20should=20still=20be=20able=20to=20dis?=
=?UTF-8?q?able=20alert=20when=20AAD=20is=20broken=20(#142483)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Unskipping test
* Adding retries
---
.../group1/tests/alerting/disable.ts | 71 ++++++++++---------
1 file changed, 38 insertions(+), 33 deletions(-)
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts
index 860576f806e38..df9fc34e17014 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts
@@ -26,8 +26,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte
const supertest = getService('supertest');
const supertestWithoutAuth = getService('supertestWithoutAuth');
- // Failing: See https://github.com/elastic/kibana/issues/140797
- describe.skip('disable', () => {
+ describe('disable', () => {
const objectRemover = new ObjectRemover(supertest);
after(() => objectRemover.removeAll());
@@ -110,21 +109,23 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte
expect(response.body).to.eql('');
// task should still exist but be disabled
- const taskRecord2 = await getScheduledTask(createdAlert.scheduled_task_id);
- expect(taskRecord2.type).to.eql('task');
- expect(taskRecord2.task.taskType).to.eql('alerting:test.noop');
- expect(JSON.parse(taskRecord2.task.params)).to.eql({
- alertId: createdAlert.id,
- spaceId: space.id,
- consumer: 'alertsFixture',
- });
- expect(taskRecord2.task.enabled).to.eql(false);
- // Ensure AAD isn't broken
- await checkAAD({
- supertest,
- spaceId: space.id,
- type: 'alert',
- id: createdAlert.id,
+ await retry.try(async () => {
+ const taskRecord2 = await getScheduledTask(createdAlert.scheduled_task_id);
+ expect(taskRecord2.type).to.eql('task');
+ expect(taskRecord2.task.taskType).to.eql('alerting:test.noop');
+ expect(JSON.parse(taskRecord2.task.params)).to.eql({
+ alertId: createdAlert.id,
+ spaceId: space.id,
+ consumer: 'alertsFixture',
+ });
+ expect(taskRecord2.task.enabled).to.eql(false);
+ // Ensure AAD isn't broken
+ await checkAAD({
+ supertest,
+ spaceId: space.id,
+ type: 'alert',
+ id: createdAlert.id,
+ });
});
break;
default:
@@ -295,15 +296,17 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte
expect(response.statusCode).to.eql(204);
expect(response.body).to.eql('');
// task should still exist but be disabled
- const taskRecord = await getScheduledTask(createdAlert.scheduled_task_id);
- expect(taskRecord.type).to.eql('task');
- expect(taskRecord.task.taskType).to.eql('alerting:test.noop');
- expect(JSON.parse(taskRecord.task.params)).to.eql({
- alertId: createdAlert.id,
- spaceId: space.id,
- consumer: 'alerts',
+ await retry.try(async () => {
+ const taskRecord = await getScheduledTask(createdAlert.scheduled_task_id);
+ expect(taskRecord.type).to.eql('task');
+ expect(taskRecord.task.taskType).to.eql('alerting:test.noop');
+ expect(JSON.parse(taskRecord.task.params)).to.eql({
+ alertId: createdAlert.id,
+ spaceId: space.id,
+ consumer: 'alerts',
+ });
+ expect(taskRecord.task.enabled).to.eql(false);
});
- expect(taskRecord.task.enabled).to.eql(false);
break;
default:
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
@@ -366,15 +369,17 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte
expect(response.statusCode).to.eql(204);
expect(response.body).to.eql('');
// task should still exist but be disabled
- const taskRecord2 = await getScheduledTask(createdAlert.scheduled_task_id);
- expect(taskRecord2.type).to.eql('task');
- expect(taskRecord2.task.taskType).to.eql('alerting:test.noop');
- expect(JSON.parse(taskRecord2.task.params)).to.eql({
- alertId: createdAlert.id,
- spaceId: space.id,
- consumer: 'alertsFixture',
+ await retry.try(async () => {
+ const taskRecord2 = await getScheduledTask(createdAlert.scheduled_task_id);
+ expect(taskRecord2.type).to.eql('task');
+ expect(taskRecord2.task.taskType).to.eql('alerting:test.noop');
+ expect(JSON.parse(taskRecord2.task.params)).to.eql({
+ alertId: createdAlert.id,
+ spaceId: space.id,
+ consumer: 'alertsFixture',
+ });
+ expect(taskRecord2.task.enabled).to.eql(false);
});
- expect(taskRecord2.task.enabled).to.eql(false);
break;
default:
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
From fb6b10e23210a820f19e6e69684ba0b90730099e Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Tue, 4 Oct 2022 13:29:00 +0100
Subject: [PATCH 13/27] skip flaky suite (#142110)
---
.../api_integration/apis/uptime/rest/add_monitor_project.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts
index 9dce7e7d8fdaa..a8eec4c568dc9 100644
--- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts
+++ b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts
@@ -19,7 +19,8 @@ import { PrivateLocationTestService } from './services/private_location_test_ser
import { comparePolicies, getTestProjectSyntheticsPolicy } from './sample_data/test_policy';
export default function ({ getService }: FtrProviderContext) {
- describe('AddProjectMonitors', function () {
+ // FLAKY: https://github.com/elastic/kibana/issues/142110
+ describe.skip('AddProjectMonitors', function () {
this.tags('skipCloud');
const supertest = getService('supertest');
From b62c0f98cf8b82d4124518983abedf76babf5843 Mon Sep 17 00:00:00 2001
From: Rickyanto Ang
Date: Tue, 4 Oct 2022 05:33:39 -0700
Subject: [PATCH 14/27] added beta tag for linux config (#142171)
---
.../policy_forms/components/policy_form_layout.test.tsx | 7 +++++++
.../pages/policy/view/policy_forms/events/linux.tsx | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx
index 49a2e8173476a..60dc7bd29895a 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx
@@ -135,6 +135,13 @@ describe('Policy Form Layout', () => {
expect(saveButton).toHaveLength(1);
expect(saveButton.text()).toEqual('Save');
});
+ it('should display beta badge', async () => {
+ await asyncActions;
+ policyFormLayoutView.update();
+ const saveButton = policyFormLayoutView.find('EuiBetaBadge');
+ expect(saveButton).toHaveLength(1);
+ expect(saveButton.text()).toEqual('beta');
+ });
describe('when the save button is clicked', () => {
let saveButton: FindReactWrapperResponse;
let confirmModal: FindReactWrapperResponse;
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx
index 1b8e4f2040150..984bc53a014e3 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx
@@ -88,7 +88,7 @@ const SUPPLEMENTAL_OPTIONS: ReadonlyArray {
return !config.linux.events.session_data;
},
- beta: false,
+ beta: true,
},
];
From 6824718c0a15347395d8e49db23b934b475b142d Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Tue, 4 Oct 2022 14:46:53 +0200
Subject: [PATCH 15/27] [Synthetics] Increase project API payload limit
(#142140)
---
src/plugins/bfetch/server/plugin.ts | 8 ++++++--
.../server/routes/monitor_cruds/add_monitor_project.ts | 7 +++++++
x-pack/plugins/synthetics/server/server.ts | 5 +++--
.../plugins/synthetics/server/synthetics_route_wrapper.ts | 1 +
4 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/src/plugins/bfetch/server/plugin.ts b/src/plugins/bfetch/server/plugin.ts
index 0f51f5da62353..85720480cf9a0 100644
--- a/src/plugins/bfetch/server/plugin.ts
+++ b/src/plugins/bfetch/server/plugin.ts
@@ -20,6 +20,7 @@ import {
} from '@kbn/core/server';
import { schema } from '@kbn/config-schema';
import { map$ } from '@kbn/std';
+import { RouteConfigOptions } from '@kbn/core-http-server';
import {
StreamingResponseHandler,
BatchRequestData,
@@ -54,7 +55,8 @@ export interface BfetchServerSetup {
context: RequestHandlerContext
) => StreamingResponseHandler,
method?: 'GET' | 'POST' | 'PUT' | 'DELETE',
- pluginRouter?: ReturnType
+ pluginRouter?: ReturnType,
+ options?: RouteConfigOptions<'get' | 'post' | 'put' | 'delete'>
) => void;
}
@@ -117,14 +119,16 @@ export class BfetchServerPlugin
router: ReturnType;
logger: Logger;
}): BfetchServerSetup['addStreamingResponseRoute'] =>
- (path, handler, method = 'POST', pluginRouter) => {
+ (path, handler, method = 'POST', pluginRouter, options) => {
const httpRouter = pluginRouter || router;
+
const routeDefinition = {
path: `/${removeLeadingSlash(path)}`,
validate: {
body: schema.any(),
query: schema.object({ compress: schema.boolean({ defaultValue: false }) }),
},
+ options,
};
const routeHandler: RequestHandler = async (
context: RequestHandlerContext,
diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts
index 668d97a0819e3..ea269d87413e7 100644
--- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts
+++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts
@@ -13,6 +13,8 @@ import { API_URLS } from '../../../common/constants';
import { getAllLocations } from '../../synthetics_service/get_all_locations';
import { ProjectMonitorFormatter } from '../../synthetics_service/project_monitor/project_monitor_formatter';
+const MAX_PAYLOAD_SIZE = 1048576 * 20; // 20MiB
+
export const addSyntheticsProjectMonitorRoute: SyntheticsStreamingRouteFactory = (
libs: UMServerLibs
) => ({
@@ -25,6 +27,11 @@ export const addSyntheticsProjectMonitorRoute: SyntheticsStreamingRouteFactory =
monitors: schema.arrayOf(schema.any()),
}),
},
+ options: {
+ body: {
+ maxBytes: MAX_PAYLOAD_SIZE,
+ },
+ },
handler: async ({
request,
savedObjectsClient,
diff --git a/x-pack/plugins/synthetics/server/server.ts b/x-pack/plugins/synthetics/server/server.ts
index 12844c9cb9223..7f667e0fb264d 100644
--- a/x-pack/plugins/synthetics/server/server.ts
+++ b/x-pack/plugins/synthetics/server/server.ts
@@ -57,7 +57,7 @@ export const initSyntheticsServer = (
});
syntheticsAppStreamingApiRoutes.forEach((route) => {
- const { method, streamHandler, path } = syntheticsRouteWrapper(
+ const { method, streamHandler, path, options } = syntheticsRouteWrapper(
createSyntheticsRouteWithAuth(libs, route),
server,
syntheticsMonitorClient
@@ -82,7 +82,8 @@ export const initSyntheticsServer = (
};
},
method,
- server.router
+ server.router,
+ options
);
});
};
diff --git a/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts b/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts
index 8706735fa9256..fc1376e157607 100644
--- a/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts
+++ b/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts
@@ -19,6 +19,7 @@ export const syntheticsRouteWrapper: SyntheticsRouteWrapper = (
...uptimeRoute,
options: {
tags: ['access:uptime-read', ...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : [])],
+ ...(uptimeRoute.options ?? {}),
},
streamHandler: async (context, request, subject) => {
const coreContext = await context.core;
From 6875d18d0f1340d684e234ef866970bcb9ed087b Mon Sep 17 00:00:00 2001
From: Kurt
Date: Tue, 4 Oct 2022 08:50:12 -0400
Subject: [PATCH 16/27] Removing esArchiver in favor of testDataLoader for
`bulk_get` Saved Objects integration tests (#140998)
* Removing esArchiver in favor of testDataLoader
* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'
* Adding test data for loader
* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'
* Adding generic TestDataLoader
* Importing just the type per PR feedback
* Changing testDataLoader function names to be more descriptive
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Larry Gregory
Co-authored-by: Thomas Watson
---
.../common/lib/test_data_loader.ts | 46 ++---
.../fixtures/kbn_archiver/default_space.json | 163 ++++++++++++++++++
.../common/fixtures/kbn_archiver/space_1.json | 72 ++++++++
.../common/fixtures/kbn_archiver/space_2.json | 58 +++++++
.../common/suites/bulk_get.ts | 43 +++--
.../security_and_spaces/apis/bulk_get.ts | 11 +-
.../spaces_only/apis/bulk_get.ts | 7 +-
.../common/suites/copy_to_space.ts | 34 +++-
.../suites/resolve_copy_to_space_conflicts.ts | 29 +++-
9 files changed, 401 insertions(+), 62 deletions(-)
rename x-pack/test/{spaces_api_integration => }/common/lib/test_data_loader.ts (79%)
create mode 100644 x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json
create mode 100644 x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json
create mode 100644 x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json
diff --git a/x-pack/test/spaces_api_integration/common/lib/test_data_loader.ts b/x-pack/test/common/lib/test_data_loader.ts
similarity index 79%
rename from x-pack/test/spaces_api_integration/common/lib/test_data_loader.ts
rename to x-pack/test/common/lib/test_data_loader.ts
index 4b25c722603c8..61c8ff4c1bf52 100644
--- a/x-pack/test/spaces_api_integration/common/lib/test_data_loader.ts
+++ b/x-pack/test/common/lib/test_data_loader.ts
@@ -5,16 +5,14 @@
* 2.0.
*/
-import { FtrProviderContext } from '../ftr_provider_context';
-
-const SPACE_1 = {
+export const SPACE_1 = {
id: 'space_1',
name: 'Space 1',
description: 'This is the first test space',
disabledFeatures: [],
};
-const SPACE_2 = {
+export const SPACE_2 = {
id: 'space_2',
name: 'Space 2',
description: 'This is the second test space',
@@ -64,36 +62,38 @@ const OBJECTS_TO_SHARE: Array<{
},
];
-export function getTestDataLoader({ getService }: FtrProviderContext) {
+// @ts-ignore
+export function getTestDataLoader({ getService }) {
const spacesService = getService('spaces');
const kbnServer = getService('kibanaServer');
const supertest = getService('supertest');
const log = getService('log');
return {
- before: async () => {
+ createFtrSpaces: async () => {
await Promise.all([await spacesService.create(SPACE_1), await spacesService.create(SPACE_2)]);
},
- after: async () => {
+ deleteFtrSpaces: async () => {
await Promise.all([spacesService.delete(SPACE_1.id), spacesService.delete(SPACE_2.id)]);
},
- beforeEach: async () => {
+ createFtrSavedObjectsData: async (
+ spaceData: Array<{ spaceName: string | null; dataUrl: string }>
+ ) => {
log.debug('Loading test data for the following spaces: default, space_1 and space_2');
- await Promise.all([
- kbnServer.importExport.load(
- 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/default_space.json'
- ),
- kbnServer.importExport.load(
- 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_1.json',
- { space: SPACE_1.id }
- ),
- kbnServer.importExport.load(
- 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_2.json',
- { space: SPACE_2.id }
- ),
- ]);
+
+ await Promise.all(
+ spaceData.map((spaceDataObj) => {
+ if (spaceDataObj.spaceName) {
+ return kbnServer.importExport.load(spaceDataObj.dataUrl, {
+ space: spaceDataObj.spaceName,
+ });
+ } else {
+ return kbnServer.importExport.load(spaceDataObj.dataUrl);
+ }
+ })
+ );
// Adjust spaces for the imported saved objects.
for (const { objects, spacesToAdd = [], spacesToRemove = [] } of OBJECTS_TO_SHARE) {
@@ -111,9 +111,9 @@ export function getTestDataLoader({ getService }: FtrProviderContext) {
}
},
- afterEach: async () => {
+ deleteFtrSavedObjectsData: async () => {
const allSpacesIds = [
- ...(await spacesService.getAll()).map((space) => space.id),
+ ...(await spacesService.getAll()).map((space: { id: string }) => space.id),
'non_existent_space',
];
log.debug(`Removing data from the following spaces: ${allSpacesIds.join(', ')}`);
diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json
new file mode 100644
index 0000000000000..9a2713fc61872
--- /dev/null
+++ b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json
@@ -0,0 +1,163 @@
+{
+ "attributes": {
+ "title": "logstash-*"
+ },
+ "coreMigrationVersion": "8.4.0",
+ "id": "defaultspace-index-pattern-id",
+ "migrationVersion": {
+ "index-pattern": "8.0.0"
+ },
+ "originId": "cts_ip_1",
+ "references": [],
+ "type": "index-pattern",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzUyOCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "Count of requests",
+ "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}",
+ "version": 1,
+ "visState": "{\"title\":\"Count of requests\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}]}",
+ "description": "",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"index\":\"defaultspace-index-pattern-id\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}"
+ }
+ },
+ "id": "defaultspace-isolatedtype-id",
+ "references": [],
+ "type": "isolatedtype",
+ "updated_at": "2017-09-21T18:51:23.794Z",
+ "version": "WzQ4NywxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "Requests",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"
+ }
+ },
+ "coreMigrationVersion": "8.4.0",
+ "id": "defaultspace-dashboard-id",
+ "migrationVersion": {
+ "dashboard": "8.4.0"
+ },
+ "type": "dashboard",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzUyMCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A share-capable (isolated) saved-object only in the default space"
+ },
+ "id": "only_default_space",
+ "type": "sharecapabletype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A shared saved-object in all spaces"
+ },
+ "id": "all_spaces",
+ "type": "sharedtype",
+ "references": [],
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ5NywxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "My favorite global object"
+ },
+ "id": "globaltype-id",
+ "references": [],
+ "type": "globaltype",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzQ4NywxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A shared saved-object in the default and space_1 spaces"
+ },
+ "id": "default_and_space_1",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A sharedtype saved-object with id: conflict_1"
+ },
+ "id": "conflict_1",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A sharedtype saved-object with id: conflict_2a"
+ },
+ "id": "conflict_2a",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A sharedtype saved-object with id: conflict_2b"
+ },
+ "id": "conflict_2b",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A sharedtype saved-object with id: conflict_3"
+ },
+ "id": "conflict_3",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A sharedtype saved-object with id: conflict_4a"
+ },
+ "id": "conflict_4a",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "Resolve outcome exactMatch"
+ },
+ "id": "exact-match",
+ "type": "resolvetype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "Resolve outcome aliasMatch"
+ },
+ "id": "alias-match-newid",
+ "type": "resolvetype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json
new file mode 100644
index 0000000000000..6356d5c01989b
--- /dev/null
+++ b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json
@@ -0,0 +1,72 @@
+
+
+{
+ "attributes": {
+ "title": "logstash-*"
+ },
+ "coreMigrationVersion": "8.4.0",
+ "id": "space1-index-pattern-id",
+ "migrationVersion": {
+ "index-pattern": "8.0.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzUyOSwxXQ=="
+}
+
+{
+ "attributes": {
+ "description": "",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"index\":\"space1-index-pattern-id\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}"
+ },
+ "title": "Count of requests",
+ "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}",
+ "version": 1,
+ "visState": "{\"title\":\"Count of requests\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}]}"
+ },
+ "id": "space1-isolatedtype-id",
+ "references": [],
+ "type": "isolatedtype",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzQ4NywxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "Requests",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"
+ },
+ "version": 1
+ },
+ "coreMigrationVersion": "8.4.0",
+ "id": "space1-dashboard-id",
+ "migrationVersion": {
+ "dashboard": "8.4.0"
+ },
+ "type": "dashboard",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzUyMCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A shared saved-object only in space_1"
+ },
+ "id": "only_space_1",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A share-capable (isolated) saved-object only in space_1"
+ },
+ "id": "only_space_1",
+ "type": "sharecapabletype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json
new file mode 100644
index 0000000000000..9715a5f54d2b4
--- /dev/null
+++ b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json
@@ -0,0 +1,58 @@
+{
+ "attributes": {
+ "title": "logstash-*"
+ },
+ "coreMigrationVersion": "8.4.0",
+ "id": "space2-index-pattern-id",
+ "migrationVersion": {
+ "index-pattern": "8.0.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzUyOSwxXQ=="
+}
+
+{
+ "attributes": {
+ "description": "",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"index\":\"space2-index-pattern-id\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}"
+ },
+ "title": "Count of requests",
+ "version": 1
+ },
+ "id": "space2-isolatedtype-id",
+ "references": [],
+ "type": "isolatedtype",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzQ4NywxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "Requests",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"
+ },
+ "version": 1
+ },
+ "coreMigrationVersion": "8.4.0",
+ "id": "space2-dashboard-id",
+ "migrationVersion": {
+ "dashboard": "8.4.0"
+ },
+ "type": "dashboard",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzUyMCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A shared saved-object only in space_2"
+ },
+ "id": "only_space_2",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts
index 10709a6f20916..c9cb3b9739eee 100644
--- a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts
+++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts
@@ -6,11 +6,12 @@
*/
import expect from '@kbn/expect';
-import { SuperTest } from 'supertest';
+import { getTestDataLoader, SPACE_1, SPACE_2 } from '../../../common/lib/test_data_loader';
import { SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases';
import { SPACES } from '../lib/spaces';
import { expectResponses, getUrlPrefix, getTestTitle } from '../lib/saved_object_test_utils';
import { ExpectResponseBody, TestCase, TestDefinition, TestSuite } from '../lib/types';
+import type { FtrProviderContext } from '../ftr_provider_context';
export interface BulkGetTestDefinition extends TestDefinition {
request: Array<{ type: string; id: string }>;
@@ -33,7 +34,10 @@ const createRequest = ({ type, id, namespaces }: BulkGetTestCase) => ({
...(namespaces && { namespaces }), // individual "object namespaces" string array
});
-export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest) {
+export function bulkGetTestSuiteFactory(context: FtrProviderContext) {
+ const testDataLoader = getTestDataLoader(context);
+ const supertest = context.getService('supertestWithoutAuth');
+
const expectSavedObjectForbidden = expectResponses.forbiddenTypes('bulk_get');
const expectResponseBody =
(testCases: BulkGetTestCase | BulkGetTestCase[], statusCode: 200 | 403): ExpectResponseBody =>
@@ -91,16 +95,31 @@ export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest {
- before(() =>
- esArchiver.load(
- 'x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces'
- )
- );
- after(() =>
- esArchiver.unload(
- 'x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces'
- )
- );
+ before(async () => {
+ await testDataLoader.createFtrSpaces();
+ await testDataLoader.createFtrSavedObjectsData([
+ {
+ spaceName: null,
+ dataUrl:
+ 'x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json',
+ },
+ {
+ spaceName: SPACE_1.id,
+ dataUrl:
+ 'x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json',
+ },
+ {
+ spaceName: SPACE_2.id,
+ dataUrl:
+ 'x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json',
+ },
+ ]);
+ });
+
+ after(async () => {
+ await testDataLoader.deleteFtrSpaces();
+ await testDataLoader.deleteFtrSavedObjectsData();
+ });
for (const test of tests) {
it(`should return ${test.responseStatusCode} ${test.title}`, async () => {
diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts
index 2c1fbf442b0ec..ed251440d361a 100644
--- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts
+++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts
@@ -67,14 +67,9 @@ const createTestCases = (spaceId: string) => {
return { normalTypes, crossNamespace, hiddenType, allTypes };
};
-export default function ({ getService }: FtrProviderContext) {
- const supertest = getService('supertestWithoutAuth');
- const esArchiver = getService('esArchiver');
-
- const { addTests, createTestDefinitions, expectSavedObjectForbidden } = bulkGetTestSuiteFactory(
- esArchiver,
- supertest
- );
+export default function (context: FtrProviderContext) {
+ const { addTests, createTestDefinitions, expectSavedObjectForbidden } =
+ bulkGetTestSuiteFactory(context);
const createTests = (spaceId: string) => {
const { normalTypes, crossNamespace, hiddenType, allTypes } = createTestCases(spaceId);
// use singleRequest to reduce execution time and/or test combined cases
diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_get.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_get.ts
index 41fa4749cc48e..30ed220ea9ae3 100644
--- a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_get.ts
+++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_get.ts
@@ -55,11 +55,8 @@ const createTestCases = (spaceId: string) => [
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, namespaces: [ALL_SPACES_ID] },
];
-export default function ({ getService }: FtrProviderContext) {
- const supertest = getService('supertest');
- const esArchiver = getService('esArchiver');
-
- const { addTests, createTestDefinitions } = bulkGetTestSuiteFactory(esArchiver, supertest);
+export default function (context: FtrProviderContext) {
+ const { addTests, createTestDefinitions } = bulkGetTestSuiteFactory(context);
const createTests = (spaceId: string) => {
const testCases = createTestCases(spaceId);
return createTestDefinitions(testCases, false, { singleRequest: true });
diff --git a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts
index c781eff6d3272..4c5ae878bbf6e 100644
--- a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts
@@ -14,8 +14,8 @@ import {
} from '@kbn/core/server';
import { getAggregatedSpaceData, getUrlPrefix } from '../lib/space_test_utils';
import { DescribeFn, TestDefinitionAuthentication } from '../lib/types';
-import { getTestDataLoader } from '../lib/test_data_loader';
-import { FtrProviderContext } from '../ftr_provider_context';
+import { getTestDataLoader, SPACE_1, SPACE_2 } from '../../../common/lib/test_data_loader';
+import type { FtrProviderContext } from '../ftr_provider_context';
type TestResponse = Record;
@@ -74,6 +74,21 @@ const UUID_PATTERN = new RegExp(
/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i
);
+const SPACE_DATA_TO_LOAD: Array<{ spaceName: string | null; dataUrl: string }> = [
+ {
+ spaceName: null,
+ dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/default_space.json',
+ },
+ {
+ spaceName: SPACE_1.id,
+ dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_1.json',
+ },
+ {
+ spaceName: SPACE_2.id,
+ dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_2.json',
+ },
+];
+
const getDestinationWithoutConflicts = () => 'space_2';
const getDestinationWithConflicts = (originSpaceId?: string) =>
!originSpaceId || originSpaceId === DEFAULT_SPACE_ID ? 'space_1' : DEFAULT_SPACE_ID;
@@ -748,16 +763,19 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) {
// test data only allows for the following spaces as the copy origin
expect(['default', 'space_1']).to.contain(spaceId);
- await testDataLoader.before();
+ await testDataLoader.createFtrSpaces();
});
after(async () => {
- await testDataLoader.after();
+ await testDataLoader.deleteFtrSpaces();
});
describe('single-namespace types', () => {
- beforeEach(async () => await testDataLoader.beforeEach());
- afterEach(async () => await testDataLoader.afterEach());
+ beforeEach(async () => {
+ await testDataLoader.createFtrSavedObjectsData(SPACE_DATA_TO_LOAD);
+ });
+
+ afterEach(async () => await testDataLoader.deleteFtrSavedObjectsData());
const dashboardObject = { type: 'dashboard', id: `cts_dashboard_${spaceId}` };
@@ -898,8 +916,8 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) {
const spaces = ['space_2'];
const includeReferences = false;
describe(`multi-namespace types with overwrite=${overwrite} and createNewCopies=${createNewCopies}`, () => {
- before(async () => await testDataLoader.beforeEach());
- after(async () => await testDataLoader.afterEach());
+ before(async () => await testDataLoader.createFtrSavedObjectsData(SPACE_DATA_TO_LOAD));
+ after(async () => await testDataLoader.deleteFtrSavedObjectsData());
const testCases = tests.multiNamespaceTestCases(overwrite, createNewCopies);
testCases.forEach(({ testTitle, objects, statusCode, response }) => {
diff --git a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts
index 58a434bd0ca91..5f2c361714c49 100644
--- a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts
@@ -11,8 +11,8 @@ import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants';
import { CopyResponse } from '@kbn/spaces-plugin/server/lib/copy_to_spaces';
import { getUrlPrefix } from '../lib/space_test_utils';
import { DescribeFn, TestDefinitionAuthentication } from '../lib/types';
-import { FtrProviderContext } from '../ftr_provider_context';
-import { getTestDataLoader } from '../lib/test_data_loader';
+import type { FtrProviderContext } from '../ftr_provider_context';
+import { getTestDataLoader, SPACE_1, SPACE_2 } from '../../../common/lib/test_data_loader';
type TestResponse = Record;
@@ -44,6 +44,21 @@ interface ResolveCopyToSpaceTestDefinition {
const NON_EXISTENT_SPACE_ID = 'non_existent_space';
+const SPACE_DATA_TO_LOAD: Array<{ spaceName: string | null; dataUrl: string }> = [
+ {
+ spaceName: null,
+ dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/default_space.json',
+ },
+ {
+ spaceName: SPACE_1.id,
+ dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_1.json',
+ },
+ {
+ spaceName: SPACE_2.id,
+ dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_2.json',
+ },
+];
+
const getDestinationSpace = (originSpaceId?: string) => {
if (!originSpaceId || originSpaceId === DEFAULT_SPACE_ID) {
return 'space_1';
@@ -487,8 +502,10 @@ export function resolveCopyToSpaceConflictsSuite(context: FtrProviderContext) {
});
describe('single-namespace types', () => {
- beforeEach(async () => await testDataLoader.beforeEach());
- afterEach(async () => await testDataLoader.afterEach());
+ beforeEach(
+ async () => await testDataLoader.createFtrSavedObjectsData(SPACE_DATA_TO_LOAD)
+ );
+ afterEach(async () => await testDataLoader.deleteFtrSavedObjectsData());
const dashboardObject = { type: 'dashboard', id: `cts_dashboard_${spaceId}` };
const visualizationObject = { type: 'visualization', id: `cts_vis_3_${spaceId}` };
@@ -630,8 +647,8 @@ export function resolveCopyToSpaceConflictsSuite(context: FtrProviderContext) {
const includeReferences = false;
const createNewCopies = false;
describe(`multi-namespace types with "overwrite" retry`, () => {
- before(async () => await testDataLoader.beforeEach());
- after(async () => await testDataLoader.afterEach());
+ before(async () => await testDataLoader.createFtrSavedObjectsData(SPACE_DATA_TO_LOAD));
+ after(async () => await testDataLoader.deleteFtrSavedObjectsData());
const testCases = tests.multiNamespaceTestCases();
testCases.forEach(({ testTitle, objects, retries, statusCode, response }) => {
From f5f60b640b935b86ee76e6c808a65b56be93edb1 Mon Sep 17 00:00:00 2001
From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com>
Date: Tue, 4 Oct 2022 18:04:28 +0500
Subject: [PATCH 17/27] [Console] Refactor Console settings toggles to follow
best practices (#140902)
* Refactor settings modal labels
* Fix checks
* Update related test case
* Migrate old settings to new ones
* Refactor migrate fn to be more generic
Co-authored-by: Muhammad Ibragimov
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../application/components/settings_modal.tsx | 28 ++++-----
.../editor/legacy/console_editor/editor.tsx | 4 +-
.../use_send_current_request.test.tsx | 4 +-
.../use_send_current_request.ts | 6 +-
.../console/public/services/settings.ts | 62 ++++++++++++-------
.../translations/translations/fr-FR.json | 2 -
.../translations/translations/ja-JP.json | 2 -
.../translations/translations/zh-CN.json | 2 -
8 files changed, 63 insertions(+), 47 deletions(-)
diff --git a/src/plugins/console/public/application/components/settings_modal.tsx b/src/plugins/console/public/application/components/settings_modal.tsx
index 095dde1c29507..67b0e2c0d957a 100644
--- a/src/plugins/console/public/application/components/settings_modal.tsx
+++ b/src/plugins/console/public/application/components/settings_modal.tsx
@@ -77,9 +77,9 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
const [polling, setPolling] = useState(props.settings.polling);
const [pollInterval, setPollInterval] = useState(props.settings.pollInterval);
const [tripleQuotes, setTripleQuotes] = useState(props.settings.tripleQuotes);
- const [isHistoryDisabled, setIsHistoryDisabled] = useState(props.settings.isHistoryDisabled);
- const [isKeyboardShortcutsDisabled, setIsKeyboardShortcutsDisabled] = useState(
- props.settings.isKeyboardShortcutsDisabled
+ const [isHistoryEnabled, setIsHistoryEnabled] = useState(props.settings.isHistoryEnabled);
+ const [isKeyboardShortcutsEnabled, setIsKeyboardShortcutsEnabled] = useState(
+ props.settings.isKeyboardShortcutsEnabled
);
const autoCompleteCheckboxes = [
@@ -140,8 +140,8 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
polling,
pollInterval,
tripleQuotes,
- isHistoryDisabled,
- isKeyboardShortcutsDisabled,
+ isHistoryEnabled,
+ isKeyboardShortcutsEnabled,
});
}
@@ -153,17 +153,17 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
}, []);
const toggleKeyboardShortcuts = useCallback(
- (isDisabled: boolean) => {
+ (isEnabled: boolean) => {
if (props.editorInstance) {
unregisterCommands(props.editorInstance);
- setIsKeyboardShortcutsDisabled(isDisabled);
+ setIsKeyboardShortcutsEnabled(isEnabled);
}
},
[props.editorInstance]
);
const toggleSavingToHistory = useCallback(
- (isDisabled: boolean) => setIsHistoryDisabled(isDisabled),
+ (isEnabled: boolean) => setIsHistoryEnabled(isEnabled),
[]
);
@@ -289,11 +289,11 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
}
>
}
onChange={(e) => toggleSavingToHistory(e.target.checked)}
@@ -309,11 +309,11 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
}
>
}
onChange={(e) => toggleKeyboardShortcuts(e.target.checked)}
diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx
index 74a052646e198..ed8c87b5df147 100644
--- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx
+++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx
@@ -259,8 +259,8 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) {
}, [settings]);
useEffect(() => {
- const { isKeyboardShortcutsDisabled } = settings;
- if (!isKeyboardShortcutsDisabled) {
+ const { isKeyboardShortcutsEnabled } = settings;
+ if (isKeyboardShortcutsEnabled) {
registerCommands({
senseEditor: editorInstanceRef.current!,
sendCurrentRequest,
diff --git a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx
index 0c7e4c46d95a6..e895ddc135db8 100644
--- a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx
+++ b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx
@@ -106,7 +106,9 @@ describe('useSendCurrentRequest', () => {
(sendRequest as jest.Mock).mockReturnValue(
[{ request: {} }, { request: {} }] /* two responses to save history */
);
- (mockContextValue.services.settings.toJSON as jest.Mock).mockReturnValue({});
+ (mockContextValue.services.settings.toJSON as jest.Mock).mockReturnValue({
+ isHistoryEnabled: true,
+ });
(mockContextValue.services.history.addToHistory as jest.Mock).mockImplementation(() => {
// Mock throwing
throw new Error('cannot save!');
diff --git a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts
index 87f72571a63e6..28d875c246ca3 100644
--- a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts
+++ b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts
@@ -52,9 +52,9 @@ export const useSendCurrentRequest = () => {
const results = await sendRequest({ http, requests });
let saveToHistoryError: undefined | Error;
- const { isHistoryDisabled } = settings.toJSON();
+ const { isHistoryEnabled } = settings.toJSON();
- if (!isHistoryDisabled) {
+ if (isHistoryEnabled) {
results.forEach(({ request: { path, method, data } }) => {
try {
history.addToHistory(path, method, data);
@@ -84,7 +84,7 @@ export const useSendCurrentRequest = () => {
notifications.toasts.remove(toast);
},
onDisableSavingToHistory: () => {
- settings.setIsHistoryDisabled(true);
+ settings.setIsHistoryEnabled(false);
notifications.toasts.remove(toast);
},
}),
diff --git a/src/plugins/console/public/services/settings.ts b/src/plugins/console/public/services/settings.ts
index aa2280f06064f..e4731dd3f3a31 100644
--- a/src/plugins/console/public/services/settings.ts
+++ b/src/plugins/console/public/services/settings.ts
@@ -15,8 +15,8 @@ export const DEFAULT_SETTINGS = Object.freeze({
tripleQuotes: true,
wrapMode: true,
autocomplete: Object.freeze({ fields: true, indices: true, templates: true, dataStreams: true }),
- isHistoryDisabled: false,
- isKeyboardShortcutsDisabled: false,
+ isHistoryEnabled: true,
+ isKeyboardShortcutsEnabled: true,
});
export interface DevToolsSettings {
@@ -31,8 +31,8 @@ export interface DevToolsSettings {
polling: boolean;
pollInterval: number;
tripleQuotes: boolean;
- isHistoryDisabled: boolean;
- isKeyboardShortcutsDisabled: boolean;
+ isHistoryEnabled: boolean;
+ isKeyboardShortcutsEnabled: boolean;
}
enum SettingKeys {
@@ -42,12 +42,32 @@ enum SettingKeys {
AUTOCOMPLETE_SETTINGS = 'autocomplete_settings',
CONSOLE_POLLING = 'console_polling',
POLL_INTERVAL = 'poll_interval',
- IS_HISTORY_DISABLED = 'is_history_disabled',
- IS_KEYBOARD_SHORTCUTS_DISABLED = 'is_keyboard_shortcuts_disabled',
+ IS_HISTORY_ENABLED = 'is_history_enabled',
+ IS_KEYBOARD_SHORTCUTS_ENABLED = 'is_keyboard_shortcuts_enabled',
}
export class Settings {
- constructor(private readonly storage: Storage) {}
+ constructor(private readonly storage: Storage) {
+ // Migration from old settings to new ones
+ this.addMigrationRule('is_history_disabled', SettingKeys.IS_HISTORY_ENABLED, (value: any) => {
+ return !value;
+ });
+ this.addMigrationRule(
+ 'is_keyboard_shortcuts_disabled',
+ SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED,
+ (value: any) => {
+ return !value;
+ }
+ );
+ }
+
+ private addMigrationRule(previousKey: string, newKey: string, migration: (value: any) => any) {
+ const value = this.storage.get(previousKey);
+ if (value !== undefined) {
+ this.storage.set(newKey, migration(value));
+ this.storage.delete(previousKey);
+ }
+ }
getFontSize() {
return this.storage.get(SettingKeys.FONT_SIZE, DEFAULT_SETTINGS.fontSize);
@@ -94,13 +114,13 @@ export class Settings {
return true;
}
- setIsHistoryDisabled(isDisabled: boolean) {
- this.storage.set(SettingKeys.IS_HISTORY_DISABLED, isDisabled);
+ setIsHistoryEnabled(isEnabled: boolean) {
+ this.storage.set(SettingKeys.IS_HISTORY_ENABLED, isEnabled);
return true;
}
- getIsHistoryDisabled() {
- return this.storage.get(SettingKeys.IS_HISTORY_DISABLED, DEFAULT_SETTINGS.isHistoryDisabled);
+ getIsHistoryEnabled() {
+ return this.storage.get(SettingKeys.IS_HISTORY_ENABLED, DEFAULT_SETTINGS.isHistoryEnabled);
}
setPollInterval(interval: number) {
@@ -111,15 +131,15 @@ export class Settings {
return this.storage.get(SettingKeys.POLL_INTERVAL, DEFAULT_SETTINGS.pollInterval);
}
- setIsKeyboardShortcutsDisabled(disable: boolean) {
- this.storage.set(SettingKeys.IS_KEYBOARD_SHORTCUTS_DISABLED, disable);
+ setIsKeyboardShortcutsEnabled(isEnabled: boolean) {
+ this.storage.set(SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED, isEnabled);
return true;
}
getIsKeyboardShortcutsDisabled() {
return this.storage.get(
- SettingKeys.IS_KEYBOARD_SHORTCUTS_DISABLED,
- DEFAULT_SETTINGS.isKeyboardShortcutsDisabled
+ SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED,
+ DEFAULT_SETTINGS.isKeyboardShortcutsEnabled
);
}
@@ -131,8 +151,8 @@ export class Settings {
fontSize: parseFloat(this.getFontSize()),
polling: Boolean(this.getPolling()),
pollInterval: this.getPollInterval(),
- isHistoryDisabled: Boolean(this.getIsHistoryDisabled()),
- isKeyboardShortcutsDisabled: Boolean(this.getIsKeyboardShortcutsDisabled()),
+ isHistoryEnabled: Boolean(this.getIsHistoryEnabled()),
+ isKeyboardShortcutsEnabled: Boolean(this.getIsKeyboardShortcutsDisabled()),
};
}
@@ -143,8 +163,8 @@ export class Settings {
autocomplete,
polling,
pollInterval,
- isHistoryDisabled,
- isKeyboardShortcutsDisabled,
+ isHistoryEnabled,
+ isKeyboardShortcutsEnabled,
}: DevToolsSettings) {
this.setFontSize(fontSize);
this.setWrapMode(wrapMode);
@@ -152,8 +172,8 @@ export class Settings {
this.setAutocomplete(autocomplete);
this.setPolling(polling);
this.setPollInterval(pollInterval);
- this.setIsHistoryDisabled(isHistoryDisabled);
- this.setIsKeyboardShortcutsDisabled(isKeyboardShortcutsDisabled);
+ this.setIsHistoryEnabled(isHistoryEnabled);
+ this.setIsKeyboardShortcutsEnabled(isKeyboardShortcutsEnabled);
}
}
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index a5b6ae699c723..006c2744d4eb0 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -260,7 +260,6 @@
"console.settingsPage.autocompleteLabel": "Saisie semi-automatique",
"console.settingsPage.cancelButtonLabel": "Annuler",
"console.settingsPage.dataStreamsLabelText": "Flux de données",
- "console.settingsPage.disableKeyboardShortcutsMessage": "Désactiver les raccourcis clavier",
"console.settingsPage.fieldsLabelText": "Champs",
"console.settingsPage.fontSizeLabel": "Taille de la police",
"console.settingsPage.historyLabel": "Historique",
@@ -274,7 +273,6 @@
"console.settingsPage.refreshInterval.everyHourTimeInterval": "Toutes les heures",
"console.settingsPage.refreshInterval.onceTimeInterval": "Une fois, au chargement de la console",
"console.settingsPage.saveButtonLabel": "Enregistrer",
- "console.settingsPage.savingRequestsToHistoryMessage": "Désactiver l'enregistrement des requêtes dans l'historique",
"console.settingsPage.templatesLabelText": "Modèles",
"console.settingsPage.tripleQuotesMessage": "Utiliser des guillemets triples dans le volet de sortie",
"console.settingsPage.wrapLongLinesLabelText": "Formater les longues lignes",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 72e20dc6efa37..4601cc9bb3919 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -260,7 +260,6 @@
"console.settingsPage.autocompleteLabel": "自動入力",
"console.settingsPage.cancelButtonLabel": "キャンセル",
"console.settingsPage.dataStreamsLabelText": "データストリーム",
- "console.settingsPage.disableKeyboardShortcutsMessage": "キーボードショートカットを無効にする",
"console.settingsPage.fieldsLabelText": "フィールド",
"console.settingsPage.fontSizeLabel": "フォントサイズ",
"console.settingsPage.historyLabel": "履歴",
@@ -274,7 +273,6 @@
"console.settingsPage.refreshInterval.everyHourTimeInterval": "毎時",
"console.settingsPage.refreshInterval.onceTimeInterval": "コンソールの読み込み時に1回",
"console.settingsPage.saveButtonLabel": "保存",
- "console.settingsPage.savingRequestsToHistoryMessage": "履歴へのリクエストの保存を無効にしてください",
"console.settingsPage.templatesLabelText": "テンプレート",
"console.settingsPage.tripleQuotesMessage": "出力ウィンドウでは三重引用符を使用してください",
"console.settingsPage.wrapLongLinesLabelText": "長い行を改行",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index bf90d943ab06b..3181f9602038b 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -260,7 +260,6 @@
"console.settingsPage.autocompleteLabel": "自动完成",
"console.settingsPage.cancelButtonLabel": "取消",
"console.settingsPage.dataStreamsLabelText": "数据流",
- "console.settingsPage.disableKeyboardShortcutsMessage": "禁用键盘快捷键",
"console.settingsPage.fieldsLabelText": "字段",
"console.settingsPage.fontSizeLabel": "字体大小",
"console.settingsPage.historyLabel": "历史记录",
@@ -274,7 +273,6 @@
"console.settingsPage.refreshInterval.everyHourTimeInterval": "每小时",
"console.settingsPage.refreshInterval.onceTimeInterval": "一次,控制台加载时",
"console.settingsPage.saveButtonLabel": "保存",
- "console.settingsPage.savingRequestsToHistoryMessage": "禁止将请求保存到历史记录",
"console.settingsPage.templatesLabelText": "模板",
"console.settingsPage.tripleQuotesMessage": "在输出窗格中使用三重引号",
"console.settingsPage.wrapLongLinesLabelText": "长行换行",
From 890bf7430cd9a62ee1d2f46c16f486bfb6aebd59 Mon Sep 17 00:00:00 2001
From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com>
Date: Tue, 4 Oct 2022 09:09:55 -0400
Subject: [PATCH 18/27] [Security Solution][Analyzer] Make all analyzer apis
have time range as optional (#142536)
---
.../common/endpoint/schema/resolver.ts | 10 ++-
.../e2e/detection_alerts/resolver.cy.ts | 4 +-
.../resolver/data_access_layer/factory.ts | 75 +++++++++----------
.../data_access_layer/mocks/generator_tree.ts | 8 +-
.../mocks/no_ancestors_two_children.ts | 6 +-
..._children_in_index_called_awesome_index.ts | 6 +-
..._children_with_related_events_on_origin.ts | 6 +-
.../one_node_with_paginated_related_events.ts | 6 +-
.../current_related_event_fetcher.ts | 5 +-
.../store/middleware/node_data_fetcher.ts | 4 +-
.../middleware/related_events_fetcher.ts | 4 +-
.../store/middleware/resolver_tree_fetcher.ts | 6 +-
.../public/resolver/types.ts | 8 +-
.../routes/resolver/queries/events.ts | 55 +++-----------
.../routes/resolver/tree/queries/base.ts | 16 ++--
15 files changed, 98 insertions(+), 121 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts b/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts
index 15c89c8cd9c28..6de81d3e95a55 100644
--- a/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts
@@ -58,10 +58,12 @@ export const validateEvents = {
afterEvent: schema.maybe(schema.string()),
}),
body: schema.object({
- timeRange: schema.object({
- from: schema.string(),
- to: schema.string(),
- }),
+ timeRange: schema.maybe(
+ schema.object({
+ from: schema.string(),
+ to: schema.string(),
+ })
+ ),
indexPatterns: schema.arrayOf(schema.string()),
filter: schema.maybe(schema.string()),
entityType: schema.maybe(schema.string()),
diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts
index aa2263b9b518c..c2436f3f2de9a 100644
--- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts
+++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts
@@ -28,12 +28,12 @@ describe('Analyze events view for alerts', () => {
waitForAlertsToPopulate();
});
- it('should render analyzer when button is clicked', () => {
+ it('should render when button is clicked', () => {
openAnalyzerForFirstAlertInTimeline();
cy.get(ANALYZER_NODE).first().should('be.visible');
});
- it(`should render an analyzer view and display
+ it(`should display
a toast indicating the date range of found events when a time range has 0 events in it`, () => {
const dateContainingZeroEvents = 'Jul 27, 2022 @ 00:00:00.000';
setStartDate(dateContainingZeroEvents);
diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts
index 04e694b2cedbb..719fdedb73546 100644
--- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts
+++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts
@@ -17,6 +17,17 @@ import type {
ResolverSchema,
} from '../../../common/endpoint/types';
+function getRangeFilter(timeRange: TimeRange | undefined) {
+ return timeRange
+ ? {
+ timeRange: {
+ from: timeRange.from,
+ to: timeRange.to,
+ },
+ }
+ : [];
+}
+
/**
* The data access layer for resolver. All communication with the Kibana server is done through this object. This object is provided to Resolver. In tests, a mock data access layer can be used instead.
*/
@@ -34,7 +45,7 @@ export function dataAccessLayerFactory(
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
const response: ResolverPaginatedEvents = await context.services.http.post(
@@ -43,10 +54,7 @@ export function dataAccessLayerFactory(
query: {},
body: JSON.stringify({
indexPatterns,
- timeRange: {
- from: timeRange.from,
- to: timeRange.to,
- },
+ ...getRangeFilter(timeRange),
filter: JSON.stringify({
bool: {
filter: [
@@ -76,16 +84,13 @@ export function dataAccessLayerFactory(
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
const commonFields = {
query: { afterEvent: after, limit: 25 },
body: {
- timeRange: {
- from: timeRange.from,
- to: timeRange.to,
- },
+ ...getRangeFilter(timeRange),
indexPatterns,
},
};
@@ -127,30 +132,28 @@ export function dataAccessLayerFactory(
limit,
}: {
ids: string[];
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
limit: number;
}): Promise {
- const response: ResolverPaginatedEvents = await context.services.http.post(
- '/api/endpoint/resolver/events',
- {
- query: { limit },
- body: JSON.stringify({
- timeRange: {
- from: timeRange.from,
- to: timeRange.to,
+ const query = {
+ query: { limit },
+ body: JSON.stringify({
+ indexPatterns,
+ ...getRangeFilter(timeRange),
+ filter: JSON.stringify({
+ bool: {
+ filter: [
+ { terms: { 'process.entity_id': ids } },
+ { term: { 'event.category': 'process' } },
+ ],
},
- indexPatterns,
- filter: JSON.stringify({
- bool: {
- filter: [
- { terms: { 'process.entity_id': ids } },
- { term: { 'event.category': 'process' } },
- ],
- },
- }),
}),
- }
+ }),
+ };
+ const response: ResolverPaginatedEvents = await context.services.http.post(
+ '/api/endpoint/resolver/events',
+ query
);
return response.events;
},
@@ -172,7 +175,7 @@ export function dataAccessLayerFactory(
eventTimestamp: string;
eventID?: string | number;
winlogRecordID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
/** @description - eventID isn't provided by winlog. This can be removed once runtime fields are available */
@@ -200,10 +203,7 @@ export function dataAccessLayerFactory(
query: { limit: 1 },
body: JSON.stringify({
indexPatterns,
- timeRange: {
- from: timeRange.from,
- to: timeRange.to,
- },
+ ...getRangeFilter(timeRange),
filter: JSON.stringify(filter),
}),
}
@@ -217,10 +217,7 @@ export function dataAccessLayerFactory(
query: { limit: 1 },
body: JSON.stringify({
indexPatterns,
- timeRange: {
- from: timeRange.from,
- to: timeRange.to,
- },
+ ...getRangeFilter(timeRange),
entityType: 'alertDetail',
eventID,
}),
@@ -250,7 +247,7 @@ export function dataAccessLayerFactory(
}: {
dataId: string;
schema: ResolverSchema;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indices: string[];
ancestors: number;
descendants: number;
diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts
index 130b81c5622b2..6b833c93704b4 100644
--- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts
+++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts
@@ -63,7 +63,7 @@ export function generateTreeWithDAL(
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
const node = allNodes.get(entityID);
@@ -88,7 +88,7 @@ export function generateTreeWithDAL(
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> {
const node = allNodes.get(entityID);
@@ -119,7 +119,7 @@ export function generateTreeWithDAL(
eventCategory: string[];
eventTimestamp: string;
eventID?: string | number;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return null;
@@ -135,7 +135,7 @@ export function generateTreeWithDAL(
limit,
}: {
ids: string[];
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
limit: number;
}): Promise {
diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts
index 000d08b4e15c7..e883a96b162e8 100644
--- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts
+++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts
@@ -59,7 +59,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return Promise.resolve({
@@ -83,7 +83,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise<{
events: SafeResolverEvent[];
@@ -110,7 +110,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me
eventTimestamp: string;
eventID?: string | number;
winlogRecordID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return null;
diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts
index 808c4463f3a89..c4c7fda097e8f 100644
--- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts
+++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts
@@ -64,7 +64,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): {
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return Promise.resolve({
@@ -90,7 +90,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): {
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise<{
events: SafeResolverEvent[];
@@ -121,7 +121,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): {
eventTimestamp: string;
eventID?: string | number;
winlogRecordID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return mockEndpointEvent({
diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts
index 774111baf165d..30f7e07bf041a 100644
--- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts
+++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts
@@ -67,7 +67,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): {
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
/**
@@ -97,7 +97,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): {
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> {
const events =
@@ -129,7 +129,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): {
eventTimestamp: string;
eventID?: string | number;
winlogRecordID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return relatedEvents.events.find((event) => eventModel.eventID(event) === eventID) ?? null;
diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts
index 7eb8c28a433e3..dc7031acdbd91 100644
--- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts
+++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts
@@ -58,7 +58,7 @@ export function oneNodeWithPaginatedEvents(): {
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
/**
@@ -86,7 +86,7 @@ export function oneNodeWithPaginatedEvents(): {
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> {
let events: SafeResolverEvent[] = [];
@@ -121,7 +121,7 @@ export function oneNodeWithPaginatedEvents(): {
eventTimestamp: string;
eventID?: string | number;
winlogRecordID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return mockTree.events.find((event) => eventModel.eventID(event) === eventID) ?? null;
diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts
index 6b58dd4e8e62e..cd4119f9569e7 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts
@@ -48,8 +48,9 @@ export function CurrentRelatedEventFetcher(
api.dispatch({
type: 'appRequestedCurrentRelatedEventData',
});
- const timeRangeFilters = selectors.timeRangeFilters(state);
-
+ const detectedBounds = selectors.detectedBounds(state);
+ const timeRangeFilters =
+ detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state);
let result: SafeResolverEvent | null = null;
try {
result = await dataAccessLayer.event({
diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts
index c3173b3238737..9a3a9eb3450fd 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts
@@ -60,7 +60,9 @@ export function NodeDataFetcher(
let results: SafeResolverEvent[] | undefined;
try {
- const timeRangeFilters = selectors.timeRangeFilters(state);
+ const detectedBounds = selectors.detectedBounds(state);
+ const timeRangeFilters =
+ detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state);
results = await dataAccessLayer.nodeData({
ids: Array.from(newIDsToRequest),
timeRange: timeRangeFilters,
diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts
index ec0f068b5425c..ab8f71940104e 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts
@@ -30,7 +30,9 @@ export function RelatedEventsFetcher(
const indices = selectors.eventIndices(state);
const oldParams = last;
- const timeRangeFilters = selectors.timeRangeFilters(state);
+ const detectedBounds = selectors.detectedBounds(state);
+ const timeRangeFilters =
+ detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state);
// Update this each time before fetching data (or even if we don't fetch data) so that subsequent actions that call this (concurrently) will have up to date info.
last = newParams;
diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts
index e4da1af5f4d79..61319158fccc2 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts
@@ -93,9 +93,9 @@ export function ResolverTreeFetcher(
descendants: descendantsRequestAmount(),
});
if (unboundedTree.length > 0) {
- const timestamps = unboundedTree.map((event) =>
- firstNonNullValue(event.data['@timestamp'])
- );
+ const timestamps = unboundedTree
+ .map((event) => firstNonNullValue(event.data['@timestamp']))
+ .sort();
const oldestTimestamp = timestamps[0];
const newestTimestamp = timestamps.slice(-1);
api.dispatch({
diff --git a/x-pack/plugins/security_solution/public/resolver/types.ts b/x-pack/plugins/security_solution/public/resolver/types.ts
index 00ecd995176eb..88e97f416dc49 100644
--- a/x-pack/plugins/security_solution/public/resolver/types.ts
+++ b/x-pack/plugins/security_solution/public/resolver/types.ts
@@ -692,7 +692,7 @@ export interface DataAccessLayer {
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}) => Promise;
@@ -710,7 +710,7 @@ export interface DataAccessLayer {
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}) => Promise;
@@ -725,7 +725,7 @@ export interface DataAccessLayer {
limit,
}: {
ids: string[];
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
limit: number;
}): Promise;
@@ -747,7 +747,7 @@ export interface DataAccessLayer {
eventTimestamp: string;
eventID?: string | number;
winlogRecordID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}) => Promise;
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts
index ba4f682423670..869ae911ad890 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts
@@ -11,31 +11,22 @@ import type { JsonObject, JsonValue } from '@kbn/utility-types';
import { parseFilterQuery } from '../../../../utils/serialized_query';
import type { SafeResolverEvent } from '../../../../../common/endpoint/types';
import type { PaginationBuilder } from '../utils/pagination';
-
-interface TimeRange {
- from: string;
- to: string;
-}
+import { BaseResolverQuery } from '../tree/queries/base';
+import type { ResolverQueryParams } from '../tree/queries/base';
/**
* Builds a query for retrieving events.
*/
-export class EventsQuery {
- private readonly pagination: PaginationBuilder;
- private readonly indexPatterns: string | string[];
- private readonly timeRange: TimeRange;
+export class EventsQuery extends BaseResolverQuery {
+ readonly pagination: PaginationBuilder;
constructor({
- pagination,
indexPatterns,
timeRange,
- }: {
- pagination: PaginationBuilder;
- indexPatterns: string | string[];
- timeRange: TimeRange;
- }) {
+ isInternalRequest,
+ pagination,
+ }: ResolverQueryParams & { pagination: PaginationBuilder }) {
+ super({ indexPatterns, timeRange, isInternalRequest });
this.pagination = pagination;
- this.indexPatterns = indexPatterns;
- this.timeRange = timeRange;
}
private query(filters: JsonObject[]): JsonObject {
@@ -44,15 +35,7 @@ export class EventsQuery {
bool: {
filter: [
...filters,
- {
- range: {
- '@timestamp': {
- gte: this.timeRange.from,
- lte: this.timeRange.to,
- format: 'strict_date_optional_time',
- },
- },
- },
+ ...this.getRangeFilter(),
{
term: { 'event.kind': 'event' },
},
@@ -71,15 +54,7 @@ export class EventsQuery {
{
term: { 'event.id': id },
},
- {
- range: {
- '@timestamp': {
- gte: this.timeRange.from,
- lte: this.timeRange.to,
- format: 'strict_date_optional_time',
- },
- },
- },
+ ...this.getRangeFilter(),
],
},
},
@@ -97,15 +72,7 @@ export class EventsQuery {
{
term: { 'process.entity_id': id },
},
- {
- range: {
- '@timestamp': {
- gte: this.timeRange.from,
- lte: this.timeRange.to,
- format: 'strict_date_optional_time',
- },
- },
- },
+ ...this.getRangeFilter(),
],
},
},
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts
index 6637e7931b056..256f2b58b6864 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts
@@ -11,10 +11,10 @@ import type { TimeRange } from '../utils';
import { resolverFields } from '../utils';
export interface ResolverQueryParams {
- readonly schema: ResolverSchema;
+ readonly schema?: ResolverSchema;
readonly indexPatterns: string | string[];
readonly timeRange: TimeRange | undefined;
- readonly isInternalRequest: boolean;
+ readonly isInternalRequest?: boolean;
readonly resolverFields?: JsonValue[];
getRangeFilter?: () => Array<{
range: { '@timestamp': { gte: string; lte: string; format: string } };
@@ -25,12 +25,18 @@ export class BaseResolverQuery implements ResolverQueryParams {
readonly schema: ResolverSchema;
readonly indexPatterns: string | string[];
readonly timeRange: TimeRange | undefined;
- readonly isInternalRequest: boolean;
+ readonly isInternalRequest?: boolean;
readonly resolverFields?: JsonValue[];
constructor({ schema, indexPatterns, timeRange, isInternalRequest }: ResolverQueryParams) {
- this.resolverFields = resolverFields(schema);
- this.schema = schema;
+ const schemaOrDefault = schema
+ ? schema
+ : {
+ id: 'process.entity_id',
+ parent: 'process.parent.entity_id',
+ };
+ this.resolverFields = resolverFields(schemaOrDefault);
+ this.schema = schemaOrDefault;
this.indexPatterns = indexPatterns;
this.timeRange = timeRange;
this.isInternalRequest = isInternalRequest;
From f147fe8b98ae94558411fd43d822c329c688a33a Mon Sep 17 00:00:00 2001
From: Josh Dover <1813008+joshdover@users.noreply.github.com>
Date: Tue, 4 Oct 2022 15:28:15 +0200
Subject: [PATCH 19/27] [Fleet] Update unenroll logic to account for new API
key fields (#142579)
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/fleet/common/types/models/agent.ts | 5 +++++
.../fleet/server/services/agents/unenroll.test.ts | 13 +++++++++++++
.../services/agents/unenroll_action_runner.ts | 10 ++++++++++
3 files changed, 28 insertions(+)
diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts
index fa54f8c943e27..ea2ad78dc10cd 100644
--- a/x-pack/plugins/fleet/common/types/models/agent.ts
+++ b/x-pack/plugins/fleet/common/types/models/agent.ts
@@ -94,7 +94,12 @@ interface AgentBase {
export interface Agent extends AgentBase {
id: string;
access_api_key?: string;
+ // @deprecated
default_api_key_history?: FleetServerAgent['default_api_key_history'];
+ outputs?: Array<{
+ api_key_id: string;
+ to_retire_api_key_ids?: FleetServerAgent['default_api_key_history'];
+ }>;
status?: AgentStatus;
packages: string[];
sort?: Array;
diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts
index 5beb5c0a9ac00..9169df19fbcfb 100644
--- a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts
+++ b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts
@@ -331,6 +331,15 @@ describe('invalidateAPIKeysForAgents', () => {
id: 'defaultApiKeyHistory2',
},
],
+ outputs: [
+ {
+ api_key_id: 'outputApiKey1',
+ to_retire_api_key_ids: [{ id: 'outputApiKeyRetire1' }, { id: 'outputApiKeyRetire2' }],
+ },
+ {
+ api_key_id: 'outputApiKey2',
+ },
+ ],
} as any,
]);
@@ -340,6 +349,10 @@ describe('invalidateAPIKeysForAgents', () => {
'defaultApiKey1',
'defaultApiKeyHistory1',
'defaultApiKeyHistory2',
+ 'outputApiKey1',
+ 'outputApiKeyRetire1',
+ 'outputApiKeyRetire2',
+ 'outputApiKey2',
]);
});
});
diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts
index c735254f18256..fed5d44fe98e8 100644
--- a/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts
+++ b/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts
@@ -215,6 +215,16 @@ export async function invalidateAPIKeysForAgents(agents: Agent[]) {
if (agent.default_api_key_history) {
agent.default_api_key_history.forEach((apiKey) => keys.push(apiKey.id));
}
+ if (agent.outputs) {
+ agent.outputs.forEach((output) => {
+ if (output.api_key_id) {
+ keys.push(output.api_key_id);
+ }
+ if (output.to_retire_api_key_ids) {
+ output.to_retire_api_key_ids.forEach((apiKey) => keys.push(apiKey.id));
+ }
+ });
+ }
return keys;
}, []);
From 5add1f9b76d1a60eea521bea880aec84c594a72d Mon Sep 17 00:00:00 2001
From: Philippe Oberti
Date: Tue, 4 Oct 2022 08:45:11 -0500
Subject: [PATCH 20/27] [TIP] Add full screen feature for indicators table
(#142519)
[TIP] Add full screen feature for indicators table
---
.../use_toolbar_options.test.tsx.snap | 120 ++++++++++++++++++
.../hooks/use_toolbar_options.test.tsx | 119 +----------------
.../hooks/use_toolbar_options.tsx | 2 +-
3 files changed, 124 insertions(+), 117 deletions(-)
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap
new file mode 100644
index 0000000000000..4b58689023333
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap
@@ -0,0 +1,120 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`useToolbarOptions() should return correct value for 0 indicators total 1`] = `
+Object {
+ "additionalControls": Object {
+ "left": Object {
+ "append": ,
+ "prepend":
+
+ -
+
+ ,
+ },
+ "right": ,
+ },
+ "showDisplaySelector": false,
+ "showFullScreenSelector": true,
+}
+`;
+
+exports[`useToolbarOptions() should return correct value for 25 indicators total 1`] = `
+Object {
+ "additionalControls": Object {
+ "left": Object {
+ "append": ,
+ "prepend":
+
+ Showing
+ 1
+ -
+ 25
+ of
+
+ 25
+ indicators
+
+ ,
+ },
+ "right": ,
+ },
+ "showDisplaySelector": false,
+ "showFullScreenSelector": true,
+}
+`;
+
+exports[`useToolbarOptions() should return correct value for 50 indicators total 1`] = `
+Object {
+ "additionalControls": Object {
+ "left": Object {
+ "append": ,
+ "prepend":
+
+ Showing
+ 26
+ -
+ 50
+ of
+
+ 50
+ indicators
+
+ ,
+ },
+ "right": ,
+ },
+ "showDisplaySelector": false,
+ "showFullScreenSelector": true,
+}
+`;
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx
index 084279fe8353a..ecf1cbf0a477a 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx
@@ -25,40 +25,7 @@ describe('useToolbarOptions()', () => {
{ wrapper: TestProvidersComponent }
);
- expect(result.result.current).toMatchInlineSnapshot(`
- Object {
- "additionalControls": Object {
- "left": Object {
- "append": ,
- "prepend":
-
- -
-
- ,
- },
- "right": ,
- },
- "showDisplaySelector": false,
- "showFullScreenSelector": false,
- }
- `);
+ expect(result.result.current).toMatchSnapshot();
});
it('should return correct value for 25 indicators total', () => {
@@ -76,47 +43,7 @@ describe('useToolbarOptions()', () => {
{ wrapper: TestProvidersComponent }
);
- expect(result.result.current).toMatchInlineSnapshot(`
- Object {
- "additionalControls": Object {
- "left": Object {
- "append": ,
- "prepend":
-
- Showing
- 1
- -
- 25
- of
-
- 25
- indicators
-
- ,
- },
- "right": ,
- },
- "showDisplaySelector": false,
- "showFullScreenSelector": false,
- }
- `);
+ expect(result.result.current).toMatchSnapshot();
});
it('should return correct value for 50 indicators total', () => {
@@ -134,46 +61,6 @@ describe('useToolbarOptions()', () => {
{ wrapper: TestProvidersComponent }
);
- expect(result.result.current).toMatchInlineSnapshot(`
- Object {
- "additionalControls": Object {
- "left": Object {
- "append": ,
- "prepend":
-
- Showing
- 26
- -
- 50
- of
-
- 50
- indicators
-
- ,
- },
- "right": ,
- },
- "showDisplaySelector": false,
- "showFullScreenSelector": false,
- }
- `);
+ expect(result.result.current).toMatchSnapshot();
});
});
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx
index b19d6df71463e..12bd94951e33c 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx
@@ -41,7 +41,7 @@ export const useToolbarOptions = ({
return useMemo(
() => ({
showDisplaySelector: false,
- showFullScreenSelector: false,
+ showFullScreenSelector: true,
additionalControls: {
left: {
prepend: (
From 796dcb99153f3872c2f220cf0687620425731db2 Mon Sep 17 00:00:00 2001
From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Date: Tue, 4 Oct 2022 07:58:43 -0600
Subject: [PATCH 21/27] skip failing test suite (#142548)
---
.../instrumented_events/from_the_browser/loaded_dashboard.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts b/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts
index bc04d60c3fb54..7b21a5637d167 100644
--- a/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts
+++ b/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts
@@ -26,7 +26,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const dashboardAddPanel = getService('dashboardAddPanel');
const queryBar = getService('queryBar');
- describe('Loaded Dashboard', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/142548
+ describe.skip('Loaded Dashboard', () => {
let fromTimestamp: string | undefined;
const getEvents = async (count: number, options?: GetEventsOptions) =>
From b798f9f627c780cebea2221db38dbb5db8a5e089 Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Tue, 4 Oct 2022 16:23:42 +0200
Subject: [PATCH 22/27] [Uptime] Unskip flaky api test (#142595)
---
x-pack/test/api_integration/apis/uptime/feature_controls.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/x-pack/test/api_integration/apis/uptime/feature_controls.ts b/x-pack/test/api_integration/apis/uptime/feature_controls.ts
index 8185cb0f03a20..39d7406636353 100644
--- a/x-pack/test/api_integration/apis/uptime/feature_controls.ts
+++ b/x-pack/test/api_integration/apis/uptime/feature_controls.ts
@@ -145,8 +145,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
}
});
- // FLAKY: https://github.com/elastic/kibana/issues/136542
- describe.skip('spaces', () => {
+ describe('spaces', () => {
// the following tests create a user_1 which has uptime read access to space_1 and dashboard all access to space_2
const space1Id = 'space_1';
const space2Id = 'space_2';
From d95e690e9e633c5cff79c6f9b847a8bb6fb16b5f Mon Sep 17 00:00:00 2001
From: jennypavlova
Date: Tue, 4 Oct 2022 16:37:19 +0200
Subject: [PATCH 23/27] [Infrastructure UI] Use same no data messaging on hosts
view (#142063)
* [WIP] Implement No Data message
* Implement refetch
* Render lens component and hide when there is no data
* Add onLoading hook and conditional rendering
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../metrics/hosts/components/hosts_table.tsx | 35 ++++++++++++++++++-
.../pages/metrics/hosts/hosts_content.tsx | 19 ++++++++++
2 files changed, 53 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx
index 74f2468eb4c45..e92ac801e8612 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx
@@ -8,8 +8,10 @@
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import type { Query, TimeRange } from '@kbn/es-query';
-import React from 'react';
+import React, { useState } from 'react';
import type { DataView } from '@kbn/data-views-plugin/public';
+import { i18n } from '@kbn/i18n';
+import { NoData } from '../../../../components/empty_states';
import { InfraClientStartDeps } from '../../../../types';
const getLensHostsTable = (
@@ -498,23 +500,54 @@ interface Props {
timeRange: TimeRange;
query: Query;
searchSessionId: string;
+ onRefetch: () => void;
+ onLoading: (isLoading: boolean) => void;
+ isLensLoading: boolean;
}
export const HostsTable: React.FunctionComponent = ({
dataView,
timeRange,
query,
searchSessionId,
+ onRefetch,
+ onLoading,
+ isLensLoading,
}) => {
const {
services: { lens },
} = useKibana();
const LensComponent = lens?.EmbeddableComponent;
+ const [noData, setNoData] = useState(false);
+
+ if (noData && !isLensLoading) {
+ return (
+
+ );
+ }
return (
{
+ if (!isLoading && adapters?.tables) {
+ setNoData(adapters?.tables.tables.default?.rows.length === 0);
+ onLoading(false);
+ }
+ }}
/>
);
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx
index 63e95a19f1c7b..7bf087db39eb5 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx
@@ -27,6 +27,7 @@ export const HostsContent: React.FunctionComponent = () => {
useMetricsDataViewContext();
// needed to refresh the lens table when filters havent changed
const [searchSessionId, setSearchSessionId] = useState(data.search.session.start());
+ const [isLensLoading, setIsLensLoading] = useState(false);
const onQuerySubmit = useCallback(
(payload: { dateRange: TimeRange; query?: Query }) => {
@@ -34,11 +35,26 @@ export const HostsContent: React.FunctionComponent = () => {
if (payload.query) {
setQuery(payload.query);
}
+ setIsLensLoading(true);
setSearchSessionId(data.search.session.start());
},
[setDateRange, setQuery, data.search.session]
);
+ const onLoading = useCallback(
+ (isLoading: boolean) => {
+ if (isLensLoading) {
+ setIsLensLoading(isLoading);
+ }
+ },
+ [setIsLensLoading, isLensLoading]
+ );
+
+ const onRefetch = useCallback(() => {
+ setIsLensLoading(true);
+ setSearchSessionId(data.search.session.start());
+ }, [data.search.session]);
+
return (
{metricsDataView ? (
@@ -61,6 +77,9 @@ export const HostsContent: React.FunctionComponent = () => {
timeRange={dateRange}
query={query}
searchSessionId={searchSessionId}
+ onRefetch={onRefetch}
+ onLoading={onLoading}
+ isLensLoading={isLensLoading}
/>
>
) : hasFailedCreatingDataView || hasFailedFetchingDataView ? (
From 001d44cb028df385afe7ff01d5ea3ca5e432efed Mon Sep 17 00:00:00 2001
From: Luke Gmys
Date: Tue, 4 Oct 2022 16:41:56 +0200
Subject: [PATCH 24/27] [TIP] Add update status component (#142560)
---
.../cypress/e2e/indicators.cy.ts | 4 +-
.../cypress/screens/indicators.ts | 2 +-
.../public/components/layout/layout.tsx | 16 ++++-
.../public/components/update_status/index.ts | 8 +++
.../update_status/update_status.test.tsx | 63 +++++++++++++++++++
.../update_status/update_status.tsx | 43 +++++++++++++
.../indicators/hooks/use_indicators.test.tsx | 1 +
.../indicators/hooks/use_indicators.ts | 5 +-
.../indicators/indicators_page.test.tsx | 1 +
.../modules/indicators/indicators_page.tsx | 8 ++-
10 files changed, 143 insertions(+), 8 deletions(-)
create mode 100644 x-pack/plugins/threat_intelligence/public/components/update_status/index.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/components/update_status/update_status.test.tsx
create mode 100644 x-pack/plugins/threat_intelligence/public/components/update_status/update_status.tsx
diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts
index e52effa09ab3b..c5d67894aa0ff 100644
--- a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts
+++ b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts
@@ -185,9 +185,7 @@ describe('Indicators', () => {
it('should render the inspector flyout', () => {
cy.get(INSPECTOR_BUTTON).last().click({ force: true });
- cy.get(INSPECTOR_PANEL).should('be.visible');
-
- cy.get(INSPECTOR_PANEL).contains('Index patterns');
+ cy.get(INSPECTOR_PANEL).contains('Indicators search requests');
});
});
});
diff --git a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts
index 2bc1b704e8159..0464e57c6749b 100644
--- a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts
+++ b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts
@@ -31,7 +31,7 @@ export const FILTERS_GLOBAL_CONTAINER = '[data-test-subj="filters-global-contain
export const TIME_RANGE_PICKER = `[data-test-subj="superDatePickerToggleQuickMenuButton"]`;
-export const QUERY_INPUT = `[data-test-subj="iocListPageQueryInput"]`;
+export const QUERY_INPUT = `[data-test-subj="queryInput"]`;
export const EMPTY_STATE = '[data-test-subj="indicatorsTableEmptyState"]';
diff --git a/x-pack/plugins/threat_intelligence/public/components/layout/layout.tsx b/x-pack/plugins/threat_intelligence/public/components/layout/layout.tsx
index 6c7621977b8dc..04ee12819d988 100644
--- a/x-pack/plugins/threat_intelligence/public/components/layout/layout.tsx
+++ b/x-pack/plugins/threat_intelligence/public/components/layout/layout.tsx
@@ -6,17 +6,23 @@
*/
import { EuiPageHeader, EuiPageHeaderSection, EuiSpacer, EuiText } from '@elastic/eui';
-import React, { FC } from 'react';
+import React, { FC, ReactNode } from 'react';
import { SecuritySolutionPageWrapper } from '../../containers/security_solution_page_wrapper';
export interface LayoutProps {
pageTitle?: string;
border?: boolean;
+ subHeader?: ReactNode;
}
export const TITLE_TEST_ID = 'tiDefaultPageLayoutTitle';
-export const DefaultPageLayout: FC = ({ children, pageTitle, border = true }) => {
+export const DefaultPageLayout: FC = ({
+ children,
+ pageTitle,
+ border = true,
+ subHeader,
+}) => {
return (
@@ -26,6 +32,12 @@ export const DefaultPageLayout: FC = ({ children, pageTitle, border
{pageTitle}
)}
+ {subHeader ? (
+ <>
+
+ {subHeader}
+ >
+ ) : null}
diff --git a/x-pack/plugins/threat_intelligence/public/components/update_status/index.ts b/x-pack/plugins/threat_intelligence/public/components/update_status/index.ts
new file mode 100644
index 0000000000000..f83c0e64fda23
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/components/update_status/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './update_status';
diff --git a/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.test.tsx b/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.test.tsx
new file mode 100644
index 0000000000000..2ed1503d89a78
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.test.tsx
@@ -0,0 +1,63 @@
+/*
+ * 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 { render } from '@testing-library/react';
+import React from 'react';
+import { TestProvidersComponent } from '../../common/mocks/test_providers';
+import { UpdateStatus } from './update_status';
+
+describe('', () => {
+ it('should render Updated now', () => {
+ const result = render(, {
+ wrapper: TestProvidersComponent,
+ });
+
+ expect(result.asFragment()).toMatchInlineSnapshot(`
+
+
+
+ `);
+ });
+
+ it('should render Updating when isUpdating', () => {
+ const result = render(, {
+ wrapper: TestProvidersComponent,
+ });
+
+ expect(result.asFragment()).toMatchInlineSnapshot(`
+
+
+
+ `);
+ });
+});
diff --git a/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.tsx b/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.tsx
new file mode 100644
index 0000000000000..02f43481186dd
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.tsx
@@ -0,0 +1,43 @@
+/*
+ * 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 React from 'react';
+
+import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedRelative } from '@kbn/i18n-react';
+
+interface UpdateStatusProps {
+ updatedAt: number;
+ isUpdating: boolean;
+}
+
+const UPDATING = i18n.translate('xpack.threatIntelligence.updateStatus.updating', {
+ defaultMessage: 'Updating...',
+});
+
+const UPDATED = i18n.translate('xpack.threatIntelligence.updateStatus.updated', {
+ defaultMessage: 'Updated',
+});
+
+export const UpdateStatus: React.FC = ({ isUpdating, updatedAt }) => (
+
+
+
+ {isUpdating ? (
+ UPDATING
+ ) : (
+ <>
+ {UPDATED}
+
+
+ >
+ )}
+
+
+
+);
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx
index 42f6a4eb1fdb7..40d64636fa346 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx
@@ -105,6 +105,7 @@ describe('useIndicators()', () => {
expect(hookResult.result.current).toMatchInlineSnapshot(`
Object {
+ "dataUpdatedAt": 0,
"handleRefresh": [Function],
"indicatorCount": 0,
"indicators": Array [],
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts
index 2352f302a1d4d..e2e0aaddf07aa 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts
@@ -47,6 +47,8 @@ export interface UseIndicatorsValue {
* Data loading is in progress (see docs on `isFetching` here: https://tanstack.com/query/v4/docs/guides/queries)
*/
isFetching: boolean;
+
+ dataUpdatedAt: number;
}
export const useIndicators = ({
@@ -95,7 +97,7 @@ export const useIndicators = ({
[inspectorAdapters, searchService]
);
- const { isLoading, isFetching, data, refetch } = useQuery(
+ const { isLoading, isFetching, data, refetch, dataUpdatedAt } = useQuery(
[
'indicatorsTable',
{
@@ -132,5 +134,6 @@ export const useIndicators = ({
isLoading,
isFetching,
handleRefresh,
+ dataUpdatedAt,
};
};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx
index e46c605d1a90a..7f4db9fa75262 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx
@@ -42,6 +42,7 @@ describe('', () => {
onChangeItemsPerPage: stub,
onChangePage: stub,
handleRefresh: stub,
+ dataUpdatedAt: Date.now(),
});
(useFilters as jest.MockedFunction).mockReturnValue({
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx
index 511faaa73a7a0..fcf690631d740 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx
@@ -20,6 +20,7 @@ import { useColumnSettings } from './components/indicators_table/hooks/use_colum
import { useAggregatedIndicators } from './hooks/use_aggregated_indicators';
import { IndicatorsFilters } from './containers/indicators_filters';
import { useSecurityContext } from '../../hooks/use_security_context';
+import { UpdateStatus } from '../../components/update_status';
const queryClient = new QueryClient();
@@ -48,6 +49,7 @@ const IndicatorsPageContent: VFC = () => {
pagination,
isLoading: isLoadingIndicators,
isFetching: isFetchingIndicators,
+ dataUpdatedAt,
} = useIndicators({
filters,
filterQuery,
@@ -72,10 +74,14 @@ const IndicatorsPageContent: VFC = () => {
return (
-
+ }
+ >
+
Date: Tue, 4 Oct 2022 15:51:26 +0100
Subject: [PATCH 25/27] Add getByTestSubj command (#142591)
---
.../power_user/feature_flag/comparison.cy.ts | 12 ++---
.../integration_policy.cy.ts | 12 ++---
.../settings/agent_configurations.cy.ts | 11 ++--
.../power_user/settings/custom_links.cy.ts | 10 ++--
.../storage_explorer/storage_explorer.cy.ts | 14 ++---
.../e2e/read_only_user/deep_links.cy.ts | 8 +--
.../e2e/read_only_user/dependencies.cy.ts | 6 +--
.../read_only_user/errors/error_details.cy.ts | 4 +-
.../read_only_user/errors/errors_page.cy.ts | 6 +--
.../cypress/e2e/read_only_user/home.cy.ts | 2 +-
.../header_filters/header_filters.cy.ts | 2 +-
.../service_inventory/service_inventory.cy.ts | 4 +-
.../service_overview/errors_table.cy.ts | 8 +--
.../service_overview/header_filters.cy.ts | 28 ++++------
.../service_overview/instances_table.cy.ts | 16 ++----
.../service_overview/service_overview.cy.ts | 42 +++++++--------
.../service_overview/time_comparison.cy.ts | 40 ++++++--------
.../transaction_details/span_links.cy.ts | 52 +++++++++----------
.../transaction_details.cy.ts | 12 ++---
.../transactions_overview.cy.ts | 8 +--
.../apm/ftr_e2e/cypress/support/commands.ts | 18 ++++---
.../apm/ftr_e2e/cypress/support/types.d.ts | 1 +
22 files changed, 144 insertions(+), 172 deletions(-)
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts
index d1159efd0fc90..7d40105db192e 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts
@@ -36,19 +36,19 @@ describe('Comparison feature flag', () => {
it('shows the comparison feature enabled in services overview', () => {
cy.visitKibana('/app/apm/services');
cy.get('input[type="checkbox"]#comparison').should('be.checked');
- cy.get('[data-test-subj="comparisonSelect"]').should('not.be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('not.be.disabled');
});
it('shows the comparison feature enabled in dependencies overview', () => {
cy.visitKibana('/app/apm/dependencies');
cy.get('input[type="checkbox"]#comparison').should('be.checked');
- cy.get('[data-test-subj="comparisonSelect"]').should('not.be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('not.be.disabled');
});
it('shows the comparison feature disabled in service map overview page', () => {
cy.visitKibana('/app/apm/service-map');
cy.get('input[type="checkbox"]#comparison').should('be.checked');
- cy.get('[data-test-subj="comparisonSelect"]').should('not.be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('not.be.disabled');
});
});
@@ -71,7 +71,7 @@ describe('Comparison feature flag', () => {
it('shows the comparison feature disabled in services overview', () => {
cy.visitKibana('/app/apm/services');
cy.get('input[type="checkbox"]#comparison').should('not.be.checked');
- cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('be.disabled');
});
it('shows the comparison feature disabled in dependencies overview page', () => {
@@ -81,13 +81,13 @@ describe('Comparison feature flag', () => {
cy.visitKibana('/app/apm/dependencies');
cy.wait('@topDependenciesRequest');
cy.get('input[type="checkbox"]#comparison').should('not.be.checked');
- cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('be.disabled');
});
it('shows the comparison feature disabled in service map overview page', () => {
cy.visitKibana('/app/apm/service-map');
cy.get('input[type="checkbox"]#comparison').should('not.be.checked');
- cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('be.disabled');
});
});
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts
index c25e6a6800311..5d275770e462d 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts
@@ -60,21 +60,19 @@ describe('when navigating to integration page', () => {
cy.visitKibana(integrationsPath);
// open integration policy form
- cy.get('[data-test-subj="integration-card:epr:apm:featured').click();
+ cy.getByTestSubj('integration-card:epr:apm:featured').click();
cy.contains('Elastic APM in Fleet').click();
cy.contains('a', 'APM integration').click();
- cy.get('[data-test-subj="addIntegrationPolicyButton"]').click();
+ cy.getByTestSubj('addIntegrationPolicyButton').click();
});
it('checks validators for required fields', () => {
const requiredFields = policyFormFields.filter((field) => field.required);
requiredFields.map((field) => {
- cy.get(`[data-test-subj="${field.selector}"`).clear();
- cy.get('[data-test-subj="createPackagePolicySaveButton"').should(
- 'be.disabled'
- );
- cy.get(`[data-test-subj="${field.selector}"`).type(field.value);
+ cy.getByTestSubj(field.selector).clear();
+ cy.getByTestSubj('createPackagePolicySaveButton').should('be.disabled');
+ cy.getByTestSubj(field.selector).type(field.value);
});
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts
index 5be39b4f082dc..47f8c537b100c 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts
@@ -90,7 +90,7 @@ describe('Agent configuration', () => {
'/api/apm/settings/agent-configuration/environments?*'
).as('serviceEnvironmentApi');
cy.contains('Create configuration').click();
- cy.get('[data-test-subj="serviceNameComboBox"]')
+ cy.getByTestSubj('serviceNameComboBox')
.click()
.type('opbeans-node')
.type('{enter}');
@@ -98,7 +98,7 @@ describe('Agent configuration', () => {
cy.contains('opbeans-node').realClick();
cy.wait('@serviceEnvironmentApi');
- cy.get('[data-test-subj="serviceEnviromentComboBox"]')
+ cy.getByTestSubj('serviceEnviromentComboBox')
.click({ force: true })
.type('prod')
.type('{enter}');
@@ -115,14 +115,11 @@ describe('Agent configuration', () => {
'/api/apm/settings/agent-configuration/environments'
).as('serviceEnvironmentApi');
cy.contains('Create configuration').click();
- cy.get('[data-test-subj="serviceNameComboBox"]')
- .click()
- .type('All')
- .type('{enter}');
+ cy.getByTestSubj('serviceNameComboBox').click().type('All').type('{enter}');
cy.contains('All').realClick();
cy.wait('@serviceEnvironmentApi');
- cy.get('[data-test-subj="serviceEnviromentComboBox"]')
+ cy.getByTestSubj('serviceEnviromentComboBox')
.click({ force: true })
.type('All');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts
index 615ff2b49a85a..b680f745609bc 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts
@@ -52,7 +52,7 @@ describe('Custom links', () => {
it('creates custom link', () => {
cy.visitKibana(basePath);
- const emptyPrompt = cy.get('[data-test-subj="customLinksEmptyPrompt"]');
+ const emptyPrompt = cy.getByTestSubj('customLinksEmptyPrompt');
cy.contains('Create custom link').click();
cy.contains('Create link');
cy.contains('Save').should('be.disabled');
@@ -63,7 +63,7 @@ describe('Custom links', () => {
emptyPrompt.should('not.exist');
cy.contains('foo');
cy.contains('https://foo.com');
- cy.get('[data-test-subj="editCustomLink"]').click();
+ cy.getByTestSubj('editCustomLink').click();
cy.contains('Delete').click();
});
@@ -71,14 +71,14 @@ describe('Custom links', () => {
cy.visitKibana(basePath);
// wait for empty prompt
- cy.get('[data-test-subj="customLinksEmptyPrompt"]').should('be.visible');
+ cy.getByTestSubj('customLinksEmptyPrompt').should('be.visible');
cy.contains('Create custom link').click();
- cy.get('[data-test-subj="filter-0"]').select('service.name');
+ cy.getByTestSubj('filter-0').select('service.name');
cy.get(
'[data-test-subj="service.name.value"] [data-test-subj="comboBoxSearchInput"]'
).type('foo');
- cy.get('[data-test-subj="filter-0"]').select('service.environment');
+ cy.getByTestSubj('filter-0').select('service.environment');
cy.get(
'[data-test-subj="service.environment.value"] [data-test-subj="comboBoxInput"]'
).should('not.contain', 'foo');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts
index e989ea5cf0faf..20577f8bf5793 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts
@@ -85,7 +85,7 @@ describe('Storage Explorer', () => {
});
it('renders the storage timeseries chart', () => {
- cy.get('[data-test-subj="storageExplorerTimeseriesChart"]');
+ cy.getByTestSubj('storageExplorerTimeseriesChart');
});
it('has a list of services and environments', () => {
@@ -115,7 +115,7 @@ describe('Storage Explorer', () => {
it('with the correct environment when changing the environment', () => {
cy.wait(mainAliasNames);
- cy.get('[data-test-subj="environmentFilter"]').type('production');
+ cy.getByTestSubj('environmentFilter').type('production');
cy.contains('button', 'production').click({ force: true });
@@ -148,7 +148,7 @@ describe('Storage Explorer', () => {
it('with the correct lifecycle phase when changing the lifecycle phase', () => {
cy.wait(mainAliasNames);
- cy.get('[data-test-subj="storageExplorerLifecyclePhaseSelect"]').click();
+ cy.getByTestSubj('storageExplorerLifecyclePhaseSelect').click();
cy.contains('button', 'Warm').click();
cy.expectAPIsToHaveBeenCalledWith({
@@ -180,13 +180,13 @@ describe('Storage Explorer', () => {
cy.wait(mainAliasNames);
cy.contains('opbeans-node');
- cy.get('[data-test-subj="storageDetailsButton_opbeans-node"]').click();
- cy.get('[data-test-subj="loadingSpinner"]').should('be.visible');
+ cy.getByTestSubj('storageDetailsButton_opbeans-node').click();
+ cy.getByTestSubj('loadingSpinner').should('be.visible');
cy.wait('@storageDetailsRequest');
cy.contains('Service storage details');
- cy.get('[data-test-subj="storageExplorerTimeseriesChart"]');
- cy.get('[data-test-subj="serviceStorageDetailsTable"]');
+ cy.getByTestSubj('storageExplorerTimeseriesChart');
+ cy.getByTestSubj('serviceStorageDetailsTable');
});
});
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts
index cfcabe85b5b2a..00b842f3265c7 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts
@@ -11,7 +11,7 @@ describe('APM deep links', () => {
});
it('navigates to apm links on search elastic', () => {
cy.visitKibana('/');
- cy.get('[data-test-subj="nav-search-input"]').type('APM');
+ cy.getByTestSubj('nav-search-input').type('APM');
cy.contains('APM');
cy.contains('APM / Services');
cy.contains('APM / Traces');
@@ -23,17 +23,17 @@ describe('APM deep links', () => {
cy.contains('APM').click({ force: true });
cy.url().should('include', '/apm/services');
- cy.get('[data-test-subj="nav-search-input"]').type('APM');
+ cy.getByTestSubj('nav-search-input').type('APM');
// navigates to services page
cy.contains('APM / Services').click({ force: true });
cy.url().should('include', '/apm/services');
- cy.get('[data-test-subj="nav-search-input"]').type('APM');
+ cy.getByTestSubj('nav-search-input').type('APM');
// navigates to traces page
cy.contains('APM / Traces').click({ force: true });
cy.url().should('include', '/apm/traces');
- cy.get('[data-test-subj="nav-search-input"]').type('APM');
+ cy.getByTestSubj('nav-search-input').type('APM');
// navigates to service maps
cy.contains('APM / Service Map').click({ force: true });
cy.url().should('include', '/apm/service-map');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts
index 653809a8e04d3..2ef3ae42b1aac 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts
@@ -66,9 +66,9 @@ describe('Dependencies', () => {
})}`
);
- cy.get('[data-test-subj="latencyChart"]');
- cy.get('[data-test-subj="throughputChart"]');
- cy.get('[data-test-subj="errorRateChart"]');
+ cy.getByTestSubj('latencyChart');
+ cy.getByTestSubj('throughputChart');
+ cy.getByTestSubj('errorRateChart');
cy.contains('opbeans-java').click({ force: true });
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts
index 19de523c7ab1f..d00d8036df3bb 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts
@@ -68,13 +68,13 @@ describe('Error details', () => {
it('shows errors distribution chart', () => {
cy.visitKibana(errorDetailsPageHref);
cy.contains('Error group 00000');
- cy.get('[data-test-subj="errorDistribution"]').contains('Occurrences');
+ cy.getByTestSubj('errorDistribution').contains('Occurrences');
});
it('shows top erroneous transactions table', () => {
cy.visitKibana(errorDetailsPageHref);
cy.contains('Top 5 affected transactions');
- cy.get('[data-test-subj="topErroneousTransactionsTable"]')
+ cy.getByTestSubj('topErroneousTransactionsTable')
.contains('a', 'GET /apple 🍎')
.click();
cy.url().should('include', 'opbeans-java/transactions/view');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts
index 301b3384ee2eb..8ac95d509d0bd 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts
@@ -81,14 +81,14 @@ describe('Errors page', () => {
it('clicking on type adds a filter in the kuerybar', () => {
cy.visitKibana(javaServiceErrorsPageHref);
- cy.get('[data-test-subj="headerFilterKuerybar"]')
+ cy.getByTestSubj('headerFilterKuerybar')
.invoke('val')
.should('be.empty');
// `force: true` because Cypress says the element is 0x0
cy.contains('exception 0').click({
force: true,
});
- cy.get('[data-test-subj="headerFilterKuerybar"]')
+ cy.getByTestSubj('headerFilterKuerybar')
.its('length')
.should('be.gt', 0);
cy.get('table')
@@ -158,7 +158,7 @@ describe('Check detailed statistics API with multiple errors', () => {
])
);
});
- cy.get('[data-test-subj="pagination-button-1"]').click();
+ cy.getByTestSubj('pagination-button-1').click();
cy.wait('@errorsDetailedStatistics').then((payload) => {
expect(payload.request.body.groupIds).eql(
JSON.stringify([
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts
index 2ee2f4f019b12..e0c4a3aedd2b3 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts
@@ -69,7 +69,7 @@ describe('Home page', () => {
cy.contains('Services');
cy.contains('opbeans-rum').click({ force: true });
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'page-load'
);
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts
index c4e87ac15fbe1..4f72e968d81f8 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts
@@ -44,7 +44,7 @@ describe('Service inventory - header filters', () => {
cy.contains('Services');
cy.contains('opbeans-node');
cy.contains('service 1');
- cy.get('[data-test-subj="headerFilterKuerybar"]')
+ cy.getByTestSubj('headerFilterKuerybar')
.type(`service.name: "${specialServiceName}"`)
.type('{enter}');
cy.contains('service 1');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts
index 015df91d792e9..2d40c690a8c92 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts
@@ -93,7 +93,7 @@ describe('Service inventory', () => {
it('with the correct environment when changing the environment', () => {
cy.wait(mainAliasNames);
- cy.get('[data-test-subj="environmentFilter"]').type('production');
+ cy.getByTestSubj('environmentFilter').type('production');
cy.contains('button', 'production').click();
@@ -175,7 +175,7 @@ describe('Service inventory', () => {
])
);
});
- cy.get('[data-test-subj="pagination-button-1"]').click();
+ cy.getByTestSubj('pagination-button-1').click();
cy.wait('@detailedStatisticsRequest').then((payload) => {
expect(payload.request.body.serviceNames).eql(
JSON.stringify([
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts
index b175eb0430ed4..d693148010c7e 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts
@@ -50,16 +50,12 @@ describe('Errors table', () => {
it('clicking on type adds a filter in the kuerybar and navigates to errors page', () => {
cy.visitKibana(serviceOverviewHref);
- cy.get('[data-test-subj="headerFilterKuerybar"]')
- .invoke('val')
- .should('be.empty');
+ cy.getByTestSubj('headerFilterKuerybar').invoke('val').should('be.empty');
// `force: true` because Cypress says the element is 0x0
cy.contains('Exception').click({
force: true,
});
- cy.get('[data-test-subj="headerFilterKuerybar"]')
- .its('length')
- .should('be.gt', 0);
+ cy.getByTestSubj('headerFilterKuerybar').its('length').should('be.gt', 0);
cy.get('table').find('td:contains("Exception")').should('have.length', 1);
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts
index 6376d544821aa..8a25024506696 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts
@@ -77,13 +77,13 @@ describe('Service overview - header filters', () => {
cy.visitKibana(serviceOverviewHref);
cy.contains('opbeans-node');
cy.url().should('not.include', 'transactionType');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'request'
);
- cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker');
+ cy.getByTestSubj('headerFilterTransactionType').select('Worker');
cy.url().should('include', 'transactionType=Worker');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
@@ -94,7 +94,7 @@ describe('Service overview - header filters', () => {
cy.intercept('GET', endpoint).as(name);
});
cy.visitKibana(serviceOverviewHref);
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'request'
);
@@ -104,9 +104,9 @@ describe('Service overview - header filters', () => {
value: 'transactionType=request',
});
- cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker');
+ cy.getByTestSubj('headerFilterTransactionType').select('Worker');
cy.url().should('include', 'transactionType=Worker');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
@@ -129,18 +129,12 @@ describe('Service overview - header filters', () => {
})
);
cy.contains('opbeans-java');
- cy.get('[data-test-subj="headerFilterKuerybar"]').type('transaction.n');
+ cy.getByTestSubj('headerFilterKuerybar').type('transaction.n');
cy.contains('transaction.name');
- cy.get('[data-test-subj="suggestionContainer"]')
- .find('li')
- .first()
- .click();
- cy.get('[data-test-subj="headerFilterKuerybar"]').type(':');
- cy.get('[data-test-subj="suggestionContainer"]')
- .find('li')
- .first()
- .click();
- cy.get('[data-test-subj="headerFilterKuerybar"]').type('{enter}');
+ cy.getByTestSubj('suggestionContainer').find('li').first().click();
+ cy.getByTestSubj('headerFilterKuerybar').type(':');
+ cy.getByTestSubj('suggestionContainer').find('li').first().click();
+ cy.getByTestSubj('headerFilterKuerybar').type('{enter}');
cy.url().should('include', '&kuery=transaction.name');
});
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts
index 03653df2b0bb6..578b116a10592 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts
@@ -63,7 +63,7 @@ describe('Instances table', () => {
it('shows empty message', () => {
cy.visitKibana(testServiveHref);
cy.contains('test-service');
- cy.get('[data-test-subj="serviceInstancesTableContainer"]').contains(
+ cy.getByTestSubj('serviceInstancesTableContainer').contains(
'No instances found'
);
});
@@ -77,9 +77,7 @@ describe('Instances table', () => {
it('hides instances table', () => {
cy.visitKibana(serviceRumOverviewHref);
cy.contains('opbeans-rum');
- cy.get('[data-test-subj="serviceInstancesTableContainer"]').should(
- 'not.exist'
- );
+ cy.getByTestSubj('serviceInstancesTableContainer').should('not.exist');
});
});
@@ -109,10 +107,8 @@ describe('Instances table', () => {
cy.contains(serviceNodeName);
cy.wait('@instancesDetailsRequest');
- cy.get(
- `[data-test-subj="instanceDetailsButton_${serviceNodeName}"]`
- ).realClick();
- cy.get('[data-test-subj="loadingSpinner"]').should('be.visible');
+ cy.getByTestSubj(`instanceDetailsButton_${serviceNodeName}`).realClick();
+ cy.getByTestSubj('loadingSpinner').should('be.visible');
cy.wait('@instanceDetailsRequest').then(() => {
cy.contains('Service');
});
@@ -130,9 +126,7 @@ describe('Instances table', () => {
cy.contains(serviceNodeName);
cy.wait('@instancesDetailsRequest');
- cy.get(
- `[data-test-subj="instanceActionsButton_${serviceNodeName}"]`
- ).click();
+ cy.getByTestSubj(`instanceActionsButton_${serviceNodeName}`).click();
cy.contains('Pod logs');
cy.contains('Pod metrics');
// cy.contains('Container logs');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts
index e8319c8efafeb..8173e94557b29 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts
@@ -109,13 +109,13 @@ describe('Service Overview', () => {
cy.contains('opbeans-node');
// set skipFailures to true to not fail the test when there are accessibility failures
checkA11y({ skipFailures: true });
- cy.get('[data-test-subj="latencyChart"]');
- cy.get('[data-test-subj="throughput"]');
- cy.get('[data-test-subj="transactionsGroupTable"]');
- cy.get('[data-test-subj="serviceOverviewErrorsTable"]');
- cy.get('[data-test-subj="dependenciesTable"]');
- cy.get('[data-test-subj="instancesLatencyDistribution"]');
- cy.get('[data-test-subj="serviceOverviewInstancesTable"]');
+ cy.getByTestSubj('latencyChart');
+ cy.getByTestSubj('throughput');
+ cy.getByTestSubj('transactionsGroupTable');
+ cy.getByTestSubj('serviceOverviewErrorsTable');
+ cy.getByTestSubj('dependenciesTable');
+ cy.getByTestSubj('instancesLatencyDistribution');
+ cy.getByTestSubj('serviceOverviewInstancesTable');
});
});
@@ -134,17 +134,17 @@ describe('Service Overview', () => {
cy.wait('@transactionTypesRequest');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'request'
);
- cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').select('Worker');
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
cy.contains('Transactions').click();
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
@@ -159,18 +159,18 @@ describe('Service Overview', () => {
cy.visitKibana(baseUrl);
cy.wait('@transactionTypesRequest');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'request'
);
- cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').select('Worker');
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
cy.contains('View transactions').click();
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
@@ -226,7 +226,7 @@ describe('Service Overview', () => {
'suggestionsRequest'
);
- cy.get('[data-test-subj="environmentFilter"] input').type('production', {
+ cy.getByTestSubj('environmentFilter').find('input').type('production', {
force: true,
});
@@ -235,9 +235,7 @@ describe('Service Overview', () => {
value: 'fieldValue=production',
});
- cy.get(
- '[data-test-subj="comboBoxOptionsList environmentFilter-optionsList"]'
- )
+ cy.getByTestSubj('comboBoxOptionsList environmentFilter-optionsList')
.contains('production')
.click({ force: true });
@@ -271,11 +269,11 @@ describe('Service Overview', () => {
});
it('when selecting a different comparison window', () => {
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d');
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1d');
// selects another comparison type
- cy.get('[data-test-subj="comparisonSelect"]').select('1w');
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1w');
+ cy.getByTestSubj('comparisonSelect').select('1w');
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1w');
cy.expectAPIsToHaveBeenCalledWith({
apisIntercepted: aliasNamesWithComparison,
value: 'offset',
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts
index 718a2a4a06cf7..bce3da42d5a3f 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts
@@ -101,18 +101,18 @@ describe('Service overview: Time Comparison', () => {
cy.visitKibana(serviceOverviewPath);
cy.contains('opbeans-java');
// opens the page with "Day before" selected
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d');
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1d');
// selects another comparison type
- cy.get('[data-test-subj="comparisonSelect"]').select('1w');
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1w');
+ cy.getByTestSubj('comparisonSelect').select('1w');
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1w');
});
it('changes comparison type when a new time range is selected', () => {
cy.visitKibana(serviceOverviewHref);
cy.contains('opbeans-java');
// Time comparison default value
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d');
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1d');
cy.contains('Day before');
cy.contains('Week before');
@@ -121,17 +121,14 @@ describe('Service overview: Time Comparison', () => {
'2021-10-20T00:00:00.000Z'
);
- cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click();
+ cy.getByTestSubj('superDatePickerApplyTimeButton').click();
- cy.get('[data-test-subj="comparisonSelect"]').should(
- 'have.value',
- '864000000ms'
- );
- cy.get('[data-test-subj="comparisonSelect"]').should(
+ cy.getByTestSubj('comparisonSelect').should('have.value', '864000000ms');
+ cy.getByTestSubj('comparisonSelect').should(
'not.contain.text',
'Day before'
);
- cy.get('[data-test-subj="comparisonSelect"]').should(
+ cy.getByTestSubj('comparisonSelect').should(
'not.contain.text',
'Week before'
);
@@ -141,17 +138,14 @@ describe('Service overview: Time Comparison', () => {
cy.contains('Week before');
cy.changeTimeRange('Last 24 hours');
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d');
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1d');
cy.contains('Day before');
cy.contains('Week before');
cy.changeTimeRange('Last 7 days');
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1w');
- cy.get('[data-test-subj="comparisonSelect"]').should(
- 'contain.text',
- 'Week before'
- );
- cy.get('[data-test-subj="comparisonSelect"]').should(
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1w');
+ cy.getByTestSubj('comparisonSelect').should('contain.text', 'Week before');
+ cy.getByTestSubj('comparisonSelect').should(
'not.contain.text',
'Day before'
);
@@ -170,7 +164,7 @@ describe('Service overview: Time Comparison', () => {
);
cy.contains('opbeans-java');
cy.wait('@throughputChartRequest');
- cy.get('[data-test-subj="throughput"]')
+ cy.getByTestSubj('throughput')
.get('#echHighlighterClipPath__throughput')
.realHover({ position: 'center' });
cy.contains('Week before');
@@ -186,17 +180,17 @@ describe('Service overview: Time Comparison', () => {
cy.contains('opbeans-java');
// Comparison is enabled by default
- cy.get('[data-test-subj="comparisonSelect"]').should('be.enabled');
+ cy.getByTestSubj('comparisonSelect').should('be.enabled');
// toggles off comparison
cy.contains('Comparison').click();
- cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('be.disabled');
});
it('calls APIs without comparison time range', () => {
cy.visitKibana(serviceOverviewHref);
- cy.get('[data-test-subj="comparisonSelect"]').should('be.enabled');
+ cy.getByTestSubj('comparisonSelect').should('be.enabled');
const offset = `offset=1d`;
// When the page loads it fetches all APIs with comparison time range
@@ -212,7 +206,7 @@ describe('Service overview: Time Comparison', () => {
// toggles off comparison
cy.contains('Comparison').click();
- cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('be.disabled');
// When comparison is disabled APIs are called withou comparison time range
cy.wait(apisToIntercept.map(({ name }) => `@${name}`)).then(
(interceptions) => {
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts
index cddba048e8a18..60b36b10ee4a3 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts
@@ -50,8 +50,8 @@ describe('Span links', () => {
);
cy.contains('Transaction A').click();
cy.contains('2 Span links');
- cy.get(
- `[data-test-subj="spanLinksBadge_${ids.producerInternalOnlyIds.spanAId}"]`
+ cy.getByTestSubj(
+ `spanLinksBadge_${ids.producerInternalOnlyIds.spanAId}`
).realHover();
cy.contains('2 Span links found');
cy.contains('2 incoming');
@@ -64,8 +64,8 @@ describe('Span links', () => {
);
cy.contains('Transaction B').click();
cy.contains('2 Span links');
- cy.get(
- `[data-test-subj="spanLinksBadge_${ids.producerExternalOnlyIds.spanBId}"]`
+ cy.getByTestSubj(
+ `spanLinksBadge_${ids.producerExternalOnlyIds.spanBId}`
).realHover();
cy.contains('2 Span links found');
cy.contains('1 incoming');
@@ -78,8 +78,8 @@ describe('Span links', () => {
);
cy.contains('Transaction C').click();
cy.contains('2 Span links');
- cy.get(
- `[data-test-subj="spanLinksBadge_${ids.producerConsumerIds.transactionCId}"]`
+ cy.getByTestSubj(
+ `spanLinksBadge_${ids.producerConsumerIds.transactionCId}`
).realHover();
cy.contains('2 Span links found');
cy.contains('1 incoming');
@@ -92,8 +92,8 @@ describe('Span links', () => {
);
cy.contains('Transaction C').click();
cy.contains('1 Span link');
- cy.get(
- `[data-test-subj="spanLinksBadge_${ids.producerConsumerIds.spanCId}"]`
+ cy.getByTestSubj(
+ `spanLinksBadge_${ids.producerConsumerIds.spanCId}`
).realHover();
cy.contains('1 Span link found');
cy.contains('1 incoming');
@@ -106,8 +106,8 @@ describe('Span links', () => {
);
cy.contains('Transaction D').click();
cy.contains('2 Span links');
- cy.get(
- `[data-test-subj="spanLinksBadge_${ids.producerMultipleIds.transactionDId}"]`
+ cy.getByTestSubj(
+ `spanLinksBadge_${ids.producerMultipleIds.transactionDId}`
).realHover();
cy.contains('2 Span links found');
cy.contains('0 incoming');
@@ -120,8 +120,8 @@ describe('Span links', () => {
);
cy.contains('Transaction D').click();
cy.contains('2 Span links');
- cy.get(
- `[data-test-subj="spanLinksBadge_${ids.producerMultipleIds.spanEId}"]`
+ cy.getByTestSubj(
+ `spanLinksBadge_${ids.producerMultipleIds.spanEId}`
).realHover();
cy.contains('2 Span links found');
cy.contains('0 incoming');
@@ -136,7 +136,7 @@ describe('Span links', () => {
);
cy.contains('Transaction A').click();
cy.contains('Span A').click();
- cy.get('[data-test-subj="spanLinksTab"]').click();
+ cy.getByTestSubj('spanLinksTab').click();
cy.contains('producer-consumer')
.should('have.attr', 'href')
.and('include', '/services/producer-consumer/overview');
@@ -155,7 +155,7 @@ describe('Span links', () => {
'include',
`link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.transactionDId}`
);
- cy.get('[data-test-subj="spanLinkTypeSelect"]').should(
+ cy.getByTestSubj('spanLinkTypeSelect').should(
'contain.text',
'Outgoing links (0)'
);
@@ -167,7 +167,7 @@ describe('Span links', () => {
);
cy.contains('Transaction B').click();
cy.contains('Span B').click();
- cy.get('[data-test-subj="spanLinksTab"]').click();
+ cy.getByTestSubj('spanLinksTab').click();
cy.contains('consumer-multiple')
.should('have.attr', 'href')
@@ -178,9 +178,7 @@ describe('Span links', () => {
'include',
`link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.spanEId}`
);
- cy.get('[data-test-subj="spanLinkTypeSelect"]').select(
- 'Outgoing links (1)'
- );
+ cy.getByTestSubj('spanLinkTypeSelect').select('Outgoing links (1)');
cy.contains('Unknown');
cy.contains('trace#1-span#1');
});
@@ -193,7 +191,7 @@ describe('Span links', () => {
cy.get(
`[aria-controls="${ids.producerConsumerIds.transactionCId}"]`
).click();
- cy.get('[data-test-subj="spanLinksTab"]').click();
+ cy.getByTestSubj('spanLinksTab').click();
cy.contains('consumer-multiple')
.should('have.attr', 'href')
@@ -205,9 +203,7 @@ describe('Span links', () => {
`link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.spanEId}`
);
- cy.get('[data-test-subj="spanLinkTypeSelect"]').select(
- 'Outgoing links (1)'
- );
+ cy.getByTestSubj('spanLinkTypeSelect').select('Outgoing links (1)');
cy.contains('producer-internal-only')
.should('have.attr', 'href')
.and('include', '/services/producer-internal-only/overview');
@@ -225,7 +221,7 @@ describe('Span links', () => {
);
cy.contains('Transaction C').click();
cy.contains('Span C').click();
- cy.get('[data-test-subj="spanLinksTab"]').click();
+ cy.getByTestSubj('spanLinksTab').click();
cy.contains('consumer-multiple')
.should('have.attr', 'href')
@@ -237,7 +233,7 @@ describe('Span links', () => {
`link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.transactionDId}`
);
- cy.get('[data-test-subj="spanLinkTypeSelect"]').should(
+ cy.getByTestSubj('spanLinkTypeSelect').should(
'contain.text',
'Outgoing links (0)'
);
@@ -251,7 +247,7 @@ describe('Span links', () => {
cy.get(
`[aria-controls="${ids.producerMultipleIds.transactionDId}"]`
).click();
- cy.get('[data-test-subj="spanLinksTab"]').click();
+ cy.getByTestSubj('spanLinksTab').click();
cy.contains('producer-consumer')
.should('have.attr', 'href')
@@ -273,7 +269,7 @@ describe('Span links', () => {
`link-to/transaction/${ids.producerInternalOnlyIds.transactionAId}?waterfallItemId=${ids.producerInternalOnlyIds.spanAId}`
);
- cy.get('[data-test-subj="spanLinkTypeSelect"]').should(
+ cy.getByTestSubj('spanLinkTypeSelect').should(
'contain.text',
'Incoming links (0)'
);
@@ -285,7 +281,7 @@ describe('Span links', () => {
);
cy.contains('Transaction D').click();
cy.contains('Span E').click();
- cy.get('[data-test-subj="spanLinksTab"]').click();
+ cy.getByTestSubj('spanLinksTab').click();
cy.contains('producer-external-only')
.should('have.attr', 'href')
@@ -307,7 +303,7 @@ describe('Span links', () => {
`link-to/transaction/${ids.producerConsumerIds.transactionCId}?waterfallItemId=${ids.producerConsumerIds.transactionCId}`
);
- cy.get('[data-test-subj="spanLinkTypeSelect"]').should(
+ cy.getByTestSubj('spanLinkTypeSelect').should(
'contain.text',
'Incoming links (0)'
);
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts
index 5172a5f167fc9..09bd37f5b0b6c 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts
@@ -42,15 +42,15 @@ describe('Transaction details', () => {
it('shows transaction name and transaction charts', () => {
cy.contains('h2', 'GET /api/product');
- cy.get('[data-test-subj="latencyChart"]');
- cy.get('[data-test-subj="throughput"]');
- cy.get('[data-test-subj="transactionBreakdownChart"]');
- cy.get('[data-test-subj="errorRate"]');
+ cy.getByTestSubj('latencyChart');
+ cy.getByTestSubj('throughput');
+ cy.getByTestSubj('transactionBreakdownChart');
+ cy.getByTestSubj('errorRate');
});
it('shows top errors table', () => {
cy.contains('Top 5 errors');
- cy.get('[data-test-subj="topErrorsForTransactionTable"]')
+ cy.getByTestSubj('topErrorsForTransactionTable')
.contains('a', '[MockError] Foo')
.click();
cy.url().should('include', 'opbeans-java/errors');
@@ -58,7 +58,7 @@ describe('Transaction details', () => {
describe('when navigating to a trace sample', () => {
it('keeps the same trace sample after reloading the page', () => {
- cy.get('[data-test-subj="pagination-button-last"]').click();
+ cy.getByTestSubj('pagination-button-last').click();
cy.url().then((url) => {
cy.reload();
cy.url().should('eq', url);
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts
index 83753b7fe2595..2e7e0d336cd5d 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts
@@ -49,17 +49,17 @@ describe('Transactions Overview', () => {
it('persists transaction type selected when navigating to Overview tab', () => {
cy.visitKibana(serviceTransactionsHref);
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'request'
);
- cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').select('Worker');
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
cy.get('a[href*="/app/apm/services/opbeans-node/overview"]').click();
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts
index 7830e791c3655..9e6e0189e636c 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts
@@ -52,15 +52,19 @@ Cypress.Commands.add(
}
);
+Cypress.Commands.add('getByTestSubj', (selector: string) => {
+ return cy.get(`[data-test-subj="${selector}"]`);
+});
+
Cypress.Commands.add('changeTimeRange', (value: string) => {
- cy.get('[data-test-subj="superDatePickerToggleQuickMenuButton"]').click();
+ cy.getByTestSubj('superDatePickerToggleQuickMenuButton').click();
cy.contains(value).click();
});
Cypress.Commands.add('visitKibana', (url: string) => {
cy.visit(url);
- cy.get('[data-test-subj="kbnLoadingMessage"]').should('exist');
- cy.get('[data-test-subj="kbnLoadingMessage"]').should('not.exist', {
+ cy.getByTestSubj('kbnLoadingMessage').should('exist');
+ cy.getByTestSubj('kbnLoadingMessage').should('not.exist', {
timeout: 50000,
});
});
@@ -70,13 +74,13 @@ Cypress.Commands.add(
(start: string, end: string) => {
const format = 'MMM D, YYYY @ HH:mm:ss.SSS';
- cy.get('[data-test-subj="superDatePickerstartDatePopoverButton"]').click();
- cy.get('[data-test-subj="superDatePickerAbsoluteDateInput"]')
+ cy.getByTestSubj('superDatePickerstartDatePopoverButton').click();
+ cy.getByTestSubj('superDatePickerAbsoluteDateInput')
.eq(0)
.clear({ force: true })
.type(moment(start).format(format), { force: true });
- cy.get('[data-test-subj="superDatePickerendDatePopoverButton"]').click();
- cy.get('[data-test-subj="superDatePickerAbsoluteDateInput"]')
+ cy.getByTestSubj('superDatePickerendDatePopoverButton').click();
+ cy.getByTestSubj('superDatePickerAbsoluteDateInput')
.eq(1)
.clear({ force: true })
.type(moment(end).format(format), { force: true });
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts
index 2235847e584a4..5d59d4691820a 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts
@@ -22,5 +22,6 @@ declare namespace Cypress {
value: string;
}): void;
updateAdvancedSettings(settings: Record): void;
+ getByTestSubj(selector: string): Chainable>;
}
}
From d7700a609ff6d720b33c477c62f84038eb3e5021 Mon Sep 17 00:00:00 2001
From: Byron Hulcher
Date: Tue, 4 Oct 2022 11:01:21 -0400
Subject: [PATCH 26/27] Removing link from native connector advanced
configuration steps (#142541)
---
.../native_connector_advanced_configuration.tsx | 16 ++--------------
1 file changed, 2 insertions(+), 14 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx
index fba38e958163a..3e3582bb619fa 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx
@@ -16,7 +16,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { generateEncodedPath } from '../../../../../shared/encode_path_params';
-import { EuiLinkTo, EuiButtonTo } from '../../../../../shared/react_router_helpers';
+import { EuiButtonTo } from '../../../../../shared/react_router_helpers';
import { SEARCH_INDEX_TAB_PATH } from '../../../../routes';
import { IndexNameLogic } from '../../index_name_logic';
@@ -31,19 +31,7 @@ export const NativeConnectorAdvancedConfiguration: React.FC = () => {
- {i18n.translate(
- 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.recurringScheduleLinkLabel',
- {
- defaultMessage: 'recurring sync schedule',
- }
- )}
-
- ),
- }}
+ defaultMessage="Finalize your connector by triggering a one time sync, or setting a recurring sync schedule."
/>
From 53bf927a6fe28545d5b8d7513591f3d8100f8b30 Mon Sep 17 00:00:00 2001
From: Nicolas Chaulet
Date: Tue, 4 Oct 2022 11:03:39 -0400
Subject: [PATCH 27/27] [Fleet] Bulk install packages before creating agent and
package policy (#142471)
---
.../hooks/devtools_request.tsx | 74 ++++
.../single_page_layout/hooks/form.tsx | 318 ++++++++++++++
.../single_page_layout/hooks/index.tsx | 9 +
.../single_page_layout/index.tsx | 414 ++++--------------
.../fleet/public/hooks/use_request/epm.ts | 10 +
5 files changed, 487 insertions(+), 338 deletions(-)
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx
new file mode 100644
index 0000000000000..55e91154060b7
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx
@@ -0,0 +1,74 @@
+/*
+ * 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 { useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+
+import { ExperimentalFeaturesService } from '../../../../../services';
+import {
+ generateCreatePackagePolicyDevToolsRequest,
+ generateCreateAgentPolicyDevToolsRequest,
+} from '../../../services';
+import {
+ FLEET_SYSTEM_PACKAGE,
+ HIDDEN_API_REFERENCE_PACKAGES,
+} from '../../../../../../../../common/constants';
+import type { PackageInfo, NewAgentPolicy, NewPackagePolicy } from '../../../../../types';
+import { SelectedPolicyTab } from '../../components';
+
+export function useDevToolsRequest({
+ newAgentPolicy,
+ packagePolicy,
+ packageInfo,
+ selectedPolicyTab,
+ withSysMonitoring,
+}: {
+ withSysMonitoring: boolean;
+ selectedPolicyTab: SelectedPolicyTab;
+ newAgentPolicy: NewAgentPolicy;
+ packagePolicy: NewPackagePolicy;
+ packageInfo?: PackageInfo;
+}) {
+ const { showDevtoolsRequest: isShowDevtoolRequestExperimentEnabled } =
+ ExperimentalFeaturesService.get();
+
+ const showDevtoolsRequest =
+ !HIDDEN_API_REFERENCE_PACKAGES.includes(packageInfo?.name ?? '') &&
+ isShowDevtoolRequestExperimentEnabled;
+
+ const [devtoolRequest, devtoolRequestDescription] = useMemo(() => {
+ if (selectedPolicyTab === SelectedPolicyTab.NEW) {
+ const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE;
+ return [
+ `${generateCreateAgentPolicyDevToolsRequest(
+ newAgentPolicy,
+ withSysMonitoring && !packagePolicyIsSystem
+ )}\n\n${generateCreatePackagePolicyDevToolsRequest({
+ ...packagePolicy,
+ })}`,
+ i18n.translate(
+ 'xpack.fleet.createPackagePolicy.devtoolsRequestWithAgentPolicyDescription',
+ {
+ defaultMessage:
+ 'These Kibana requests creates a new agent policy and a new package policy.',
+ }
+ ),
+ ];
+ }
+
+ return [
+ generateCreatePackagePolicyDevToolsRequest({
+ ...packagePolicy,
+ }),
+ i18n.translate('xpack.fleet.createPackagePolicy.devtoolsRequestDescription', {
+ defaultMessage: 'This Kibana request creates a new package policy.',
+ }),
+ ];
+ }, [packagePolicy, newAgentPolicy, withSysMonitoring, selectedPolicyTab]);
+
+ return { showDevtoolsRequest, devtoolRequest, devtoolRequestDescription };
+}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx
new file mode 100644
index 0000000000000..e0f206ef612a8
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx
@@ -0,0 +1,318 @@
+/*
+ * 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 { useCallback, useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { safeLoad } from 'js-yaml';
+
+import type {
+ AgentPolicy,
+ NewPackagePolicy,
+ NewAgentPolicy,
+ CreatePackagePolicyRequest,
+ PackagePolicy,
+ PackageInfo,
+} from '../../../../../types';
+import {
+ useStartServices,
+ sendCreateAgentPolicy,
+ sendCreatePackagePolicy,
+ sendBulkInstallPackages,
+} from '../../../../../hooks';
+import { isVerificationError } from '../../../../../services';
+import { FLEET_ELASTIC_AGENT_PACKAGE, FLEET_SYSTEM_PACKAGE } from '../../../../../../../../common';
+import { useConfirmForceInstall } from '../../../../../../integrations/hooks';
+import { validatePackagePolicy, validationHasErrors } from '../../services';
+import type { PackagePolicyValidationResults } from '../../services';
+import type { PackagePolicyFormState } from '../../types';
+import { SelectedPolicyTab } from '../../components';
+import { useOnSaveNavigate } from '../../hooks';
+
+async function createAgentPolicy({
+ packagePolicy,
+ newAgentPolicy,
+ withSysMonitoring,
+}: {
+ packagePolicy: NewPackagePolicy;
+ newAgentPolicy: NewAgentPolicy;
+ withSysMonitoring: boolean;
+}): Promise {
+ // do not create agent policy with system integration if package policy already is for system package
+ const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE;
+ const resp = await sendCreateAgentPolicy(newAgentPolicy, {
+ withSysMonitoring: withSysMonitoring && !packagePolicyIsSystem,
+ });
+ if (resp.error) {
+ throw resp.error;
+ }
+ if (!resp.data) {
+ throw new Error('Invalid agent policy creation no data');
+ }
+ return resp.data.item;
+}
+
+async function savePackagePolicy(pkgPolicy: CreatePackagePolicyRequest['body']) {
+ const result = await sendCreatePackagePolicy(pkgPolicy);
+
+ return result;
+}
+
+export function useOnSubmit({
+ agentCount,
+ selectedPolicyTab,
+ newAgentPolicy,
+ withSysMonitoring,
+ queryParamsPolicyId,
+ packageInfo,
+}: {
+ packageInfo?: PackageInfo;
+ newAgentPolicy: NewAgentPolicy;
+ withSysMonitoring: boolean;
+ selectedPolicyTab: SelectedPolicyTab;
+ agentCount: number;
+ queryParamsPolicyId: string | undefined;
+}) {
+ const { notifications } = useStartServices();
+ const confirmForceInstall = useConfirmForceInstall();
+ // only used to store the resulting package policy once saved
+ const [savedPackagePolicy, setSavedPackagePolicy] = useState();
+ // Form state
+ const [formState, setFormState] = useState('VALID');
+
+ const [agentPolicy, setAgentPolicy] = useState();
+ // New package policy state
+ const [packagePolicy, setPackagePolicy] = useState({
+ name: '',
+ description: '',
+ namespace: 'default',
+ policy_id: '',
+ enabled: true,
+ inputs: [],
+ });
+
+ // Validation state
+ const [validationResults, setValidationResults] = useState();
+ const [hasAgentPolicyError, setHasAgentPolicyError] = useState(false);
+ const hasErrors = validationResults ? validationHasErrors(validationResults) : false;
+
+ // Update agent policy method
+ const updateAgentPolicy = useCallback(
+ (updatedAgentPolicy: AgentPolicy | undefined) => {
+ if (updatedAgentPolicy) {
+ setAgentPolicy(updatedAgentPolicy);
+ if (packageInfo) {
+ setHasAgentPolicyError(false);
+ }
+ } else {
+ setHasAgentPolicyError(true);
+ setAgentPolicy(undefined);
+ }
+
+ // eslint-disable-next-line no-console
+ console.debug('Agent policy updated', updatedAgentPolicy);
+ },
+ [packageInfo, setAgentPolicy]
+ );
+ // Update package policy validation
+ const updatePackagePolicyValidation = useCallback(
+ (newPackagePolicy?: NewPackagePolicy) => {
+ if (packageInfo) {
+ const newValidationResult = validatePackagePolicy(
+ newPackagePolicy || packagePolicy,
+ packageInfo,
+ safeLoad
+ );
+ setValidationResults(newValidationResult);
+ // eslint-disable-next-line no-console
+ console.debug('Package policy validation results', newValidationResult);
+
+ return newValidationResult;
+ }
+ },
+ [packagePolicy, packageInfo]
+ );
+ // Update package policy method
+ const updatePackagePolicy = useCallback(
+ (updatedFields: Partial) => {
+ const newPackagePolicy = {
+ ...packagePolicy,
+ ...updatedFields,
+ };
+ setPackagePolicy(newPackagePolicy);
+
+ // eslint-disable-next-line no-console
+ console.debug('Package policy updated', newPackagePolicy);
+ const newValidationResults = updatePackagePolicyValidation(newPackagePolicy);
+ const hasPackage = newPackagePolicy.package;
+ const hasValidationErrors = newValidationResults
+ ? validationHasErrors(newValidationResults)
+ : false;
+ const hasAgentPolicy = newPackagePolicy.policy_id && newPackagePolicy.policy_id !== '';
+ if (
+ hasPackage &&
+ (hasAgentPolicy || selectedPolicyTab === SelectedPolicyTab.NEW) &&
+ !hasValidationErrors
+ ) {
+ setFormState('VALID');
+ } else {
+ setFormState('INVALID');
+ }
+ },
+ [packagePolicy, setFormState, updatePackagePolicyValidation, selectedPolicyTab]
+ );
+
+ const onSaveNavigate = useOnSaveNavigate({
+ packagePolicy,
+ queryParamsPolicyId,
+ });
+
+ const navigateAddAgent = (policy?: PackagePolicy) =>
+ onSaveNavigate(policy, ['openEnrollmentFlyout']);
+
+ const navigateAddAgentHelp = (policy?: PackagePolicy) =>
+ onSaveNavigate(policy, ['showAddAgentHelp']);
+
+ const onSubmit = useCallback(
+ async ({
+ force,
+ overrideCreatedAgentPolicy,
+ }: { overrideCreatedAgentPolicy?: AgentPolicy; force?: boolean } = {}) => {
+ if (formState === 'VALID' && hasErrors) {
+ setFormState('INVALID');
+ return;
+ }
+ if (agentCount !== 0 && formState !== 'CONFIRM') {
+ setFormState('CONFIRM');
+ return;
+ }
+ let createdPolicy = overrideCreatedAgentPolicy;
+ if (selectedPolicyTab === SelectedPolicyTab.NEW && !overrideCreatedAgentPolicy) {
+ try {
+ setFormState('LOADING');
+ if ((withSysMonitoring || newAgentPolicy.monitoring_enabled?.length) ?? 0 > 0) {
+ const packagesToPreinstall: string[] = [];
+ if (packageInfo) {
+ packagesToPreinstall.push(packageInfo.name);
+ }
+ if (withSysMonitoring) {
+ packagesToPreinstall.push(FLEET_SYSTEM_PACKAGE);
+ }
+ if (newAgentPolicy.monitoring_enabled?.length ?? 0 > 0) {
+ packagesToPreinstall.push(FLEET_ELASTIC_AGENT_PACKAGE);
+ }
+
+ if (packagesToPreinstall.length > 0) {
+ await sendBulkInstallPackages([...new Set(packagesToPreinstall)]);
+ }
+ }
+
+ createdPolicy = await createAgentPolicy({
+ newAgentPolicy,
+ packagePolicy,
+ withSysMonitoring,
+ });
+ setAgentPolicy(createdPolicy);
+ updatePackagePolicy({ policy_id: createdPolicy.id });
+ } catch (e) {
+ setFormState('VALID');
+ notifications.toasts.addError(e, {
+ title: i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', {
+ defaultMessage: 'Unable to create agent policy',
+ }),
+ });
+ return;
+ }
+ }
+
+ setFormState('LOADING');
+ // passing pkgPolicy with policy_id here as setPackagePolicy doesn't propagate immediately
+ const { error, data } = await savePackagePolicy({
+ ...packagePolicy,
+ policy_id: createdPolicy?.id ?? packagePolicy.policy_id,
+ force,
+ });
+ setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_NO_AGENTS');
+ if (!error) {
+ setSavedPackagePolicy(data!.item);
+
+ const hasAgentsAssigned = agentCount && agentPolicy;
+ if (!hasAgentsAssigned) {
+ setFormState('SUBMITTED_NO_AGENTS');
+ return;
+ }
+ onSaveNavigate(data!.item);
+
+ notifications.toasts.addSuccess({
+ title: i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationTitle', {
+ defaultMessage: `'{packagePolicyName}' integration added.`,
+ values: {
+ packagePolicyName: packagePolicy.name,
+ },
+ }),
+ text: hasAgentsAssigned
+ ? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', {
+ defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`,
+ values: {
+ agentPolicyName: agentPolicy!.name,
+ },
+ })
+ : undefined,
+ 'data-test-subj': 'packagePolicyCreateSuccessToast',
+ });
+ } else {
+ if (isVerificationError(error)) {
+ setFormState('VALID'); // don't show the add agent modal
+ const forceInstall = await confirmForceInstall(packagePolicy.package!);
+
+ if (forceInstall) {
+ // skip creating the agent policy because it will have already been successfully created
+ onSubmit({ overrideCreatedAgentPolicy: createdPolicy, force: true });
+ }
+ return;
+ }
+ notifications.toasts.addError(error, {
+ title: 'Error',
+ });
+ setFormState('VALID');
+ }
+ },
+ [
+ formState,
+ hasErrors,
+ agentCount,
+ selectedPolicyTab,
+ packagePolicy,
+ notifications.toasts,
+ agentPolicy,
+ onSaveNavigate,
+ confirmForceInstall,
+ newAgentPolicy,
+ updatePackagePolicy,
+ withSysMonitoring,
+ packageInfo,
+ ]
+ );
+
+ return {
+ agentPolicy,
+ updateAgentPolicy,
+ packagePolicy,
+ updatePackagePolicy,
+ savedPackagePolicy,
+ onSubmit,
+ formState,
+ setFormState,
+ hasErrors,
+ validationResults,
+ setValidationResults,
+ hasAgentPolicyError,
+ setHasAgentPolicyError,
+ // TODO check
+ navigateAddAgent,
+ navigateAddAgentHelp,
+ };
+}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx
new file mode 100644
index 0000000000000..33d1cee841590
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { useDevToolsRequest } from './devtools_request';
+export { useOnSubmit } from './form';
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
index fae3c84f21268..02f36e2cadcfe 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
@@ -21,35 +21,16 @@ import {
EuiErrorBoundary,
} from '@elastic/eui';
import type { EuiStepProps } from '@elastic/eui/src/components/steps/step';
-import { safeLoad } from 'js-yaml';
-import { useCancelAddPackagePolicy, useOnSaveNavigate } from '../hooks';
-import type { CreatePackagePolicyRequest } from '../../../../../../../common/types';
+import { useCancelAddPackagePolicy } from '../hooks';
import { splitPkgKey } from '../../../../../../../common/services';
-import {
- dataTypes,
- FLEET_SYSTEM_PACKAGE,
- HIDDEN_API_REFERENCE_PACKAGES,
-} from '../../../../../../../common/constants';
-import { useConfirmForceInstall } from '../../../../../integrations/hooks';
-import type {
- AgentPolicy,
- NewAgentPolicy,
- NewPackagePolicy,
- PackagePolicy,
-} from '../../../../types';
-import {
- sendCreatePackagePolicy,
- useStartServices,
- useConfig,
- sendGetAgentStatus,
- useGetPackageInfoByKey,
- sendCreateAgentPolicy,
-} from '../../../../hooks';
+import { dataTypes } from '../../../../../../../common/constants';
+import type { NewAgentPolicy } from '../../../../types';
+import { useConfig, sendGetAgentStatus, useGetPackageInfoByKey } from '../../../../hooks';
import {
Loading,
- Error,
+ Error as ErrorComponent,
ExtensionWrapper,
DevtoolsRequestFlyoutButton,
} from '../../../../components';
@@ -57,34 +38,21 @@ import {
import { agentPolicyFormValidation, ConfirmDeployAgentPolicyModal } from '../../components';
import { useUIExtension } from '../../../../hooks';
import type { PackagePolicyEditExtensionComponentProps } from '../../../../types';
-import {
- pkgKeyFromPackageInfo,
- isVerificationError,
- ExperimentalFeaturesService,
-} from '../../../../services';
+import { pkgKeyFromPackageInfo } from '../../../../services';
-import type {
- PackagePolicyFormState,
- AddToPolicyParams,
- CreatePackagePolicyParams,
-} from '../types';
+import type { AddToPolicyParams, CreatePackagePolicyParams } from '../types';
import { IntegrationBreadcrumb } from '../components';
-import type { PackagePolicyValidationResults } from '../services';
-import { validatePackagePolicy, validationHasErrors } from '../services';
import {
StepConfigurePackagePolicy,
StepDefinePackagePolicy,
SelectedPolicyTab,
StepSelectHosts,
} from '../components';
-import {
- generateCreatePackagePolicyDevToolsRequest,
- generateCreateAgentPolicyDevToolsRequest,
-} from '../../services';
import { CreatePackagePolicySinglePageLayout, PostInstallAddAgentModal } from './components';
+import { useDevToolsRequest, useOnSubmit } from './hooks';
const StepsWithLessPadding = styled(EuiSteps)`
.euiStep__content {
@@ -106,12 +74,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
from,
queryParamsPolicyId,
}) => {
- const { notifications } = useStartServices();
const {
agents: { enabled: isFleetEnabled },
} = useConfig();
const { params } = useRouteMatch();
- const [agentPolicy, setAgentPolicy] = useState();
const [newAgentPolicy, setNewAgentPolicy] = useState({
name: 'Agent policy 1',
@@ -123,64 +89,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
const [withSysMonitoring, setWithSysMonitoring] = useState(true);
const validation = agentPolicyFormValidation(newAgentPolicy);
- // only used to store the resulting package policy once saved
- const [savedPackagePolicy, setSavedPackagePolicy] = useState();
-
- // Retrieve agent count
- const agentPolicyId = agentPolicy?.id;
-
- const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({
- from,
- pkgkey: params.pkgkey,
- agentPolicyId,
- });
- useEffect(() => {
- const getAgentCount = async () => {
- const { data } = await sendGetAgentStatus({ policyId: agentPolicyId });
- if (data?.results.total !== undefined) {
- setAgentCount(data.results.total);
- }
- };
-
- if (isFleetEnabled && agentPolicyId) {
- getAgentCount();
- }
- }, [agentPolicyId, isFleetEnabled]);
- const [agentCount, setAgentCount] = useState(0);
-
const [selectedPolicyTab, setSelectedPolicyTab] = useState(
queryParamsPolicyId ? SelectedPolicyTab.EXISTING : SelectedPolicyTab.NEW
);
- // New package policy state
- const [packagePolicy, setPackagePolicy] = useState({
- name: '',
- description: '',
- namespace: 'default',
- policy_id: '',
- enabled: true,
- inputs: [],
- });
-
- const onSaveNavigate = useOnSaveNavigate({
- packagePolicy,
- queryParamsPolicyId,
- });
- const navigateAddAgent = (policy?: PackagePolicy) =>
- onSaveNavigate(policy, ['openEnrollmentFlyout']);
-
- const navigateAddAgentHelp = (policy?: PackagePolicy) =>
- onSaveNavigate(policy, ['showAddAgentHelp']);
-
- const confirmForceInstall = useConfirmForceInstall();
-
- // Validation state
- const [validationResults, setValidationResults] = useState();
- const [hasAgentPolicyError, setHasAgentPolicyError] = useState(false);
-
- // Form state
- const [formState, setFormState] = useState('VALID');
-
const { pkgName, pkgVersion } = splitPkgKey(params.pkgkey);
// Fetch package info
const {
@@ -194,43 +106,50 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
}
}, [packageInfoData]);
- // Update agent policy method
- const updateAgentPolicy = useCallback(
- (updatedAgentPolicy: AgentPolicy | undefined) => {
- if (updatedAgentPolicy) {
- setAgentPolicy(updatedAgentPolicy);
- if (packageInfo) {
+ const [agentCount, setAgentCount] = useState(0);
+
+ // Save package policy
+ const {
+ onSubmit,
+ updatePackagePolicy,
+ packagePolicy,
+ agentPolicy,
+ updateAgentPolicy,
+ savedPackagePolicy,
+ formState,
+ setFormState,
+ navigateAddAgent,
+ navigateAddAgentHelp,
+ setHasAgentPolicyError,
+ validationResults,
+ hasAgentPolicyError,
+ } = useOnSubmit({
+ agentCount,
+ packageInfo,
+ newAgentPolicy,
+ selectedPolicyTab,
+ withSysMonitoring,
+ queryParamsPolicyId,
+ });
+
+ const setPolicyValidation = useCallback(
+ (selectedTab: SelectedPolicyTab, updatedAgentPolicy: NewAgentPolicy) => {
+ if (selectedTab === SelectedPolicyTab.NEW) {
+ if (
+ !updatedAgentPolicy.name ||
+ updatedAgentPolicy.name.trim() === '' ||
+ !updatedAgentPolicy.namespace ||
+ updatedAgentPolicy.namespace.trim() === ''
+ ) {
+ setHasAgentPolicyError(true);
+ } else {
setHasAgentPolicyError(false);
}
- } else {
- setHasAgentPolicyError(true);
- setAgentPolicy(undefined);
}
-
- // eslint-disable-next-line no-console
- console.debug('Agent policy updated', updatedAgentPolicy);
},
- [packageInfo, setAgentPolicy]
+ [setHasAgentPolicyError]
);
- const setPolicyValidation = (
- selectedTab: SelectedPolicyTab,
- updatedAgentPolicy: NewAgentPolicy
- ) => {
- if (selectedTab === SelectedPolicyTab.NEW) {
- if (
- !updatedAgentPolicy.name ||
- updatedAgentPolicy.name.trim() === '' ||
- !updatedAgentPolicy.namespace ||
- updatedAgentPolicy.namespace.trim() === ''
- ) {
- setHasAgentPolicyError(true);
- } else {
- setHasAgentPolicyError(false);
- }
- }
- };
-
const updateNewAgentPolicy = useCallback(
(updatedFields: Partial) => {
const updatedAgentPolicy = {
@@ -240,7 +159,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
setNewAgentPolicy(updatedAgentPolicy);
setPolicyValidation(selectedPolicyTab, updatedAgentPolicy);
},
- [setNewAgentPolicy, newAgentPolicy, selectedPolicyTab]
+ [setNewAgentPolicy, setPolicyValidation, newAgentPolicy, selectedPolicyTab]
);
const updateSelectedPolicyTab = useCallback(
@@ -248,58 +167,29 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
setSelectedPolicyTab(selectedTab);
setPolicyValidation(selectedTab, newAgentPolicy);
},
- [setSelectedPolicyTab, newAgentPolicy]
+ [setSelectedPolicyTab, setPolicyValidation, newAgentPolicy]
);
- const hasErrors = validationResults ? validationHasErrors(validationResults) : false;
- // Update package policy validation
- const updatePackagePolicyValidation = useCallback(
- (newPackagePolicy?: NewPackagePolicy) => {
- if (packageInfo) {
- const newValidationResult = validatePackagePolicy(
- newPackagePolicy || packagePolicy,
- packageInfo,
- safeLoad
- );
- setValidationResults(newValidationResult);
- // eslint-disable-next-line no-console
- console.debug('Package policy validation results', newValidationResult);
-
- return newValidationResult;
- }
- },
- [packagePolicy, packageInfo]
- );
+ // Retrieve agent count
+ const agentPolicyId = agentPolicy?.id;
- // Update package policy method
- const updatePackagePolicy = useCallback(
- (updatedFields: Partial) => {
- const newPackagePolicy = {
- ...packagePolicy,
- ...updatedFields,
- };
- setPackagePolicy(newPackagePolicy);
-
- // eslint-disable-next-line no-console
- console.debug('Package policy updated', newPackagePolicy);
- const newValidationResults = updatePackagePolicyValidation(newPackagePolicy);
- const hasPackage = newPackagePolicy.package;
- const hasValidationErrors = newValidationResults
- ? validationHasErrors(newValidationResults)
- : false;
- const hasAgentPolicy = newPackagePolicy.policy_id && newPackagePolicy.policy_id !== '';
- if (
- hasPackage &&
- (hasAgentPolicy || selectedPolicyTab === SelectedPolicyTab.NEW) &&
- !hasValidationErrors
- ) {
- setFormState('VALID');
- } else {
- setFormState('INVALID');
+ const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({
+ from,
+ pkgkey: params.pkgkey,
+ agentPolicyId,
+ });
+ useEffect(() => {
+ const getAgentCount = async () => {
+ const { data } = await sendGetAgentStatus({ policyId: agentPolicyId });
+ if (data?.results.total !== undefined) {
+ setAgentCount(data.results.total);
}
- },
- [packagePolicy, updatePackagePolicyValidation, selectedPolicyTab]
- );
+ };
+
+ if (isFleetEnabled && agentPolicyId) {
+ getAgentCount();
+ }
+ }, [agentPolicyId, isFleetEnabled]);
const handleExtensionViewOnChange = useCallback<
PackagePolicyEditExtensionComponentProps['onChange']
@@ -313,132 +203,16 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
return prevState;
});
},
- [updatePackagePolicy]
- );
-
- // Save package policy
- const savePackagePolicy = useCallback(
- async (pkgPolicy: CreatePackagePolicyRequest['body']) => {
- setFormState('LOADING');
- const result = await sendCreatePackagePolicy(pkgPolicy);
- setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_NO_AGENTS');
- return result;
- },
- [agentCount]
+ [updatePackagePolicy, setFormState]
);
- const createAgentPolicy = useCallback(async (): Promise => {
- let createdAgentPolicy;
- setFormState('LOADING');
- // do not create agent policy with system integration if package policy already is for system package
- const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE;
- const resp = await sendCreateAgentPolicy(newAgentPolicy, {
- withSysMonitoring: withSysMonitoring && !packagePolicyIsSystem,
- });
- if (resp.error) {
- setFormState('VALID');
- throw resp.error;
- }
- if (resp.data) {
- createdAgentPolicy = resp.data.item;
- setAgentPolicy(createdAgentPolicy);
- updatePackagePolicy({ policy_id: createdAgentPolicy.id });
- }
- return createdAgentPolicy;
- }, [packagePolicy?.package?.name, newAgentPolicy, withSysMonitoring, updatePackagePolicy]);
-
- const onSubmit = useCallback(
- async ({
- force,
- overrideCreatedAgentPolicy,
- }: { overrideCreatedAgentPolicy?: AgentPolicy; force?: boolean } = {}) => {
- if (formState === 'VALID' && hasErrors) {
- setFormState('INVALID');
- return;
- }
- if (agentCount !== 0 && formState !== 'CONFIRM') {
- setFormState('CONFIRM');
- return;
- }
- let createdPolicy = overrideCreatedAgentPolicy;
- if (selectedPolicyTab === SelectedPolicyTab.NEW && !overrideCreatedAgentPolicy) {
- try {
- createdPolicy = await createAgentPolicy();
- } catch (e) {
- notifications.toasts.addError(e, {
- title: i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', {
- defaultMessage: 'Unable to create agent policy',
- }),
- });
- return;
- }
- }
-
- setFormState('LOADING');
- // passing pkgPolicy with policy_id here as setPackagePolicy doesn't propagate immediately
- const { error, data } = await savePackagePolicy({
- ...packagePolicy,
- policy_id: createdPolicy?.id ?? packagePolicy.policy_id,
- force,
- });
- if (!error) {
- setSavedPackagePolicy(data!.item);
-
- const hasAgentsAssigned = agentCount && agentPolicy;
- if (!hasAgentsAssigned) {
- setFormState('SUBMITTED_NO_AGENTS');
- return;
- }
- onSaveNavigate(data!.item);
-
- notifications.toasts.addSuccess({
- title: i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationTitle', {
- defaultMessage: `'{packagePolicyName}' integration added.`,
- values: {
- packagePolicyName: packagePolicy.name,
- },
- }),
- text: hasAgentsAssigned
- ? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', {
- defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`,
- values: {
- agentPolicyName: agentPolicy!.name,
- },
- })
- : undefined,
- 'data-test-subj': 'packagePolicyCreateSuccessToast',
- });
- } else {
- if (isVerificationError(error)) {
- setFormState('VALID'); // don't show the add agent modal
- const forceInstall = await confirmForceInstall(packagePolicy.package!);
-
- if (forceInstall) {
- // skip creating the agent policy because it will have already been successfully created
- onSubmit({ overrideCreatedAgentPolicy: createdPolicy, force: true });
- }
- return;
- }
- notifications.toasts.addError(error, {
- title: 'Error',
- });
- setFormState('VALID');
- }
- },
- [
- formState,
- hasErrors,
- agentCount,
- selectedPolicyTab,
- savePackagePolicy,
- packagePolicy,
- createAgentPolicy,
- notifications.toasts,
- agentPolicy,
- onSaveNavigate,
- confirmForceInstall,
- ]
- );
+ const { devtoolRequest, devtoolRequestDescription, showDevtoolsRequest } = useDevToolsRequest({
+ newAgentPolicy,
+ packagePolicy,
+ selectedPolicyTab,
+ withSysMonitoring,
+ packageInfo,
+ });
const integrationInfo = useMemo(
() =>
@@ -488,6 +262,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
withSysMonitoring,
updateSelectedPolicyTab,
queryParamsPolicyId,
+ setHasAgentPolicyError,
]
);
@@ -564,47 +339,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
},
];
- const { showDevtoolsRequest: isShowDevtoolRequestExperimentEnabled } =
- ExperimentalFeaturesService.get();
-
- const showDevtoolsRequest =
- !HIDDEN_API_REFERENCE_PACKAGES.includes(packageInfo?.name ?? '') &&
- isShowDevtoolRequestExperimentEnabled;
-
- const [devtoolRequest, devtoolRequestDescription] = useMemo(() => {
- if (selectedPolicyTab === SelectedPolicyTab.NEW) {
- const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE;
- return [
- `${generateCreateAgentPolicyDevToolsRequest(
- newAgentPolicy,
- withSysMonitoring && !packagePolicyIsSystem
- )}\n\n${generateCreatePackagePolicyDevToolsRequest({
- ...packagePolicy,
- })}`,
- i18n.translate(
- 'xpack.fleet.createPackagePolicy.devtoolsRequestWithAgentPolicyDescription',
- {
- defaultMessage:
- 'These Kibana requests creates a new agent policy and a new package policy.',
- }
- ),
- ];
- }
-
- return [
- generateCreatePackagePolicyDevToolsRequest({
- ...packagePolicy,
- }),
- i18n.translate('xpack.fleet.createPackagePolicy.devtoolsRequestDescription', {
- defaultMessage: 'This Kibana request creates a new package policy.',
- }),
- ];
- }, [packagePolicy, newAgentPolicy, withSysMonitoring, selectedPolicyTab]);
-
// Display package error if there is one
if (packageInfoError) {
return (
- {
+ return sendRequest({
+ path: epmRouteService.getBulkInstallPath(),
+ method: 'post',
+ body: {
+ packages,
+ },
+ });
+};
+
export const sendRemovePackage = (pkgName: string, pkgVersion: string, force: boolean = false) => {
return sendRequest({
path: epmRouteService.getRemovePath(pkgName, pkgVersion),