From 23666832091d0a30c00222fdd73d56af51224ff9 Mon Sep 17 00:00:00 2001
From: Scotty Bollinger
Date: Wed, 23 Jun 2021 14:43:17 -0500
Subject: [PATCH 01/86] [Enterprise Search] Add shared Users components and
enable RBAC functionality (#102826)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Add RolesEmptyPrompt component
* Move constants to shared
Will be used in next commit so DRYing them out here
* Add UserAddedInfo component
* Add UsersEmptyPrompt component
* Add UserInvitationCallout component
* Add some shared types
* Add UserSelector component
* Fix imports from a previous commit
Refactored these to shared but missed updating the implementation. See e2d3ec2ca4aba3cb6f7e8e2d2d2da96aa6bedf1b
* Add UsersHeading component
* Add UserFlyout component
* Update UsersAndRolesRowActions with confirm modal
Design calls for using a custom call out instead of window.confirm
* Add pagination size and fix type
- email can be null on bult-in elasticsearch users
* Add UsersTable component
* Remove window.confirm from logic files
The UsersAndRolesRowActions component now uses an EUI prompt for this. Whitespace changes should be hidden for this commit
* Add routes for enabling RBAC
* Update App Search routes
https://github.com/elastic/ent-search/pull/3862
added the ‘/as’ prefix to App Search role mappings routes
* Add logic for enabling role-based access
* Pass docsLink as a prop to the heading component
* Add empty states to mappings landing pages
* Fix a couple of missed i18ns
* Remove unused translations
* Remove EuiOverlayMask
This was needed in ent-search because it uses an older EUI. The newer confirm modal has its own overlay
* Update RoleMappingsTable to use new design
Previously, we showed all engines/groups in the table but the new design calls for a truncated list with additional items so [‘foo’, ‘bar’, ‘baz’] would display as “foo, bar + 1”
This is already in place for the users table
* Lint fix
* Another lint fix
* Fix test name
Co-authored-by: Jason Stoltzfus
* Move test
Co-authored-by: Jason Stoltzfus
---
.../components/role_mappings/constants.ts | 8 -
.../role_mappings/role_mappings.tsx | 22 +-
.../role_mappings/role_mappings_logic.test.ts | 49 ++--
.../role_mappings/role_mappings_logic.ts | 37 ++-
.../applications/shared/constants/index.ts | 1 +
.../applications/shared/constants/labels.ts | 15 ++
.../__mocks__/elasticsearch_users.ts | 13 ++
.../shared/role_mapping/__mocks__/roles.ts | 19 ++
.../shared/role_mapping/constants.ts | 213 +++++++++++++++++-
.../applications/shared/role_mapping/index.ts | 8 +
.../role_mappings_heading.test.tsx | 8 +-
.../role_mapping/role_mappings_heading.tsx | 8 +-
.../role_mapping/role_mappings_table.test.tsx | 34 +--
.../role_mapping/role_mappings_table.tsx | 37 ++-
.../role_mapping/roles_empty_prompt.test.tsx | 39 ++++
.../role_mapping/roles_empty_prompt.tsx | 48 ++++
.../role_mapping/user_added_info.test.tsx | 28 +++
.../shared/role_mapping/user_added_info.tsx | 40 ++++
.../shared/role_mapping/user_flyout.test.tsx | 70 ++++++
.../shared/role_mapping/user_flyout.tsx | 113 ++++++++++
.../user_invitation_callout.test.tsx | 46 ++++
.../role_mapping/user_invitation_callout.tsx | 47 ++++
.../role_mapping/user_selector.test.tsx | 112 +++++++++
.../shared/role_mapping/user_selector.tsx | 159 +++++++++++++
.../users_and_roles_row_actions.test.tsx | 22 +-
.../users_and_roles_row_actions.tsx | 63 +++++-
.../role_mapping/users_empty_prompt.test.tsx | 22 ++
.../role_mapping/users_empty_prompt.tsx | 43 ++++
.../role_mapping/users_heading.test.tsx | 32 +++
.../shared/role_mapping/users_heading.tsx | 37 +++
.../shared/role_mapping/users_table.test.tsx | 100 ++++++++
.../shared/role_mapping/users_table.tsx | 147 ++++++++++++
.../public/applications/shared/types.ts | 16 ++
.../groups/components/group_users_table.tsx | 18 +-
.../views/role_mappings/constants.ts | 8 -
.../views/role_mappings/role_mappings.tsx | 27 ++-
.../role_mappings/role_mappings_logic.test.ts | 50 ++--
.../role_mappings/role_mappings_logic.ts | 37 ++-
.../routes/app_search/role_mappings.test.ts | 37 ++-
.../server/routes/app_search/role_mappings.ts | 24 +-
.../workplace_search/role_mappings.test.ts | 29 ++-
.../routes/workplace_search/role_mappings.ts | 16 ++
.../translations/translations/ja-JP.json | 9 +-
.../translations/translations/zh-CN.json | 9 +-
44 files changed, 1748 insertions(+), 172 deletions(-)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_invitation_callout.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_invitation_callout.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_empty_prompt.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_empty_prompt.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_heading.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_heading.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts
index df1e19e264c75..cce18cbeffd0a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts
@@ -9,14 +9,6 @@ import { i18n } from '@kbn/i18n';
import { AdvanceRoleType } from '../../types';
-export const DELETE_ROLE_MAPPING_MESSAGE = i18n.translate(
- 'xpack.enterpriseSearch.appSearch.deleteRoleMappingMessage',
- {
- defaultMessage:
- 'Are you sure you want to permanently delete this mapping? This action is not reversible and some users might lose access.',
- }
-);
-
export const ROLE_MAPPING_DELETED_MESSAGE = i18n.translate(
'xpack.enterpriseSearch.appSearch.roleMappingDeletedMessage',
{
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx
index db0e6e6dead11..03e2ae67eca9e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx
@@ -10,16 +10,25 @@ import React, { useEffect } from 'react';
import { useActions, useValues } from 'kea';
import { APP_SEARCH_PLUGIN } from '../../../../../common/constants';
-import { RoleMappingsTable, RoleMappingsHeading } from '../../../shared/role_mapping';
+import {
+ RoleMappingsTable,
+ RoleMappingsHeading,
+ RolesEmptyPrompt,
+} from '../../../shared/role_mapping';
import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants';
+
+import { DOCS_PREFIX } from '../../routes';
import { AppSearchPageTemplate } from '../layout';
import { ROLE_MAPPINGS_ENGINE_ACCESS_HEADING } from './constants';
import { RoleMapping } from './role_mapping';
import { RoleMappingsLogic } from './role_mappings_logic';
+const ROLES_DOCS_LINK = `${DOCS_PREFIX}/security-and-users.html`;
+
export const RoleMappings: React.FC = () => {
const {
+ enableRoleBasedAccess,
initializeRoleMappings,
initializeRoleMapping,
handleDeleteMapping,
@@ -37,10 +46,19 @@ export const RoleMappings: React.FC = () => {
return resetState;
}, []);
+ const rolesEmptyState = (
+
+ );
+
const roleMappingsSection = (
initializeRoleMapping()}
/>
{
pageChrome={[ROLE_MAPPINGS_TITLE]}
pageHeader={{ pageTitle: ROLE_MAPPINGS_TITLE }}
isLoading={dataLoading}
+ isEmptyState={roleMappings.length < 1}
+ emptyState={rolesEmptyState}
>
{roleMappingFlyoutOpen && }
{roleMappingsSection}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts
index 870e303a2930d..6985f213d1dd5 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts
@@ -87,6 +87,13 @@ describe('RoleMappingsLogic', () => {
});
});
+ it('setRoleMappings', () => {
+ RoleMappingsLogic.actions.setRoleMappings({ roleMappings: [asRoleMapping] });
+
+ expect(RoleMappingsLogic.values.roleMappings).toEqual([asRoleMapping]);
+ expect(RoleMappingsLogic.values.dataLoading).toEqual(false);
+ });
+
it('handleRoleChange', () => {
RoleMappingsLogic.actions.handleRoleChange('dev');
@@ -266,6 +273,30 @@ describe('RoleMappingsLogic', () => {
});
describe('listeners', () => {
+ describe('enableRoleBasedAccess', () => {
+ it('calls API and sets values', async () => {
+ const setRoleMappingsSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMappings');
+ http.post.mockReturnValue(Promise.resolve(mappingsServerProps));
+ RoleMappingsLogic.actions.enableRoleBasedAccess();
+
+ expect(RoleMappingsLogic.values.dataLoading).toEqual(true);
+
+ expect(http.post).toHaveBeenCalledWith(
+ '/api/app_search/role_mappings/enable_role_based_access'
+ );
+ await nextTick();
+ expect(setRoleMappingsSpy).toHaveBeenCalledWith(mappingsServerProps);
+ });
+
+ it('handles error', async () => {
+ http.post.mockReturnValue(Promise.reject('this is an error'));
+ RoleMappingsLogic.actions.enableRoleBasedAccess();
+ await nextTick();
+
+ expect(flashAPIErrors).toHaveBeenCalledWith('this is an error');
+ });
+ });
+
describe('initializeRoleMappings', () => {
it('calls API and sets values', async () => {
const setRoleMappingsDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMappingsData');
@@ -400,18 +431,8 @@ describe('RoleMappingsLogic', () => {
});
describe('handleDeleteMapping', () => {
- let confirmSpy: any;
const roleMappingId = 'r1';
- beforeEach(() => {
- confirmSpy = jest.spyOn(window, 'confirm');
- confirmSpy.mockImplementation(jest.fn(() => true));
- });
-
- afterEach(() => {
- confirmSpy.mockRestore();
- });
-
it('calls API and refreshes list', async () => {
mount(mappingsServerProps);
const initializeRoleMappingsSpy = jest.spyOn(
@@ -436,14 +457,6 @@ describe('RoleMappingsLogic', () => {
expect(flashAPIErrors).toHaveBeenCalledWith('this is an error');
});
-
- it('will do nothing if not confirmed', () => {
- mount(mappingsServerProps);
- jest.spyOn(window, 'confirm').mockReturnValueOnce(false);
- RoleMappingsLogic.actions.handleDeleteMapping(roleMappingId);
-
- expect(http.delete).not.toHaveBeenCalled();
- });
});
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts
index fc0a235b23c77..e2ef75897528c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts
@@ -22,7 +22,6 @@ import { roleHasScopedEngines } from '../../utils/role/has_scoped_engines';
import { Engine } from '../engine/types';
import {
- DELETE_ROLE_MAPPING_MESSAGE,
ROLE_MAPPING_DELETED_MESSAGE,
ROLE_MAPPING_CREATED_MESSAGE,
ROLE_MAPPING_UPDATED_MESSAGE,
@@ -59,10 +58,16 @@ interface RoleMappingsActions {
initializeRoleMappings(): void;
resetState(): void;
setRoleMapping(roleMapping: ASRoleMapping): { roleMapping: ASRoleMapping };
+ setRoleMappings({
+ roleMappings,
+ }: {
+ roleMappings: ASRoleMapping[];
+ }): { roleMappings: ASRoleMapping[] };
setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails;
openRoleMappingFlyout(): void;
closeRoleMappingFlyout(): void;
setRoleMappingErrors(errors: string[]): { errors: string[] };
+ enableRoleBasedAccess(): void;
}
interface RoleMappingsValues {
@@ -91,6 +96,7 @@ export const RoleMappingsLogic = kea data,
setRoleMapping: (roleMapping: ASRoleMapping) => ({ roleMapping }),
+ setRoleMappings: ({ roleMappings }: { roleMappings: ASRoleMapping[] }) => ({ roleMappings }),
setRoleMappingErrors: (errors: string[]) => ({ errors }),
handleAuthProviderChange: (value: string) => ({ value }),
handleRoleChange: (roleType: RoleTypes) => ({ roleType }),
@@ -101,6 +107,7 @@ export const RoleMappingsLogic = kea ({ value }),
handleAccessAllEnginesChange: (selected: boolean) => ({ selected }),
+ enableRoleBasedAccess: true,
resetState: true,
initializeRoleMappings: true,
initializeRoleMapping: (roleMappingId) => ({ roleMappingId }),
@@ -114,13 +121,16 @@ export const RoleMappingsLogic = kea false,
+ setRoleMappings: () => false,
resetState: () => true,
+ enableRoleBasedAccess: () => true,
},
],
roleMappings: [
[],
{
setRoleMappingsData: (_, { roleMappings }) => roleMappings,
+ setRoleMappings: (_, { roleMappings }) => roleMappings,
resetState: () => [],
},
],
@@ -267,6 +277,17 @@ export const RoleMappingsLogic = kea ({
+ enableRoleBasedAccess: async () => {
+ const { http } = HttpLogic.values;
+ const route = '/api/app_search/role_mappings/enable_role_based_access';
+
+ try {
+ const response = await http.post(route);
+ actions.setRoleMappings(response);
+ } catch (e) {
+ flashAPIErrors(e);
+ }
+ },
initializeRoleMappings: async () => {
const { http } = HttpLogic.values;
const route = '/api/app_search/role_mappings';
@@ -286,14 +307,12 @@ export const RoleMappingsLogic = kea {
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/constants/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/constants/index.ts
index 70990727b8a62..b15bd9e1155cc 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/constants/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/constants/index.ts
@@ -6,4 +6,5 @@
*/
export * from './actions';
+export * from './labels';
export { DEFAULT_META } from './default_meta';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts b/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts
new file mode 100644
index 0000000000000..8e6159d2b5b2a
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const USERNAME_LABEL = i18n.translate('xpack.enterpriseSearch.usernameLabel', {
+ defaultMessage: 'Username',
+});
+export const EMAIL_LABEL = i18n.translate('xpack.enterpriseSearch.emailLabel', {
+ defaultMessage: 'Email',
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts
new file mode 100644
index 0000000000000..500f560675679
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const elasticsearchUsers = [
+ {
+ email: 'user1@user.com',
+ username: 'user1',
+ },
+];
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/roles.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/roles.ts
index 15dec753351ba..486c1ba6c9af6 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/roles.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/roles.ts
@@ -9,6 +9,8 @@ import { engines } from '../../../app_search/__mocks__/engines.mock';
import { AttributeName } from '../../types';
+import { elasticsearchUsers } from './elasticsearch_users';
+
export const asRoleMapping = {
id: 'sdgfasdgadf123',
attributeName: 'role' as AttributeName,
@@ -70,3 +72,20 @@ export const wsRoleMapping = {
},
],
};
+
+export const invitation = {
+ email: 'foo@example.com',
+ code: '123fooqwe',
+};
+
+export const wsSingleUserRoleMapping = {
+ invitation,
+ elasticsearchUser: elasticsearchUsers[0],
+ roleMapping: wsRoleMapping,
+};
+
+export const asSingleUserRoleMapping = {
+ invitation,
+ elasticsearchUser: elasticsearchUsers[0],
+ roleMapping: asRoleMapping,
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts
index 9f40844e52470..45cab32b67e08 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts
@@ -50,10 +50,26 @@ export const ROLE_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.rol
defaultMessage: 'Role',
});
+export const USERNAME_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.usernameLabel', {
+ defaultMessage: 'Username',
+});
+
+export const EMAIL_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.emailLabel', {
+ defaultMessage: 'Email',
+});
+
export const ALL_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.allLabel', {
defaultMessage: 'All',
});
+export const GROUPS_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.groupsLabel', {
+ defaultMessage: 'Groups',
+});
+
+export const ENGINES_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.enginesLabel', {
+ defaultMessage: 'Engines',
+});
+
export const AUTH_PROVIDER_LABEL = i18n.translate(
'xpack.enterpriseSearch.roleMapping.authProviderLabel',
{
@@ -82,10 +98,10 @@ export const ATTRIBUTE_VALUE_ERROR = i18n.translate(
}
);
-export const DELETE_ROLE_MAPPING_TITLE = i18n.translate(
- 'xpack.enterpriseSearch.roleMapping.deleteRoleMappingTitle',
+export const REMOVE_ROLE_MAPPING_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.removeRoleMappingTitle',
{
- defaultMessage: 'Remove this role mapping',
+ defaultMessage: 'Remove role mapping',
}
);
@@ -96,10 +112,17 @@ export const DELETE_ROLE_MAPPING_DESCRIPTION = i18n.translate(
}
);
-export const DELETE_ROLE_MAPPING_BUTTON = i18n.translate(
- 'xpack.enterpriseSearch.roleMapping.deleteRoleMappingButton',
+export const REMOVE_ROLE_MAPPING_BUTTON = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.removeRoleMappingButton',
+ {
+ defaultMessage: 'Remove mapping',
+ }
+);
+
+export const REMOVE_USER_BUTTON = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.removeUserButton',
{
- defaultMessage: 'Delete mapping',
+ defaultMessage: 'Remove user',
}
);
@@ -205,3 +228,181 @@ export const ROLE_MAPPINGS_NO_RESULTS_MESSAGE = i18n.translate(
'xpack.enterpriseSearch.roleMapping.noResults.message',
{ defaultMessage: 'Create a new role mapping' }
);
+
+export const ROLES_DISABLED_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.rolesDisabledTitle',
+ { defaultMessage: 'Role-based access is disabled' }
+);
+
+export const ROLES_DISABLED_DESCRIPTION = (productName: ProductName) =>
+ i18n.translate('xpack.enterpriseSearch.roleMapping.rolesDisabledDescription', {
+ defaultMessage:
+ 'All users set for this deployment currently have full access to {productName}. To restrict access and manage permissions, you must enable role-based access for Enterprise Search.',
+ values: { productName },
+ });
+
+export const ROLES_DISABLED_NOTE = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.rolesDisabledNote',
+ {
+ defaultMessage:
+ 'Note: enabling role-based access restricts access for both App Search and Workplace Search. Once enabled, review access management for both products, if applicable.',
+ }
+);
+
+export const ENABLE_ROLES_BUTTON = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.enableRolesButton',
+ { defaultMessage: 'Enable role-based access' }
+);
+
+export const ENABLE_ROLES_LINK = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.enableRolesLink',
+ { defaultMessage: 'Learn more about role-based access' }
+);
+
+export const INVITATION_DESCRIPTION = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.invitationDescription',
+ {
+ defaultMessage:
+ 'This URL can be shared with the user, allowing them to accept the Enterprise Search invitation and set a new password',
+ }
+);
+
+export const NEW_INVITATION_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.newInvitationLabel',
+ { defaultMessage: 'Invitation URL' }
+);
+
+export const EXISTING_INVITATION_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.existingInvitationLabel',
+ { defaultMessage: 'The user has not yet accepted the invitation.' }
+);
+
+export const INVITATION_LINK = i18n.translate('xpack.enterpriseSearch.roleMapping.invitationLink', {
+ defaultMessage: 'Enterprise Search Invitation Link',
+});
+
+export const NO_USERS_TITLE = i18n.translate('xpack.enterpriseSearch.roleMapping.noUsersTitle', {
+ defaultMessage: 'No user added',
+});
+
+export const NO_USERS_DESCRIPTION = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.noUsersDescription',
+ {
+ defaultMessage:
+ 'Users can be added individually, for flexibility. Role mappings provide a broader interface for adding large number of users using user attributes.',
+ }
+);
+
+export const ENABLE_USERS_LINK = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.enableUsersLink',
+ { defaultMessage: 'Learn more about user management' }
+);
+
+export const NEW_USER_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.newUserLabel', {
+ defaultMessage: 'Create new user',
+});
+
+export const EXISTING_USER_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.existingUserLabel',
+ { defaultMessage: 'Add existing user' }
+);
+
+export const USERNAME_NO_USERS_TEXT = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.usernameNoUsersText',
+ { defaultMessage: 'No existing user eligible for addition.' }
+);
+
+export const REQUIRED_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.requiredLabel', {
+ defaultMessage: 'Required',
+});
+
+export const USERS_HEADING_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.usersHeadingTitle',
+ { defaultMessage: 'Users' }
+);
+
+export const USERS_HEADING_DESCRIPTION = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.usersHeadingDescription',
+ {
+ defaultMessage:
+ 'User management provides granular access for individual or special permission needs. Users from federated sources such as SAML are managed by role mappings, and excluded from this list.',
+ }
+);
+
+export const USERS_HEADING_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.usersHeadingLabel',
+ { defaultMessage: 'Add a new user' }
+);
+
+export const UPDATE_USER_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.updateUserLabel',
+ {
+ defaultMessage: 'Update user',
+ }
+);
+
+export const ADD_USER_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.addUserLabel', {
+ defaultMessage: 'Add user',
+});
+
+export const USER_ADDED_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.userAddedLabel',
+ {
+ defaultMessage: 'User added',
+ }
+);
+
+export const USER_UPDATED_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.userUpdatedLabel',
+ {
+ defaultMessage: 'User updated',
+ }
+);
+
+export const NEW_USER_DESCRIPTION = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.newUserDescription',
+ {
+ defaultMessage: 'Provide granular access and permissions',
+ }
+);
+
+export const UPDATE_USER_DESCRIPTION = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.updateUserDescription',
+ {
+ defaultMessage: 'Manage granular access and permissions',
+ }
+);
+
+export const INVITATION_PENDING_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.invitationPendingLabel',
+ {
+ defaultMessage: 'Invitation pending',
+ }
+);
+
+export const ROLE_MODAL_TEXT = i18n.translate('xpack.enterpriseSearch.roleMapping.roleModalText', {
+ defaultMessage:
+ 'Removing a role mapping revokes access to any user corresponding to the mapping attributes, but may not take effect immediately for SAML-governed roles. Users with an active SAML session will retain access until it expires.',
+});
+
+export const USER_MODAL_TITLE = (username: string) =>
+ i18n.translate('xpack.enterpriseSearch.roleMapping.userModalTitle', {
+ defaultMessage: 'Remove {username}',
+ values: { username },
+ });
+
+export const USER_MODAL_TEXT = i18n.translate('xpack.enterpriseSearch.roleMapping.userModalText', {
+ defaultMessage:
+ 'Removing a user immediately revokes access to the experience, unless this user’s attributes also corresponds to a role mapping for native and SAML-governed authentication, in which case associated role mappings should also be reviewed and adjusted, as needed.',
+});
+
+export const FILTER_USERS_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.filterUsersLabel',
+ {
+ defaultMessage: 'Filter users',
+ }
+);
+
+export const NO_USERS_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.noUsersLabel', {
+ defaultMessage: 'No matching users found',
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts
index b0d10e9692714..8096b86939ff3 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts
@@ -6,9 +6,17 @@
*/
export { AttributeSelector } from './attribute_selector';
+export { RolesEmptyPrompt } from './roles_empty_prompt';
export { RoleMappingsTable } from './role_mappings_table';
export { RoleOptionLabel } from './role_option_label';
export { RoleSelector } from './role_selector';
export { RoleMappingFlyout } from './role_mapping_flyout';
export { RoleMappingsHeading } from './role_mappings_heading';
+export { UserAddedInfo } from './user_added_info';
+export { UserFlyout } from './user_flyout';
+export { UsersHeading } from './users_heading';
+export { UserInvitationCallout } from './user_invitation_callout';
+export { UserSelector } from './user_selector';
+export { UsersTable } from './users_table';
export { UsersAndRolesRowActions } from './users_and_roles_row_actions';
+export { UsersEmptyPrompt } from './users_empty_prompt';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.test.tsx
index f0bf86fb306c6..5a2958d60dc2c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.test.tsx
@@ -15,7 +15,13 @@ import { RoleMappingsHeading } from './role_mappings_heading';
describe('RoleMappingsHeading', () => {
it('renders ', () => {
- const wrapper = shallow( );
+ const wrapper = shallow(
+
+ );
expect(wrapper.find(EuiTitle)).toHaveLength(1);
expect(wrapper.find(EuiText)).toHaveLength(1);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.tsx
index eee8b180d3281..1984cc6c60a34 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.tsx
@@ -28,13 +28,11 @@ import {
interface Props {
productName: ProductName;
+ docsLink: string;
onClick(): void;
}
-// TODO: Replace EuiLink href with acutal docs link when available
-const ROLE_MAPPINGS_DOCS_HREF = '#TODO';
-
-export const RoleMappingsHeading: React.FC = ({ productName, onClick }) => (
+export const RoleMappingsHeading: React.FC = ({ productName, docsLink, onClick }) => (
@@ -45,7 +43,7 @@ export const RoleMappingsHeading: React.FC = ({ productName, onClick }) =
{ROLE_MAPPINGS_HEADING_DESCRIPTION(productName)}{' '}
-
+
{ROLE_MAPPINGS_HEADING_DOCS_LINK}
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.test.tsx
index 156b52a4016c3..81a7c06020165 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.test.tsx
@@ -13,7 +13,9 @@ import { mount } from 'enzyme';
import { EuiInMemoryTable, EuiTableHeaderCell } from '@elastic/eui';
-import { ALL_LABEL, ANY_AUTH_PROVIDER_OPTION_LABEL } from './constants';
+import { engines } from '../../app_search/__mocks__/engines.mock';
+
+import { ANY_AUTH_PROVIDER_OPTION_LABEL } from './constants';
import { RoleMappingsTable } from './role_mappings_table';
import { UsersAndRolesRowActions } from './users_and_roles_row_actions';
@@ -78,28 +80,30 @@ describe('RoleMappingsTable', () => {
expect(handleDeleteMapping).toHaveBeenCalled();
});
- it('shows default message when "accessAllEngines" is true', () => {
+ it('handles access items display for all items', () => {
const wrapper = mount(
);
- expect(wrapper.find('[data-test-subj="AccessItemsList"]').prop('children')).toEqual(ALL_LABEL);
+ expect(wrapper.find('[data-test-subj="AllItems"]')).toHaveLength(1);
});
- it('handles display when no items present', () => {
- const noItemsRoleMapping = { ...asRoleMapping, engines: [] };
- noItemsRoleMapping.accessAllEngines = false;
-
+ it('handles access items display more than 2 items', () => {
+ const extraEngine = {
+ ...engines[0],
+ id: '3',
+ };
+
+ const roleMapping = {
+ ...asRoleMapping,
+ engines: [...engines, extraEngine],
+ accessAllEngines: false,
+ };
const wrapper = mount(
-
+
);
-
- expect(wrapper.find('[data-test-subj="AccessItemsList"]').children().children().text()).toEqual(
- '—'
+ expect(wrapper.find('[data-test-subj="AccessItems"]').prop('children')).toEqual(
+ `${engines[0].name}, ${engines[1].name} + 1`
);
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx
index 7696cf03ed4b1..eb9621c7a242c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx
@@ -5,9 +5,9 @@
* 2.0.
*/
-import React, { Fragment } from 'react';
+import React from 'react';
-import { EuiIconTip, EuiTextColor, EuiInMemoryTable, EuiBasicTableColumn } from '@elastic/eui';
+import { EuiIconTip, EuiInMemoryTable, EuiBasicTableColumn } from '@elastic/eui';
import { ASRoleMapping } from '../../app_search/types';
import { WSRoleMapping } from '../../workplace_search/types';
@@ -46,8 +46,6 @@ interface Props {
handleDeleteMapping(roleMappingId: string): void;
}
-const noItemsPlaceholder = — ;
-
const getAuthProviderDisplayValue = (authProvider: string) =>
authProvider === ANY_AUTH_PROVIDER ? ANY_AUTH_PROVIDER_OPTION_LABEL : authProvider;
@@ -90,24 +88,18 @@ export const RoleMappingsTable: React.FC = ({
const accessItemsCol: EuiBasicTableColumn = {
field: 'accessItems',
name: accessHeader,
- render: (_, { accessAllEngines, accessItems }: SharedRoleMapping) => (
-
- {accessAllEngines ? (
- ALL_LABEL
- ) : (
- <>
- {accessItems.length === 0
- ? noItemsPlaceholder
- : accessItems.map(({ name }) => (
-
- {name}
-
-
- ))}
- >
- )}
-
- ),
+ render: (_, { accessAllEngines, accessItems }: SharedRoleMapping) => {
+ // Design calls for showing the first 2 items followed by a +x after those 2.
+ // ['foo', 'bar', 'baz'] would display as: "foo, bar + 1"
+ const numItems = accessItems.length;
+ if (accessAllEngines || numItems === 0)
+ return {ALL_LABEL} ;
+ const additionalItems = numItems > 2 ? ` + ${numItems - 2}` : '';
+ const names = accessItems.map((item) => item.name);
+ return (
+ {names.slice(0, 2).join(', ') + additionalItems}
+ );
+ },
};
const authProviderCol: EuiBasicTableColumn = {
@@ -143,6 +135,7 @@ export const RoleMappingsTable: React.FC = ({
const pagination = {
hidePerPageOptions: true,
+ pageSize: 10,
};
const search = {
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.test.tsx
new file mode 100644
index 0000000000000..8331a45849e3a
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.test.tsx
@@ -0,0 +1,39 @@
+/*
+ * 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 { shallow } from 'enzyme';
+
+import { EuiButton, EuiLink, EuiEmptyPrompt } from '@elastic/eui';
+
+import { RolesEmptyPrompt } from './roles_empty_prompt';
+
+describe('RolesEmptyPrompt', () => {
+ const onEnable = jest.fn();
+
+ const props = {
+ productName: 'App Search',
+ docsLink: 'http://elastic.co',
+ onEnable,
+ };
+
+ it('renders', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
+ expect(wrapper.find(EuiEmptyPrompt).dive().find(EuiLink).prop('href')).toEqual(props.docsLink);
+ });
+
+ it('calls onEnable on change', () => {
+ const wrapper = shallow( );
+ const prompt = wrapper.find(EuiEmptyPrompt).dive();
+ prompt.find(EuiButton).simulate('click');
+
+ expect(onEnable).toHaveBeenCalled();
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.tsx
new file mode 100644
index 0000000000000..11d50573c45f6
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.tsx
@@ -0,0 +1,48 @@
+/*
+ * 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 { EuiEmptyPrompt, EuiButton, EuiLink, EuiSpacer } from '@elastic/eui';
+
+import { ProductName } from '../types';
+
+import {
+ ROLES_DISABLED_TITLE,
+ ROLES_DISABLED_DESCRIPTION,
+ ROLES_DISABLED_NOTE,
+ ENABLE_ROLES_BUTTON,
+ ENABLE_ROLES_LINK,
+} from './constants';
+
+interface Props {
+ productName: ProductName;
+ docsLink: string;
+ onEnable(): void;
+}
+
+export const RolesEmptyPrompt: React.FC = ({ onEnable, docsLink, productName }) => (
+ {ROLES_DISABLED_TITLE}}
+ body={
+ <>
+ {ROLES_DISABLED_DESCRIPTION(productName)}
+ {ROLES_DISABLED_NOTE}
+ >
+ }
+ actions={[
+
+ {ENABLE_ROLES_BUTTON}
+ ,
+ ,
+
+ {ENABLE_ROLES_LINK}
+ ,
+ ]}
+ />
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.test.tsx
new file mode 100644
index 0000000000000..30bdaa0010b58
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.test.tsx
@@ -0,0 +1,28 @@
+/*
+ * 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 { shallow } from 'enzyme';
+
+import { EuiText } from '@elastic/eui';
+
+import { UserAddedInfo } from './';
+
+describe('UserAddedInfo', () => {
+ const props = {
+ username: 'user1',
+ email: 'test@test.com',
+ roleType: 'user',
+ };
+
+ it('renders', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(EuiText)).toHaveLength(6);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.tsx
new file mode 100644
index 0000000000000..a12eae66262a0
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.tsx
@@ -0,0 +1,40 @@
+/*
+ * 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 { EuiSpacer, EuiText } from '@elastic/eui';
+
+import { USERNAME_LABEL, EMAIL_LABEL } from '../constants';
+
+import { ROLE_LABEL } from './constants';
+
+interface Props {
+ username: string;
+ email: string;
+ roleType: string;
+}
+
+export const UserAddedInfo: React.FC = ({ username, email, roleType }) => (
+ <>
+
+ {USERNAME_LABEL}
+
+ {username}
+
+
+ {EMAIL_LABEL}
+
+ {email}
+
+
+ {ROLE_LABEL}
+
+ {roleType}
+
+ >
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.test.tsx
new file mode 100644
index 0000000000000..43333fe048f23
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.test.tsx
@@ -0,0 +1,70 @@
+/*
+ * 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 { shallow } from 'enzyme';
+
+import { EuiFlyout, EuiText, EuiIcon } from '@elastic/eui';
+
+import {
+ USERS_HEADING_LABEL,
+ UPDATE_USER_LABEL,
+ USER_UPDATED_LABEL,
+ NEW_USER_DESCRIPTION,
+ UPDATE_USER_DESCRIPTION,
+} from './constants';
+
+import { UserFlyout } from './';
+
+describe('UserFlyout', () => {
+ const closeUserFlyout = jest.fn();
+ const handleSaveUser = jest.fn();
+
+ const props = {
+ children:
,
+ isNew: true,
+ isComplete: false,
+ disabled: false,
+ closeUserFlyout,
+ handleSaveUser,
+ };
+
+ it('renders for new user', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(EuiFlyout)).toHaveLength(1);
+ expect(wrapper.find('h2').prop('children')).toEqual(USERS_HEADING_LABEL);
+ expect(wrapper.find(EuiText).prop('children')).toEqual({NEW_USER_DESCRIPTION}
);
+ });
+
+ it('renders for existing user', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find('h2').prop('children')).toEqual(UPDATE_USER_LABEL);
+ expect(wrapper.find(EuiText).prop('children')).toEqual({UPDATE_USER_DESCRIPTION}
);
+ });
+
+ it('renders icon and message for completed user', () => {
+ const wrapper = shallow( );
+ const icon = (
+
+ );
+ const children = (
+
+ {USER_UPDATED_LABEL} {icon}
+
+ );
+
+ expect(wrapper.find('h2').prop('children')).toEqual(children);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx
new file mode 100644
index 0000000000000..e13a56a716929
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx
@@ -0,0 +1,113 @@
+/*
+ * 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 {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFlyout,
+ EuiFlyoutBody,
+ EuiFlyoutFooter,
+ EuiFlyoutHeader,
+ EuiIcon,
+ EuiText,
+ EuiTitle,
+ EuiSpacer,
+} from '@elastic/eui';
+
+interface Props {
+ children: React.ReactNode;
+ isNew: boolean;
+ isComplete: boolean;
+ disabled: boolean;
+ closeUserFlyout(): void;
+ handleSaveUser(): void;
+}
+
+import { CANCEL_BUTTON_LABEL, CLOSE_BUTTON_LABEL } from '../constants';
+
+import {
+ USERS_HEADING_LABEL,
+ UPDATE_USER_LABEL,
+ ADD_USER_LABEL,
+ USER_ADDED_LABEL,
+ USER_UPDATED_LABEL,
+ NEW_USER_DESCRIPTION,
+ UPDATE_USER_DESCRIPTION,
+} from './constants';
+
+export const UserFlyout: React.FC = ({
+ children,
+ isNew,
+ isComplete,
+ disabled,
+ closeUserFlyout,
+ handleSaveUser,
+}) => {
+ const savedIcon = (
+
+ );
+ const IS_EDITING_HEADING = isNew ? USERS_HEADING_LABEL : UPDATE_USER_LABEL;
+ const IS_EDITING_DESCRIPTION = isNew ? NEW_USER_DESCRIPTION : UPDATE_USER_DESCRIPTION;
+ const USER_SAVED_HEADING = isNew ? USER_ADDED_LABEL : USER_UPDATED_LABEL;
+ const IS_COMPLETE_HEADING = (
+
+ {USER_SAVED_HEADING} {savedIcon}
+
+ );
+
+ const editingFooterActions = (
+
+
+ {CANCEL_BUTTON_LABEL}
+
+
+
+ {isNew ? ADD_USER_LABEL : UPDATE_USER_LABEL}
+
+
+
+ );
+
+ const completedFooterAction = (
+
+
+
+ {CLOSE_BUTTON_LABEL}
+
+
+
+ );
+
+ return (
+
+
+
+ {isComplete ? IS_COMPLETE_HEADING : IS_EDITING_HEADING}
+
+ {!isComplete && (
+
+ {IS_EDITING_DESCRIPTION}
+
+ )}
+
+
+ {children}
+
+
+ {isComplete ? completedFooterAction : editingFooterActions}
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_invitation_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_invitation_callout.test.tsx
new file mode 100644
index 0000000000000..d5272a26715b6
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_invitation_callout.test.tsx
@@ -0,0 +1,46 @@
+/*
+ * 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 { shallow } from 'enzyme';
+
+import { EuiText, EuiButtonIcon, EuiCopy } from '@elastic/eui';
+
+import { EXISTING_INVITATION_LABEL } from './constants';
+
+import { UserInvitationCallout } from './';
+
+describe('UserInvitationCallout', () => {
+ const props = {
+ isNew: true,
+ invitationCode: 'test@test.com',
+ urlPrefix: 'http://foo',
+ };
+
+ it('renders', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(EuiText)).toHaveLength(2);
+ });
+
+ it('renders the copy button', () => {
+ const copyMock = jest.fn();
+ const wrapper = shallow( );
+
+ const copyEl = shallow({wrapper.find(EuiCopy).props().children(copyMock)}
);
+ expect(copyEl.find(EuiButtonIcon).props().onClick).toEqual(copyMock);
+ });
+
+ it('renders existing invitation label', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(EuiText).first().prop('children')).toEqual(
+ {EXISTING_INVITATION_LABEL}
+ );
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_invitation_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_invitation_callout.tsx
new file mode 100644
index 0000000000000..8310077ad6f2e
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_invitation_callout.tsx
@@ -0,0 +1,47 @@
+/*
+ * 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 { EuiCopy, EuiButtonIcon, EuiSpacer, EuiText, EuiLink } from '@elastic/eui';
+
+import {
+ INVITATION_DESCRIPTION,
+ NEW_INVITATION_LABEL,
+ EXISTING_INVITATION_LABEL,
+ INVITATION_LINK,
+} from './constants';
+
+interface Props {
+ isNew: boolean;
+ invitationCode: string;
+ urlPrefix: string;
+}
+
+export const UserInvitationCallout: React.FC = ({ isNew, invitationCode, urlPrefix }) => {
+ const link = urlPrefix + invitationCode;
+ const label = isNew ? NEW_INVITATION_LABEL : EXISTING_INVITATION_LABEL;
+
+ return (
+ <>
+ {!isNew && }
+
+ {label}
+
+
+ {INVITATION_DESCRIPTION}
+
+
+ {INVITATION_LINK}
+ {' '}
+
+ {(copy) => }
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx
new file mode 100644
index 0000000000000..08ddc7ba5427f
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx
@@ -0,0 +1,112 @@
+/*
+ * 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 { elasticsearchUsers } from './__mocks__/elasticsearch_users';
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { EuiFormRow } from '@elastic/eui';
+
+import { Role as ASRole } from '../../app_search/types';
+
+import { REQUIRED_LABEL, USERNAME_NO_USERS_TEXT } from './constants';
+
+import { UserSelector } from './';
+
+const simulatedEvent = {
+ target: { value: 'foo' },
+};
+
+describe('UserSelector', () => {
+ const setUserExisting = jest.fn();
+ const setElasticsearchUsernameValue = jest.fn();
+ const setElasticsearchEmailValue = jest.fn();
+ const handleRoleChange = jest.fn();
+ const handleUsernameSelectChange = jest.fn();
+
+ const roleType = ('user' as unknown) as ASRole;
+
+ const props = {
+ isNewUser: true,
+ userFormUserIsExisting: true,
+ elasticsearchUsers,
+ elasticsearchUser: elasticsearchUsers[0],
+ roleTypes: [roleType],
+ roleType,
+ setUserExisting,
+ setElasticsearchUsernameValue,
+ setElasticsearchEmailValue,
+ handleRoleChange,
+ handleUsernameSelectChange,
+ };
+
+ it('renders Role select and calls method', () => {
+ const wrapper = shallow( );
+ wrapper.find('[data-test-subj="RoleSelect"]').simulate('change', simulatedEvent);
+
+ expect(handleRoleChange).toHaveBeenCalled();
+ });
+
+ it('renders when updating user', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find('[data-test-subj="UsernameInput"]')).toHaveLength(1);
+ expect(wrapper.find('[data-test-subj="EmailInput"]')).toHaveLength(1);
+ });
+
+ it('renders Username select and calls method', () => {
+ const wrapper = shallow( );
+ wrapper.find('[data-test-subj="UsernameSelect"]').simulate('change', simulatedEvent);
+
+ expect(handleUsernameSelectChange).toHaveBeenCalled();
+ });
+
+ it('renders Existing user radio and calls method', () => {
+ const wrapper = shallow( );
+ wrapper.find('[data-test-subj="ExistingUserRadio"]').simulate('change');
+
+ expect(setUserExisting).toHaveBeenCalledWith(true);
+ });
+
+ it('renders Email input and calls method', () => {
+ const wrapper = shallow( );
+ wrapper.find('[data-test-subj="EmailInput"]').simulate('change', simulatedEvent);
+
+ expect(setElasticsearchEmailValue).toHaveBeenCalled();
+ });
+
+ it('renders Username input and calls method', () => {
+ const wrapper = shallow( );
+ wrapper.find('[data-test-subj="UsernameInput"]').simulate('change', simulatedEvent);
+
+ expect(setElasticsearchUsernameValue).toHaveBeenCalled();
+ });
+
+ it('renders New user radio and calls method', () => {
+ const wrapper = shallow( );
+ wrapper.find('[data-test-subj="NewUserRadio"]').simulate('change');
+
+ expect(setUserExisting).toHaveBeenCalledWith(false);
+ });
+
+ it('renders helpText when values are empty', () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.find(EuiFormRow).at(0).prop('helpText')).toEqual(USERNAME_NO_USERS_TEXT);
+ expect(wrapper.find(EuiFormRow).at(1).prop('helpText')).toEqual(REQUIRED_LABEL);
+ expect(wrapper.find(EuiFormRow).at(2).prop('helpText')).toEqual(REQUIRED_LABEL);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx
new file mode 100644
index 0000000000000..70348bf29894a
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx
@@ -0,0 +1,159 @@
+/*
+ * 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 {
+ EuiFieldText,
+ EuiRadio,
+ EuiFormRow,
+ EuiSelect,
+ EuiSelectOption,
+ EuiSpacer,
+} from '@elastic/eui';
+
+import { Role as ASRole } from '../../app_search/types';
+import { ElasticsearchUser } from '../../shared/types';
+import { Role as WSRole } from '../../workplace_search/types';
+
+import { USERNAME_LABEL, EMAIL_LABEL } from '../constants';
+
+import {
+ NEW_USER_LABEL,
+ EXISTING_USER_LABEL,
+ USERNAME_NO_USERS_TEXT,
+ REQUIRED_LABEL,
+ ROLE_LABEL,
+} from './constants';
+
+type SharedRole = WSRole | ASRole;
+
+interface Props {
+ isNewUser: boolean;
+ userFormUserIsExisting: boolean;
+ elasticsearchUsers: ElasticsearchUser[];
+ elasticsearchUser: ElasticsearchUser;
+ roleTypes: SharedRole[];
+ roleType: SharedRole;
+ setUserExisting(userFormUserIsExisting: boolean): void;
+ setElasticsearchUsernameValue(username: string): void;
+ setElasticsearchEmailValue(email: string): void;
+ handleRoleChange(roleType: SharedRole): void;
+ handleUsernameSelectChange(username: string): void;
+}
+
+export const UserSelector: React.FC = ({
+ isNewUser,
+ userFormUserIsExisting,
+ elasticsearchUsers,
+ elasticsearchUser,
+ roleTypes,
+ roleType,
+ setUserExisting,
+ setElasticsearchUsernameValue,
+ setElasticsearchEmailValue,
+ handleRoleChange,
+ handleUsernameSelectChange,
+}) => {
+ const roleOptions = roleTypes.map((role) => ({ id: role, text: role }));
+ const usernameOptions = elasticsearchUsers.map(({ username }) => ({
+ id: username,
+ text: username,
+ }));
+ const hasElasticsearchUsers = elasticsearchUsers.length > 0;
+ const showNewUserExistingUserControls = userFormUserIsExisting && hasElasticsearchUsers;
+
+ const roleSelect = (
+
+ handleRoleChange(e.target.value as SharedRole)}
+ />
+
+ );
+
+ const emailInput = (
+
+ setElasticsearchEmailValue(e.target.value)}
+ />
+
+ );
+
+ const usernameAndEmailControls = (
+ <>
+
+ setElasticsearchUsernameValue(e.target.value)}
+ />
+
+ {elasticsearchUser.email !== null && emailInput}
+ {roleSelect}
+ >
+ );
+
+ const existingUserControls = (
+ <>
+
+
+ handleUsernameSelectChange(e.target.value)}
+ />
+
+ {roleSelect}
+ >
+ );
+
+ const newUserControls = (
+ <>
+
+ {usernameAndEmailControls}
+ >
+ );
+
+ const createUserControls = (
+ <>
+
+ setUserExisting(true)}
+ disabled={!hasElasticsearchUsers}
+ />
+
+
+ {showNewUserExistingUserControls && existingUserControls}
+
+ setUserExisting(false)}
+ />
+ {!showNewUserExistingUserControls && newUserControls}
+ >
+ );
+
+ return isNewUser ? createUserControls : usernameAndEmailControls;
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_and_roles_row_actions.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_and_roles_row_actions.test.tsx
index dbb47b50d4066..5f1fefc688c77 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_and_roles_row_actions.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_and_roles_row_actions.test.tsx
@@ -9,15 +9,23 @@ import React from 'react';
import { shallow } from 'enzyme';
-import { EuiButtonIcon } from '@elastic/eui';
+import { EuiButtonIcon, EuiConfirmModal } from '@elastic/eui';
+
+import {
+ REMOVE_ROLE_MAPPING_TITLE,
+ REMOVE_ROLE_MAPPING_BUTTON,
+ ROLE_MODAL_TEXT,
+} from './constants';
import { UsersAndRolesRowActions } from './users_and_roles_row_actions';
describe('UsersAndRolesRowActions', () => {
const onManageClick = jest.fn();
const onDeleteClick = jest.fn();
+ const username = 'foo';
const props = {
+ username,
onManageClick,
onDeleteClick,
};
@@ -40,7 +48,19 @@ describe('UsersAndRolesRowActions', () => {
const wrapper = shallow( );
const button = wrapper.find(EuiButtonIcon).last();
button.simulate('click');
+ wrapper.find(EuiConfirmModal).prop('onConfirm')!({} as any);
expect(onDeleteClick).toHaveBeenCalled();
});
+
+ it('renders role mapping confirm modal text', () => {
+ const wrapper = shallow( );
+ const button = wrapper.find(EuiButtonIcon).last();
+ button.simulate('click');
+ const modal = wrapper.find(EuiConfirmModal);
+
+ expect(modal.prop('title')).toEqual(REMOVE_ROLE_MAPPING_TITLE);
+ expect(modal.prop('children')).toEqual({ROLE_MODAL_TEXT}
);
+ expect(modal.prop('confirmButtonText')).toEqual(REMOVE_ROLE_MAPPING_BUTTON);
+ });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_and_roles_row_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_and_roles_row_actions.tsx
index 3d956c0aabd68..a3b0d24769bf6 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_and_roles_row_actions.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_and_roles_row_actions.tsx
@@ -5,20 +5,65 @@
* 2.0.
*/
-import React from 'react';
+import React, { useState } from 'react';
-import { EuiButtonIcon } from '@elastic/eui';
+import { EuiButtonIcon, EuiConfirmModal } from '@elastic/eui';
-import { MANAGE_BUTTON_LABEL, DELETE_BUTTON_LABEL } from '../constants';
+import { CANCEL_BUTTON_LABEL, MANAGE_BUTTON_LABEL, DELETE_BUTTON_LABEL } from '../constants';
+
+import {
+ REMOVE_ROLE_MAPPING_TITLE,
+ REMOVE_ROLE_MAPPING_BUTTON,
+ REMOVE_USER_BUTTON,
+ ROLE_MODAL_TEXT,
+ USER_MODAL_TITLE,
+ USER_MODAL_TEXT,
+} from './constants';
interface Props {
+ username?: string;
onManageClick(): void;
onDeleteClick(): void;
}
-export const UsersAndRolesRowActions: React.FC = ({ onManageClick, onDeleteClick }) => (
- <>
- {' '}
-
- >
-);
+export const UsersAndRolesRowActions: React.FC = ({
+ onManageClick,
+ onDeleteClick,
+ username,
+}) => {
+ const [deleteModalVisible, setVisible] = useState(false);
+ const showDeleteModal = () => setVisible(true);
+ const closeDeleteModal = () => setVisible(false);
+ const title = username ? USER_MODAL_TITLE(username) : REMOVE_ROLE_MAPPING_TITLE;
+ const text = username ? USER_MODAL_TEXT : ROLE_MODAL_TEXT;
+ const confirmButton = username ? REMOVE_USER_BUTTON : REMOVE_ROLE_MAPPING_BUTTON;
+
+ const deleteModal = (
+ {
+ onDeleteClick();
+ closeDeleteModal();
+ }}
+ cancelButtonText={CANCEL_BUTTON_LABEL}
+ confirmButtonText={confirmButton}
+ buttonColor="danger"
+ defaultFocusedButton="confirm"
+ >
+ {text}
+
+ );
+
+ return (
+ <>
+ {deleteModalVisible && deleteModal}
+ {' '}
+
+ >
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_empty_prompt.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_empty_prompt.test.tsx
new file mode 100644
index 0000000000000..9110c09827c49
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_empty_prompt.test.tsx
@@ -0,0 +1,22 @@
+/*
+ * 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 { shallow } from 'enzyme';
+
+import { EuiEmptyPrompt } from '@elastic/eui';
+
+import { UsersEmptyPrompt } from './';
+
+describe('UsersEmptyPrompt', () => {
+ it('renders', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_empty_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_empty_prompt.tsx
new file mode 100644
index 0000000000000..42bf690c388c4
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_empty_prompt.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,
+ EuiPanel,
+ EuiEmptyPrompt,
+ EuiLink,
+ EuiSpacer,
+} from '@elastic/eui';
+
+import { docLinks } from '../doc_links';
+
+import { NO_USERS_TITLE, NO_USERS_DESCRIPTION, ENABLE_USERS_LINK } from './constants';
+
+const USERS_DOCS_URL = `${docLinks.enterpriseSearchBase}/users-access.html`;
+
+export const UsersEmptyPrompt: React.FC = () => (
+
+
+
+
+ {NO_USERS_TITLE}}
+ body={{NO_USERS_DESCRIPTION}
}
+ actions={
+
+ {ENABLE_USERS_LINK}
+
+ }
+ />
+
+
+
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_heading.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_heading.test.tsx
new file mode 100644
index 0000000000000..9bae93079e89f
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_heading.test.tsx
@@ -0,0 +1,32 @@
+/*
+ * 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 { shallow } from 'enzyme';
+
+import { EuiButton, EuiText, EuiTitle } from '@elastic/eui';
+
+import { UsersHeading } from './';
+
+describe('UsersHeading', () => {
+ const onClick = jest.fn();
+
+ it('renders', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(EuiText)).toHaveLength(1);
+ expect(wrapper.find(EuiTitle)).toHaveLength(1);
+ });
+
+ it('handles button click', () => {
+ const wrapper = shallow( );
+ wrapper.find(EuiButton).simulate('click');
+
+ expect(onClick).toHaveBeenCalled();
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_heading.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_heading.tsx
new file mode 100644
index 0000000000000..8d097e21e9c3f
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_heading.tsx
@@ -0,0 +1,37 @@
+/*
+ * 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 { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
+
+import { USERS_HEADING_TITLE, USERS_HEADING_DESCRIPTION, USERS_HEADING_LABEL } from './constants';
+
+interface Props {
+ onClick(): void;
+}
+
+export const UsersHeading: React.FC = ({ onClick }) => (
+ <>
+
+
+
+ {USERS_HEADING_TITLE}
+
+
+ {USERS_HEADING_DESCRIPTION}
+
+
+
+
+ {USERS_HEADING_LABEL}
+
+
+
+
+ >
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.test.tsx
new file mode 100644
index 0000000000000..dc1a2713ced12
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.test.tsx
@@ -0,0 +1,100 @@
+/*
+ * 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 { asSingleUserRoleMapping, wsSingleUserRoleMapping, asRoleMapping } from './__mocks__/roles';
+
+import React from 'react';
+
+import { shallow, mount } from 'enzyme';
+
+import { EuiInMemoryTable, EuiTextColor } from '@elastic/eui';
+
+import { engines } from '../../app_search/__mocks__/engines.mock';
+
+import { UsersAndRolesRowActions } from './users_and_roles_row_actions';
+
+import { UsersTable } from './';
+
+describe('UsersTable', () => {
+ const initializeSingleUserRoleMapping = jest.fn();
+ const handleDeleteMapping = jest.fn();
+ const props = {
+ accessItemKey: 'groups' as 'groups' | 'engines',
+ singleUserRoleMappings: [wsSingleUserRoleMapping],
+ initializeSingleUserRoleMapping,
+ handleDeleteMapping,
+ };
+
+ it('renders', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(EuiInMemoryTable)).toHaveLength(1);
+ });
+
+ it('handles manage click', () => {
+ const wrapper = mount( );
+ wrapper.find(UsersAndRolesRowActions).prop('onManageClick')();
+
+ expect(initializeSingleUserRoleMapping).toHaveBeenCalled();
+ });
+
+ it('handles delete click', () => {
+ const wrapper = mount( );
+ wrapper.find(UsersAndRolesRowActions).prop('onDeleteClick')();
+
+ expect(handleDeleteMapping).toHaveBeenCalled();
+ });
+
+ it('handles display when no email present', () => {
+ const userWithNoEmail = {
+ ...wsSingleUserRoleMapping,
+ elasticsearchUser: {
+ email: null,
+ username: 'foo',
+ },
+ };
+ const wrapper = mount( );
+
+ expect(wrapper.find(EuiTextColor)).toHaveLength(1);
+ });
+
+ it('handles access items display for all items', () => {
+ const userWithAllItems = {
+ ...asSingleUserRoleMapping,
+ roleMapping: {
+ ...asRoleMapping,
+ engines: [],
+ },
+ };
+ const wrapper = mount(
+
+ );
+
+ expect(wrapper.find('[data-test-subj="AllItems"]')).toHaveLength(1);
+ });
+
+ it('handles access items display more than 2 items', () => {
+ const extraEngine = {
+ ...engines[0],
+ id: '3',
+ };
+ const userWithAllItems = {
+ ...asSingleUserRoleMapping,
+ roleMapping: {
+ ...asRoleMapping,
+ engines: [...engines, extraEngine],
+ },
+ };
+ const wrapper = mount(
+
+ );
+
+ expect(wrapper.find('[data-test-subj="AccessItems"]').prop('children')).toEqual(
+ `${engines[0].name}, ${engines[1].name} + 1`
+ );
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx
new file mode 100644
index 0000000000000..86dc2c2626229
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx
@@ -0,0 +1,147 @@
+/*
+ * 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 { EuiBadge, EuiBasicTableColumn, EuiInMemoryTable, EuiTextColor } from '@elastic/eui';
+
+import { ASRoleMapping } from '../../app_search/types';
+import { SingleUserRoleMapping } from '../../shared/types';
+import { WSRoleMapping } from '../../workplace_search/types';
+
+import {
+ INVITATION_PENDING_LABEL,
+ ALL_LABEL,
+ FILTER_USERS_LABEL,
+ NO_USERS_LABEL,
+ ROLE_LABEL,
+ USERNAME_LABEL,
+ EMAIL_LABEL,
+ GROUPS_LABEL,
+ ENGINES_LABEL,
+} from './constants';
+
+import { UsersAndRolesRowActions } from './';
+
+interface AccessItem {
+ name: string;
+}
+
+interface SharedUser extends SingleUserRoleMapping {
+ accessItems: AccessItem[];
+ username: string;
+ email: string | null;
+ roleType: string;
+ id: string;
+}
+
+interface SharedRoleMapping extends ASRoleMapping, WSRoleMapping {
+ accessItems: AccessItem[];
+}
+
+interface Props {
+ accessItemKey: 'groups' | 'engines';
+ singleUserRoleMappings: Array>;
+ initializeSingleUserRoleMapping(roleId: string): string;
+ handleDeleteMapping(roleId: string): string;
+}
+
+const noItemsPlaceholder = — ;
+const invitationBadge = {INVITATION_PENDING_LABEL} ;
+
+export const UsersTable: React.FC = ({
+ accessItemKey,
+ singleUserRoleMappings,
+ initializeSingleUserRoleMapping,
+ handleDeleteMapping,
+}) => {
+ // 'accessItems' is needed because App Search has `engines` and Workplace Search has `groups`.
+ const users = ((singleUserRoleMappings as SharedUser[]).map((user) => ({
+ username: user.elasticsearchUser.username,
+ email: user.elasticsearchUser.email,
+ roleType: user.roleMapping.roleType,
+ id: user.roleMapping.id,
+ accessItems: (user.roleMapping as SharedRoleMapping)[accessItemKey],
+ invitation: user.invitation,
+ })) as unknown) as Array>;
+
+ const columns: Array> = [
+ {
+ field: 'username',
+ name: USERNAME_LABEL,
+ render: (_, { username }: SharedUser) => username,
+ },
+ {
+ field: 'email',
+ name: EMAIL_LABEL,
+ render: (_, { email, invitation }: SharedUser) => {
+ if (!email) return noItemsPlaceholder;
+ return (
+
+ {email} {invitation && invitationBadge}
+
+ );
+ },
+ },
+ {
+ field: 'roleType',
+ name: ROLE_LABEL,
+ render: (_, user: SharedUser) => user.roleType,
+ },
+ {
+ field: 'accessItems',
+ name: accessItemKey === 'groups' ? GROUPS_LABEL : ENGINES_LABEL,
+ render: (_, { accessItems }: SharedUser) => {
+ // Design calls for showing the first 2 items followed by a +x after those 2.
+ // ['foo', 'bar', 'baz'] would display as: "foo, bar + 1"
+ const numItems = accessItems.length;
+ if (numItems === 0) return {ALL_LABEL} ;
+ const additionalItems = numItems > 2 ? ` + ${numItems - 2}` : '';
+ const names = accessItems.map((item) => item.name);
+ return (
+ {names.slice(0, 2).join(', ') + additionalItems}
+ );
+ },
+ },
+ {
+ field: 'id',
+ name: '',
+ render: (_, { id, username }: SharedUser) => (
+ initializeSingleUserRoleMapping(id)}
+ onDeleteClick={() => handleDeleteMapping(id)}
+ />
+ ),
+ },
+ ];
+
+ const pagination = {
+ hidePerPageOptions: true,
+ pageSize: 10,
+ };
+
+ const search = {
+ box: {
+ incremental: true,
+ fullWidth: false,
+ placeholder: FILTER_USERS_LABEL,
+ 'data-test-subj': 'UsersTableSearchInput',
+ },
+ };
+
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts
index 67208c63ddf4c..e6d2c67d1baf8 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts
@@ -40,3 +40,19 @@ export interface RoleMapping {
const productNames = [APP_SEARCH_PLUGIN.NAME, WORKPLACE_SEARCH_PLUGIN.NAME] as const;
export type ProductName = typeof productNames[number];
+
+export interface Invitation {
+ email: string;
+ code: string;
+}
+
+export interface ElasticsearchUser {
+ email: string | null;
+ username: string;
+}
+
+export interface SingleUserRoleMapping {
+ invitation: Invitation;
+ elasticsearchUser: ElasticsearchUser;
+ roleMapping: T;
+}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_users_table.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_users_table.tsx
index a4eb228eff92f..050aaf1dadf89 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_users_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_users_table.tsx
@@ -11,8 +11,8 @@ import { useValues } from 'kea';
import { EuiTable, EuiTableBody, EuiTablePagination } from '@elastic/eui';
import { Pager } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
+import { USERNAME_LABEL, EMAIL_LABEL } from '../../../../shared/constants';
import { TableHeader } from '../../../../shared/table_header';
import { AppLogic } from '../../../app_logic';
import { UserRow } from '../../../components/shared/user_row';
@@ -20,27 +20,15 @@ import { User } from '../../../types';
import { GroupLogic } from '../group_logic';
const USERS_PER_PAGE = 10;
-const USERNAME_TABLE_HEADER = i18n.translate(
- 'xpack.enterpriseSearch.workplaceSearch.groups.groupsUsersTable.usernameTableHeader',
- {
- defaultMessage: 'Username',
- }
-);
-const EMAIL_TABLE_HEADER = i18n.translate(
- 'xpack.enterpriseSearch.workplaceSearch.groups.groupsUsersTable.emailTableHeader',
- {
- defaultMessage: 'Email',
- }
-);
export const GroupUsersTable: React.FC = () => {
const { isFederatedAuth } = useValues(AppLogic);
const {
group: { users },
} = useValues(GroupLogic);
- const headerItems = [USERNAME_TABLE_HEADER];
+ const headerItems = [USERNAME_LABEL];
if (!isFederatedAuth) {
- headerItems.push(EMAIL_TABLE_HEADER);
+ headerItems.push(EMAIL_LABEL);
}
const [firstItem, setFirstItem] = useState(0);
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/constants.ts
index 92c8b7827b9b6..809b631c78391 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/constants.ts
@@ -7,14 +7,6 @@
import { i18n } from '@kbn/i18n';
-export const DELETE_ROLE_MAPPING_MESSAGE = i18n.translate(
- 'xpack.enterpriseSearch.workplaceSearch.roleMapping.deleteRoleMappingButtonMessage',
- {
- defaultMessage:
- 'Are you sure you want to permanently delete this mapping? This action is not reversible and some users might lose access.',
- }
-);
-
export const ROLE_MAPPING_DELETED_MESSAGE = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.roleMappingDeletedMessage',
{
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx
index b153d01224193..01d32bec14ebd 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx
@@ -10,9 +10,14 @@ import React, { useEffect } from 'react';
import { useActions, useValues } from 'kea';
import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants';
-import { RoleMappingsTable, RoleMappingsHeading } from '../../../shared/role_mapping';
+import {
+ RoleMappingsTable,
+ RoleMappingsHeading,
+ RolesEmptyPrompt,
+} from '../../../shared/role_mapping';
import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants';
import { WorkplaceSearchPageTemplate } from '../../components/layout';
+import { SECURITY_DOCS_URL } from '../../routes';
import { ROLE_MAPPINGS_TABLE_HEADER } from './constants';
@@ -20,9 +25,12 @@ import { RoleMapping } from './role_mapping';
import { RoleMappingsLogic } from './role_mappings_logic';
export const RoleMappings: React.FC = () => {
- const { initializeRoleMappings, initializeRoleMapping, handleDeleteMapping } = useActions(
- RoleMappingsLogic
- );
+ const {
+ enableRoleBasedAccess,
+ initializeRoleMappings,
+ initializeRoleMapping,
+ handleDeleteMapping,
+ } = useActions(RoleMappingsLogic);
const {
roleMappings,
@@ -35,10 +43,19 @@ export const RoleMappings: React.FC = () => {
initializeRoleMappings();
}, []);
+ const rolesEmptyState = (
+
+ );
+
const roleMappingsSection = (
initializeRoleMapping()}
/>
{
pageChrome={[ROLE_MAPPINGS_TITLE]}
pageHeader={{ pageTitle: ROLE_MAPPINGS_TITLE }}
isLoading={dataLoading}
+ isEmptyState={roleMappings.length < 1}
+ emptyState={rolesEmptyState}
>
{roleMappingFlyoutOpen && }
{roleMappingsSection}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts
index 4ee530870284e..a4bbddbd23b49 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts
@@ -90,6 +90,13 @@ describe('RoleMappingsLogic', () => {
expect(RoleMappingsLogic.values.selectedGroups).toEqual(new Set([defaultGroup.id]));
});
+ it('setRoleMappings', () => {
+ RoleMappingsLogic.actions.setRoleMappings({ roleMappings: [wsRoleMapping] });
+
+ expect(RoleMappingsLogic.values.roleMappings).toEqual([wsRoleMapping]);
+ expect(RoleMappingsLogic.values.dataLoading).toEqual(false);
+ });
+
it('handleRoleChange', () => {
RoleMappingsLogic.actions.handleRoleChange('user');
@@ -234,6 +241,30 @@ describe('RoleMappingsLogic', () => {
});
describe('listeners', () => {
+ describe('enableRoleBasedAccess', () => {
+ it('calls API and sets values', async () => {
+ const setRoleMappingsSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMappings');
+ http.post.mockReturnValue(Promise.resolve(mappingsServerProps));
+ RoleMappingsLogic.actions.enableRoleBasedAccess();
+
+ expect(RoleMappingsLogic.values.dataLoading).toEqual(true);
+
+ expect(http.post).toHaveBeenCalledWith(
+ '/api/workplace_search/org/role_mappings/enable_role_based_access'
+ );
+ await nextTick();
+ expect(setRoleMappingsSpy).toHaveBeenCalledWith(mappingsServerProps);
+ });
+
+ it('handles error', async () => {
+ http.post.mockReturnValue(Promise.reject('this is an error'));
+ RoleMappingsLogic.actions.enableRoleBasedAccess();
+ await nextTick();
+
+ expect(flashAPIErrors).toHaveBeenCalledWith('this is an error');
+ });
+ });
+
describe('initializeRoleMappings', () => {
it('calls API and sets values', async () => {
const setRoleMappingsDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMappingsData');
@@ -351,18 +382,8 @@ describe('RoleMappingsLogic', () => {
});
describe('handleDeleteMapping', () => {
- let confirmSpy: any;
const roleMappingId = 'r1';
- beforeEach(() => {
- confirmSpy = jest.spyOn(window, 'confirm');
- confirmSpy.mockImplementation(jest.fn(() => true));
- });
-
- afterEach(() => {
- confirmSpy.mockRestore();
- });
-
it('calls API and refreshes list', async () => {
const initializeRoleMappingsSpy = jest.spyOn(
RoleMappingsLogic.actions,
@@ -388,15 +409,6 @@ describe('RoleMappingsLogic', () => {
expect(flashAPIErrors).toHaveBeenCalledWith('this is an error');
});
-
- it('will do nothing if not confirmed', async () => {
- RoleMappingsLogic.actions.setRoleMapping(wsRoleMapping);
- window.confirm = () => false;
- RoleMappingsLogic.actions.handleDeleteMapping(roleMappingId);
-
- expect(http.delete).not.toHaveBeenCalled();
- await nextTick();
- });
});
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts
index 361425b7a78a1..76b41b2f383eb 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts
@@ -20,7 +20,6 @@ import { AttributeName } from '../../../shared/types';
import { RoleGroup, WSRoleMapping, Role } from '../../types';
import {
- DELETE_ROLE_MAPPING_MESSAGE,
ROLE_MAPPING_DELETED_MESSAGE,
ROLE_MAPPING_CREATED_MESSAGE,
ROLE_MAPPING_UPDATED_MESSAGE,
@@ -57,10 +56,16 @@ interface RoleMappingsActions {
initializeRoleMappings(): void;
resetState(): void;
setRoleMapping(roleMapping: WSRoleMapping): { roleMapping: WSRoleMapping };
+ setRoleMappings({
+ roleMappings,
+ }: {
+ roleMappings: WSRoleMapping[];
+ }): { roleMappings: WSRoleMapping[] };
setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails;
openRoleMappingFlyout(): void;
closeRoleMappingFlyout(): void;
setRoleMappingErrors(errors: string[]): { errors: string[] };
+ enableRoleBasedAccess(): void;
}
interface RoleMappingsValues {
@@ -88,6 +93,7 @@ export const RoleMappingsLogic = kea data,
setRoleMapping: (roleMapping: WSRoleMapping) => ({ roleMapping }),
+ setRoleMappings: ({ roleMappings }: { roleMappings: WSRoleMapping[] }) => ({ roleMappings }),
setRoleMappingErrors: (errors: string[]) => ({ errors }),
handleAuthProviderChange: (value: string[]) => ({ value }),
handleRoleChange: (roleType: Role) => ({ roleType }),
@@ -98,6 +104,7 @@ export const RoleMappingsLogic = kea ({ value }),
handleAllGroupsSelectionChange: (selected: boolean) => ({ selected }),
+ enableRoleBasedAccess: true,
resetState: true,
initializeRoleMappings: true,
initializeRoleMapping: (roleMappingId?: string) => ({ roleMappingId }),
@@ -111,13 +118,16 @@ export const RoleMappingsLogic = kea false,
+ setRoleMappings: () => false,
resetState: () => true,
+ enableRoleBasedAccess: () => true,
},
],
roleMappings: [
[],
{
setRoleMappingsData: (_, { roleMappings }) => roleMappings,
+ setRoleMappings: (_, { roleMappings }) => roleMappings,
resetState: () => [],
},
],
@@ -260,6 +270,17 @@ export const RoleMappingsLogic = kea ({
+ enableRoleBasedAccess: async () => {
+ const { http } = HttpLogic.values;
+ const route = '/api/workplace_search/org/role_mappings/enable_role_based_access';
+
+ try {
+ const response = await http.post(route);
+ actions.setRoleMappings(response);
+ } catch (e) {
+ flashAPIErrors(e);
+ }
+ },
initializeRoleMappings: async () => {
const { http } = HttpLogic.values;
const route = '/api/workplace_search/org/role_mappings';
@@ -279,14 +300,12 @@ export const RoleMappingsLogic = kea {
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts
index 718597c12e9c5..7d9f08627516b 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts
@@ -7,7 +7,11 @@
import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__';
-import { registerRoleMappingsRoute, registerRoleMappingRoute } from './role_mappings';
+import {
+ registerEnableRoleMappingsRoute,
+ registerRoleMappingsRoute,
+ registerRoleMappingRoute,
+} from './role_mappings';
const roleMappingBaseSchema = {
rules: { username: 'user' },
@@ -18,6 +22,29 @@ const roleMappingBaseSchema = {
};
describe('role mappings routes', () => {
+ describe('POST /api/app_search/role_mappings/enable_role_based_access', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'post',
+ path: '/api/app_search/role_mappings/enable_role_based_access',
+ });
+
+ registerEnableRoleMappingsRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request handler', () => {
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/as/role_mappings/enable_role_based_access',
+ });
+ });
+ });
+
describe('GET /api/app_search/role_mappings', () => {
let mockRouter: MockRouter;
@@ -36,7 +63,7 @@ describe('role mappings routes', () => {
it('creates a request handler', () => {
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
- path: '/role_mappings',
+ path: '/as/role_mappings',
});
});
});
@@ -59,7 +86,7 @@ describe('role mappings routes', () => {
it('creates a request handler', () => {
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
- path: '/role_mappings',
+ path: '/as/role_mappings',
});
});
@@ -94,7 +121,7 @@ describe('role mappings routes', () => {
it('creates a request handler', () => {
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
- path: '/role_mappings/:id',
+ path: '/as/role_mappings/:id',
});
});
@@ -129,7 +156,7 @@ describe('role mappings routes', () => {
it('creates a request handler', () => {
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
- path: '/role_mappings/:id',
+ path: '/as/role_mappings/:id',
});
});
});
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts
index 75724a3344d6d..da620be2ea950 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts
@@ -17,6 +17,21 @@ const roleMappingBaseSchema = {
authProvider: schema.arrayOf(schema.string()),
};
+export function registerEnableRoleMappingsRoute({
+ router,
+ enterpriseSearchRequestHandler,
+}: RouteDependencies) {
+ router.post(
+ {
+ path: '/api/app_search/role_mappings/enable_role_based_access',
+ validate: false,
+ },
+ enterpriseSearchRequestHandler.createRequest({
+ path: '/as/role_mappings/enable_role_based_access',
+ })
+ );
+}
+
export function registerRoleMappingsRoute({
router,
enterpriseSearchRequestHandler,
@@ -27,7 +42,7 @@ export function registerRoleMappingsRoute({
validate: false,
},
enterpriseSearchRequestHandler.createRequest({
- path: '/role_mappings',
+ path: '/as/role_mappings',
})
);
@@ -39,7 +54,7 @@ export function registerRoleMappingsRoute({
},
},
enterpriseSearchRequestHandler.createRequest({
- path: '/role_mappings',
+ path: '/as/role_mappings',
})
);
}
@@ -59,7 +74,7 @@ export function registerRoleMappingRoute({
},
},
enterpriseSearchRequestHandler.createRequest({
- path: '/role_mappings/:id',
+ path: '/as/role_mappings/:id',
})
);
@@ -73,12 +88,13 @@ export function registerRoleMappingRoute({
},
},
enterpriseSearchRequestHandler.createRequest({
- path: '/role_mappings/:id',
+ path: '/as/role_mappings/:id',
})
);
}
export const registerRoleMappingsRoutes = (dependencies: RouteDependencies) => {
+ registerEnableRoleMappingsRoute(dependencies);
registerRoleMappingsRoute(dependencies);
registerRoleMappingRoute(dependencies);
};
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.test.ts
index a945866da5ef2..aa0e9983166c0 100644
--- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.test.ts
@@ -7,9 +7,36 @@
import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__';
-import { registerOrgRoleMappingsRoute, registerOrgRoleMappingRoute } from './role_mappings';
+import {
+ registerOrgEnableRoleMappingsRoute,
+ registerOrgRoleMappingsRoute,
+ registerOrgRoleMappingRoute,
+} from './role_mappings';
describe('role mappings routes', () => {
+ describe('POST /api/workplace_search/org/role_mappings/enable_role_based_access', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'post',
+ path: '/api/workplace_search/org/role_mappings/enable_role_based_access',
+ });
+
+ registerOrgEnableRoleMappingsRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request handler', () => {
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/role_mappings/enable_role_based_access',
+ });
+ });
+ });
+
describe('GET /api/workplace_search/org/role_mappings', () => {
let mockRouter: MockRouter;
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts
index a0fcec63cbb27..cea7bcb311ce8 100644
--- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts
@@ -17,6 +17,21 @@ const roleMappingBaseSchema = {
authProvider: schema.arrayOf(schema.string()),
};
+export function registerOrgEnableRoleMappingsRoute({
+ router,
+ enterpriseSearchRequestHandler,
+}: RouteDependencies) {
+ router.post(
+ {
+ path: '/api/workplace_search/org/role_mappings/enable_role_based_access',
+ validate: false,
+ },
+ enterpriseSearchRequestHandler.createRequest({
+ path: '/ws/org/role_mappings/enable_role_based_access',
+ })
+ );
+}
+
export function registerOrgRoleMappingsRoute({
router,
enterpriseSearchRequestHandler,
@@ -79,6 +94,7 @@ export function registerOrgRoleMappingRoute({
}
export const registerRoleMappingsRoutes = (dependencies: RouteDependencies) => {
+ registerOrgEnableRoleMappingsRoute(dependencies);
registerOrgRoleMappingsRoute(dependencies);
registerOrgRoleMappingRoute(dependencies);
};
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index e246cd0681053..17c31b8cd115e 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -7521,7 +7521,6 @@
"xpack.enterpriseSearch.appSearch.credentials.title": "資格情報",
"xpack.enterpriseSearch.appSearch.credentials.updateWarning": "既存の API キーはユーザー間で共有できます。このキーのアクセス権を変更すると、このキーにアクセスできるすべてのユーザーに影響します。",
"xpack.enterpriseSearch.appSearch.credentials.updateWarningTitle": "十分ご注意ください!",
- "xpack.enterpriseSearch.appSearch.deleteRoleMappingMessage": "このマッピングを完全に削除しますか?このアクションは元に戻せません。一部のユーザーがアクセスを失う可能性があります。",
"xpack.enterpriseSearch.appSearch.DEV_ROLE_TYPE_DESCRIPTION": "開発者はエンジンのすべての要素を管理できます。",
"xpack.enterpriseSearch.appSearch.documentCreation.api.description": "{documentsApiLink}を使用すると、新しいドキュメントをエンジンに追加できるほか、ドキュメントの更新、IDによるドキュメントの取得、ドキュメントの削除が可能です。基本操作を説明するさまざまな{clientLibrariesLink}があります。",
"xpack.enterpriseSearch.appSearch.documentCreation.api.example": "実行中のAPIを表示するには、コマンドラインまたはクライアントライブラリを使用して、次の要求の例で実験することができます。",
@@ -7906,6 +7905,7 @@
"xpack.enterpriseSearch.appSearch.tokens.search.description": "エンドポイントのみの検索では、公開検索キーが使用されます。",
"xpack.enterpriseSearch.appSearch.tokens.search.name": "公開検索キー",
"xpack.enterpriseSearch.appSearch.tokens.update": "正常に API キーを更新しました。",
+ "xpack.enterpriseSearch.emailLabel": "メール",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "場所を問わず、何でも検索。組織を支える多忙なチームのために、パワフルでモダンな検索エクスペリエンスを簡単に導入できます。Webサイトやアプリ、ワークプレイスに事前調整済みの検索をすばやく追加しましょう。何でもシンプルに検索できます。",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "エンタープライズサーチはまだKibanaインスタンスで構成されていません。",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt": "エンタープライズ サーチの基本操作",
@@ -7948,15 +7948,14 @@
"xpack.enterpriseSearch.roleMapping.attributeSelectorTitle": "属性マッピング",
"xpack.enterpriseSearch.roleMapping.attributeValueLabel": "属性値",
"xpack.enterpriseSearch.roleMapping.authProviderLabel": "認証プロバイダー",
- "xpack.enterpriseSearch.roleMapping.deleteRoleMappingButton": "マッピングを削除",
"xpack.enterpriseSearch.roleMapping.deleteRoleMappingDescription": "マッピングの削除は永久的であり、元に戻すことはできません",
- "xpack.enterpriseSearch.roleMapping.deleteRoleMappingTitle": "このロールマッピングを削除",
"xpack.enterpriseSearch.roleMapping.externalAttributeLabel": "外部属性",
"xpack.enterpriseSearch.roleMapping.filterRoleMappingsPlaceholder": "ロールをフィルタリング...",
"xpack.enterpriseSearch.roleMapping.individualAuthProviderLabel": "個別の認証プロバイダーを選択",
"xpack.enterpriseSearch.roleMapping.manageRoleMappingTitle": "ロールマッピングを管理",
"xpack.enterpriseSearch.roleMapping.noResults.message": "の結果が見つかりません。",
"xpack.enterpriseSearch.roleMapping.newRoleMappingTitle": "ロールマッピングを追加",
+ "xpack.enterpriseSearch.roleMapping.removeRoleMappingTitle": "このロールマッピングを削除",
"xpack.enterpriseSearch.roleMapping.roleLabel": "ロール",
"xpack.enterpriseSearch.roleMapping.roleMappingsTitle": "ユーザーとロール",
"xpack.enterpriseSearch.roleMapping.saveRoleMappingButtonLabel": "ロールマッピングの保存",
@@ -7993,6 +7992,7 @@
"xpack.enterpriseSearch.troubleshooting.differentEsClusters.title": "{productName}とKibanaは別のElasticsearchクラスターにあります",
"xpack.enterpriseSearch.troubleshooting.standardAuth.description": "このプラグインは、{standardAuthLink}の{productName}を完全にはサポートしていません。{productName}で作成されたユーザーはKibanaアクセス権が必要です。Kibanaで作成されたユーザーは、ナビゲーションメニューに{productName}が表示されません。",
"xpack.enterpriseSearch.troubleshooting.standardAuth.title": "標準認証の{productName}はサポートされていません",
+ "xpack.enterpriseSearch.usernameLabel": "ユーザー名",
"xpack.enterpriseSearch.workplaceSearch.accountNav.account.link": "マイアカウント",
"xpack.enterpriseSearch.workplaceSearch.accountNav.logout.link": "ログアウト",
"xpack.enterpriseSearch.workplaceSearch.accountNav.orgDashboard.link": "組織ダッシュボードに移動",
@@ -8163,8 +8163,6 @@
"xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupTableHeader": "グループ",
"xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.sourcesTableHeader": "コンテンツソース",
"xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.usersTableHeader": "ユーザー",
- "xpack.enterpriseSearch.workplaceSearch.groups.groupsUsersTable.emailTableHeader": "メール",
- "xpack.enterpriseSearch.workplaceSearch.groups.groupsUsersTable.usernameTableHeader": "ユーザー名",
"xpack.enterpriseSearch.workplaceSearch.groups.groupUpdatedText": "前回更新日時{updatedAt}。",
"xpack.enterpriseSearch.workplaceSearch.groups.groupUsersUpdated": "このグループのユーザーが正常に更新されました。",
"xpack.enterpriseSearch.workplaceSearch.groups.heading": "グループを管理",
@@ -8264,7 +8262,6 @@
"xpack.enterpriseSearch.workplaceSearch.reset.button": "リセット",
"xpack.enterpriseSearch.workplaceSearch.roleMapping.adminRoleTypeDescription": "管理者は、コンテンツソース、グループ、ユーザー管理機能など、すべての組織レベルの設定に無制限にアクセスできます。",
"xpack.enterpriseSearch.workplaceSearch.roleMapping.defaultGroupName": "デフォルト",
- "xpack.enterpriseSearch.workplaceSearch.roleMapping.deleteRoleMappingButtonMessage": "このマッピングを完全に削除しますか?このアクションは元に戻せません。一部のユーザーがアクセスを失う可能性があります。",
"xpack.enterpriseSearch.workplaceSearch.roleMapping.groupAssignmentInvalidError": "1つ以上の割り当てられたグループが必要です。",
"xpack.enterpriseSearch.workplaceSearch.roleMapping.roleMappingsTableHeader": "グループアクセス",
"xpack.enterpriseSearch.workplaceSearch.roleMapping.userRoleTypeDescription": "ユーザーの機能アクセスは検索インターフェースと個人設定管理に制限されます。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 6a96769e2da1e..055ccbdde6ae8 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -7580,7 +7580,6 @@
"xpack.enterpriseSearch.appSearch.credentials.title": "凭据",
"xpack.enterpriseSearch.appSearch.credentials.updateWarning": "现有 API 密钥可在用户之间共享。更改此密钥的权限将影响有权访问此密钥的所有用户。",
"xpack.enterpriseSearch.appSearch.credentials.updateWarningTitle": "谨慎操作!",
- "xpack.enterpriseSearch.appSearch.deleteRoleMappingMessage": "确定要永久删除此映射?此操作不可逆转,且某些用户可能会失去访问权限。",
"xpack.enterpriseSearch.appSearch.DEV_ROLE_TYPE_DESCRIPTION": "开发人员可以管理引擎的所有方面。",
"xpack.enterpriseSearch.appSearch.documentCreation.api.description": "{documentsApiLink} 可用于将新文档添加到您的引擎、更新文档、按 ID 检索文档以及删除文档。有各种{clientLibrariesLink}可帮助您入门。",
"xpack.enterpriseSearch.appSearch.documentCreation.api.example": "要了解如何使用 API,可以在下面通过命令行或客户端库试用示例请求。",
@@ -7974,6 +7973,7 @@
"xpack.enterpriseSearch.appSearch.tokens.search.description": "公有搜索密钥仅用于搜索终端。",
"xpack.enterpriseSearch.appSearch.tokens.search.name": "公有搜索密钥",
"xpack.enterpriseSearch.appSearch.tokens.update": "成功更新 API 密钥。",
+ "xpack.enterpriseSearch.emailLabel": "电子邮件",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "随时随地进行全面搜索。为工作繁忙的团队轻松实现强大的现代搜索体验。将预先调整的搜索功能快速添加到您的网站、应用或工作区。全面搜索就是这么简单。",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "企业搜索尚未在您的 Kibana 实例中配置。",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt": "企业搜索入门",
@@ -8016,9 +8016,7 @@
"xpack.enterpriseSearch.roleMapping.attributeSelectorTitle": "属性映射",
"xpack.enterpriseSearch.roleMapping.attributeValueLabel": "属性值",
"xpack.enterpriseSearch.roleMapping.authProviderLabel": "身份验证提供程序",
- "xpack.enterpriseSearch.roleMapping.deleteRoleMappingButton": "删除映射",
"xpack.enterpriseSearch.roleMapping.deleteRoleMappingDescription": "请注意,删除映射是永久性的,无法撤消",
- "xpack.enterpriseSearch.roleMapping.deleteRoleMappingTitle": "移除此角色映射",
"xpack.enterpriseSearch.roleMapping.externalAttributeLabel": "外部属性",
"xpack.enterpriseSearch.roleMapping.filterRoleMappingsPlaceholder": "筛选角色......",
"xpack.enterpriseSearch.roleMapping.individualAuthProviderLabel": "选择单个身份验证提供程序",
@@ -8027,6 +8025,7 @@
"xpack.enterpriseSearch.roleMapping.newRoleMappingTitle": "添加角色映射",
"xpack.enterpriseSearch.roleMapping.roleLabel": "角色",
"xpack.enterpriseSearch.roleMapping.roleMappingsTitle": "用户和角色",
+ "xpack.enterpriseSearch.roleMapping.removeRoleMappingTitle": "移除此角色映射",
"xpack.enterpriseSearch.roleMapping.saveRoleMappingButtonLabel": "保存角色映射",
"xpack.enterpriseSearch.roleMapping.updateRoleMappingButtonLabel": "更新角色映射",
"xpack.enterpriseSearch.schema.addFieldModal.fieldNameNote.correct": "字段名称只能包含小写字母、数字和下划线",
@@ -8061,6 +8060,7 @@
"xpack.enterpriseSearch.troubleshooting.differentEsClusters.title": "{productName} 和 Kibana 在不同的 Elasticsearch 集群中",
"xpack.enterpriseSearch.troubleshooting.standardAuth.description": "此插件不完全支持使用 {standardAuthLink} 的 {productName}。{productName} 中创建的用户必须具有 Kibana 访问权限。Kibana 中创建的用户在导航菜单中将看不到 {productName}。",
"xpack.enterpriseSearch.troubleshooting.standardAuth.title": "不支持使用标准身份验证的 {productName}",
+ "xpack.enterpriseSearch.usernameLabel": "用户名",
"xpack.enterpriseSearch.workplaceSearch.accountNav.account.link": "我的帐户",
"xpack.enterpriseSearch.workplaceSearch.accountNav.logout.link": "注销",
"xpack.enterpriseSearch.workplaceSearch.accountNav.orgDashboard.link": "前往组织仪表板",
@@ -8231,8 +8231,6 @@
"xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupTableHeader": "组",
"xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.sourcesTableHeader": "内容源",
"xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.usersTableHeader": "用户",
- "xpack.enterpriseSearch.workplaceSearch.groups.groupsUsersTable.emailTableHeader": "电子邮件",
- "xpack.enterpriseSearch.workplaceSearch.groups.groupsUsersTable.usernameTableHeader": "用户名",
"xpack.enterpriseSearch.workplaceSearch.groups.groupUpdatedText": "上次更新于 {updatedAt}。",
"xpack.enterpriseSearch.workplaceSearch.groups.groupUsersUpdated": "已成功更新此组的用户",
"xpack.enterpriseSearch.workplaceSearch.groups.heading": "管理组",
@@ -8332,7 +8330,6 @@
"xpack.enterpriseSearch.workplaceSearch.reset.button": "重置",
"xpack.enterpriseSearch.workplaceSearch.roleMapping.adminRoleTypeDescription": "管理员对所有组织范围设置 (包括内容源、组和用户管理功能) 具有完全权限。",
"xpack.enterpriseSearch.workplaceSearch.roleMapping.defaultGroupName": "默认",
- "xpack.enterpriseSearch.workplaceSearch.roleMapping.deleteRoleMappingButtonMessage": "确定要永久删除此映射?此操作不可逆转,且某些用户可能会失去访问权限。",
"xpack.enterpriseSearch.workplaceSearch.roleMapping.groupAssignmentInvalidError": "至少需要一个分配的组。",
"xpack.enterpriseSearch.workplaceSearch.roleMapping.roleMappingsTableHeader": "组访问权限",
"xpack.enterpriseSearch.workplaceSearch.roleMapping.userRoleTypeDescription": "用户的功能访问权限仅限于搜索界面和个人设置管理。",
From 136d3617032526dcb396896da408791c1362cb39 Mon Sep 17 00:00:00 2001
From: Greg Thompson
Date: Wed, 23 Jun 2021 15:10:34 -0500
Subject: [PATCH 02/86] Upgrade EUI to v34.3.0 (#101334)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* eui to v34.1.0
* styled-components types
* src snapshot updates
* x-pack snapshot updates
* eui to v34.2.0
* styled-components todo
* src snapshot updates
* x-pack snapshot updates
* jest test updates
* collapsible_nav
* Hard-code global nav width for bottom bar’s (for now)
* Update to eui v34.3.0
* flyout unmock
* src flyout snapshots
* remove duplicate euioverlaymask
* xpack flyout snapshots
* remove unused import
* sidenavprops
* attr updates
* trial: flyout ownfocus
* remove unused
* graph selector
* jest
* jest
* flyout ownFocus
* saved objects flyout
* console welcome flyout
* timeline flyout
* clean up
* visible
* colorpicker data-test-subj
* selectors
* selector
* ts
* selector
* snapshot
* Fix `use_security_solution_navigation` TS error
* cypress
Co-authored-by: cchaos
Co-authored-by: Chandler Prall
---
package.json | 2 +-
.../collapsible_nav.test.tsx.snap | 3451 ++++++++---------
.../header/__snapshots__/header.test.tsx.snap | 989 +++--
.../chrome/ui/header/collapsible_nav.test.tsx | 4 -
.../public/chrome/ui/header/header.test.tsx | 2 +-
src/core/public/chrome/ui/header/header.tsx | 2 +-
.../flyout_service.test.tsx.snap | 4 +-
src/core/public/styles/_base.scss | 2 +-
.../application/components/welcome_panel.tsx | 2 +-
.../dashboard_empty_screen.test.tsx.snap | 8 +-
.../__snapshots__/data_view.test.tsx.snap | 30 +-
.../discover_grid_flyout.test.tsx | 4 +-
.../__snapshots__/source_viewer.test.tsx.snap | 22 +-
.../url/__snapshots__/url.test.tsx.snap | 8 +-
.../header/__snapshots__/header.test.tsx.snap | 2 +-
.../warning_call_out.test.tsx.snap | 98 +-
.../inspector_panel.test.tsx.snap | 1 +
.../__snapshots__/solution_nav.test.tsx.snap | 18 +
.../solution_nav/solution_nav.tsx | 2 +-
.../public/components/labs/labs_flyout.tsx | 51 +-
.../__snapshots__/intro.test.tsx.snap | 26 +-
.../not_found_errors.test.tsx.snap | 160 +-
.../__snapshots__/flyout.test.tsx.snap | 3 +
.../objects_table/components/flyout.tsx | 2 +-
.../components/color_picker.test.tsx | 4 +-
.../visualization_noresults.test.js.snap | 2 +-
test/accessibility/apps/management.ts | 1 +
.../apps/management/_import_objects.ts | 8 +-
test/functional/page_objects/settings_page.ts | 4 +
.../page_objects/visual_builder_page.ts | 2 +-
.../Waterfall/ResponsiveFlyout.tsx | 15 +-
.../asset_manager.stories.storyshot | 17 +-
.../custom_element_modal.stories.storyshot | 32 +-
.../datasource_component.stories.storyshot | 10 +-
.../keyboard_shortcuts_doc.stories.storyshot | 2145 +++++-----
.../saved_elements_modal.stories.storyshot | 12 +-
.../__snapshots__/pdf_panel.stories.storyshot | 4 +-
.../__snapshots__/settings.test.tsx.snap | 10 +-
.../autoplay_settings.stories.storyshot | 12 +-
.../toolbar_settings.stories.storyshot | 12 +-
.../filebeat_config_flyout.tsx | 2 +-
.../private_sources_sidebar.tsx | 1 -
.../components/create_agent_policy.tsx | 11 +-
.../extend_index_management.test.tsx.snap | 142 +-
.../__snapshots__/policy_table.test.tsx.snap | 15 +-
.../components/table_basic.test.tsx | 22 +-
.../upload_license.test.tsx.snap | 96 +-
.../action_edit/edit_action_flyout.tsx | 327 +-
.../__snapshots__/checker_errors.test.js.snap | 54 +-
.../__snapshots__/no_data.test.js.snap | 8 +-
.../__snapshots__/page_loading.test.js.snap | 4 +-
.../app/cases/create/flyout.test.tsx | 2 +-
.../components/app/cases/create/flyout.tsx | 10 +-
.../shared/page_template/page_template.tsx | 10 +-
...screen_capture_panel_content.test.tsx.snap | 18 +-
.../report_info_button.test.tsx.snap | 356 +-
.../privilege_summary/privilege_summary.tsx | 61 +-
.../privilege_space_form.tsx | 116 +-
.../roles_grid_page.test.tsx.snap | 34 +-
.../__snapshots__/prompt_page.test.tsx.snap | 4 +-
.../unauthenticated_page.test.tsx.snap | 2 +-
.../reset_session_page.test.tsx.snap | 2 +-
.../timelines/data_providers.spec.ts | 4 +-
.../integration/timelines/pagination.spec.ts | 6 +-
.../cypress/screens/timeline.ts | 8 +-
.../cases/components/create/flyout.test.tsx | 2 +-
.../public/cases/components/create/flyout.tsx | 10 +-
.../exceptions/add_exception_comments.tsx | 2 +-
.../index.test.tsx | 4 +-
.../endpoint_hosts/view/details/index.tsx | 1 +
.../__snapshots__/index.test.tsx.snap | 11 +-
.../timelines/components/flyout/index.tsx | 11 +-
.../components/flyout/pane/index.tsx | 13 +-
.../__snapshots__/index.test.tsx.snap | 827 ++--
.../timelines/components/side_panel/index.tsx | 10 +-
.../edit_transform_flyout.tsx | 127 +-
.../sections/alert_form/alert_add.tsx | 1 +
.../__snapshots__/license_info.test.tsx.snap | 30 +-
.../ml/__snapshots__/ml_flyout.test.tsx.snap | 205 +-
.../__snapshots__/expanded_row.test.tsx.snap | 82 +-
.../waterfall/waterfall_flyout.tsx | 10 +-
.../test/functional/apps/lens/lens_tagging.ts | 2 +-
.../functional/page_objects/graph_page.ts | 4 +-
.../test/functional/page_objects/lens_page.ts | 4 +-
.../page_objects/space_selector_page.ts | 4 +-
.../page_objects/tag_management_page.ts | 5 +-
.../functional/tests/dashboard_integration.ts | 2 +-
.../functional/tests/maps_integration.ts | 2 +-
.../functional/tests/visualize_integration.ts | 2 +-
yarn.lock | 25 +-
90 files changed, 4774 insertions(+), 5120 deletions(-)
diff --git a/package.json b/package.json
index 26465133569cd..f99eb86a43cec 100644
--- a/package.json
+++ b/package.json
@@ -103,7 +103,7 @@
"@elastic/datemath": "link:bazel-bin/packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.13",
"@elastic/ems-client": "7.14.0",
- "@elastic/eui": "33.0.0",
+ "@elastic/eui": "34.3.0",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "^9.0.1-kibana3",
"@elastic/maki": "6.3.0",
diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
index 3668829a6888c..0b10209bc13e5 100644
--- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
+++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
@@ -370,54 +370,62 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
isOpen={true}
onClose={[Function]}
>
-
-
-
- }
- />
-
-
-
-
+
-
-
-
-
+ Custom link
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+ data-euiicon-type="home"
+ />
+
+
+ Home
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+ Recently viewed
+
+
+
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
data-test-subj="collapsibleNavGroup-recentlyViewed"
+ id="generated-id"
initialIsOpen={true}
- isCollapsible={true}
- key="recentlyViewed"
+ isLoading={false}
+ isLoadingMessage={false}
onToggle={[Function]}
- title="Recently viewed"
+ paddingSize="none"
>
-
-
-
-
- Recently viewed
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
+
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
+
+
-
-
- Recently viewed
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+ Analytics
+
+
+
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
data-test-subj="collapsibleNavGroup-kibana"
- iconType="logoKibana"
+ id="generated-id"
initialIsOpen={true}
- isCollapsible={true}
- key="kibana"
+ isLoading={false}
+ isLoadingMessage={false}
onToggle={[Function]}
- title="Analytics"
+ paddingSize="none"
>
-
-
-
-
-
-
-
- Analytics
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
+
-
-
-
-
-
-
-
-
+
+
+
+
-
-
+
+
+
+
-
-
- Analytics
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+ dashboard
+
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Observability
+
+
+
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
data-test-subj="collapsibleNavGroup-observability"
- iconType="logoObservability"
+ id="generated-id"
initialIsOpen={true}
- isCollapsible={true}
- key="observability"
+ isLoading={false}
+ isLoadingMessage={false}
onToggle={[Function]}
- title="Observability"
+ paddingSize="none"
>
-
-
-
-
-
-
-
- Observability
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
+
-
-
-
-
-
-
-
-
+
+
+
+
-
-
+
+
+
+
-
-
- Observability
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Security
+
+
+
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
data-test-subj="collapsibleNavGroup-securitySolution"
- iconType="logoSecurity"
+ id="generated-id"
initialIsOpen={true}
- isCollapsible={true}
- key="securitySolution"
+ isLoading={false}
+ isLoadingMessage={false}
onToggle={[Function]}
- title="Security"
+ paddingSize="none"
>
-
-
-
-
-
-
-
- Security
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
+
-
-
-
-
-
-
-
-
+
+
+
+
-
-
+
+
+
+
-
-
- Security
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Management
+
+
+
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
data-test-subj="collapsibleNavGroup-management"
- iconType="managementApp"
+ id="generated-id"
initialIsOpen={true}
- isCollapsible={true}
- key="management"
+ isLoading={false}
+ isLoadingMessage={false}
onToggle={[Function]}
- title="Management"
+ paddingSize="none"
>
-
-
-
-
-
-
-
- Management
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
+
-
-
-
-
-
-
-
-
+
+
+
+
-
-
+
+
+
+
-
-
- Management
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+ monitoring
+
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
- Dock navigation
-
- ,
- }
- }
- color="subdued"
- data-test-subj="collapsible-nav-lock"
- iconType="lockOpen"
- label="Dock navigation"
- onClick={[Function]}
- size="xs"
- >
-
-
+
-
-
-
+ data-euiicon-type="lockOpen"
+ />
Dock navigation
-
-
-
-
-
-
+ ,
+ }
+ }
+ color="subdued"
+ data-test-subj="collapsible-nav-lock"
+ iconType="lockOpen"
+ label="Dock navigation"
+ onClick={[Function]}
+ size="xs"
+ >
+
+
+
+
+
+
+ Dock navigation
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- close
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
`;
@@ -2770,42 +2706,57 @@ exports[`CollapsibleNav renders the default nav 3`] = `
isOpen={false}
onClose={[Function]}
>
-
-
-
-
-
-
-
-
+ data-euiicon-type="home"
+ />
+
+
+ Home
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+ Recently viewed
+
+
+
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
data-test-subj="collapsibleNavGroup-recentlyViewed"
+ id="generated-id"
initialIsOpen={true}
- isCollapsible={true}
- key="recentlyViewed"
+ isLoading={false}
+ isLoadingMessage={false}
onToggle={[Function]}
- title="Recently viewed"
+ paddingSize="none"
>
-
-
-
-
- Recently viewed
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
+
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
+
+
-
-
- Recently viewed
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
- No recently viewed items
-
-
-
-
-
-
+
+ No recently viewed items
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
- Undock navigation
-
- ,
- }
- }
- color="subdued"
- data-test-subj="collapsible-nav-lock"
- iconType="lock"
- label="Undock navigation"
- onClick={[Function]}
- size="xs"
- >
-
-
-
-
-
+ data-euiicon-type="lock"
+ />
Undock navigation
-
-
-
-
-
-
+ ,
+ }
+ }
+ color="subdued"
+ data-test-subj="collapsible-nav-lock"
+ iconType="lock"
+ label="Undock navigation"
+ onClick={[Function]}
+ size="xs"
+ >
+
+
+
+
+
+
+ Undock navigation
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- close
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
`;
diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
index 6ad1e2d3a1cc6..5aee9ca1b7c08 100644
--- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
+++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
@@ -4947,42 +4947,57 @@ exports[`Header renders 1`] = `
isOpen={false}
onClose={[Function]}
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Manage cloud deployment
-
-
-
-
-
-
-
+ Manage cloud deployment
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+ data-euiicon-type="home"
+ />
+
+
+ Home
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+ Recently viewed
+
+
+
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
data-test-subj="collapsibleNavGroup-recentlyViewed"
+ id="mockId"
initialIsOpen={true}
- isCollapsible={true}
- key="recentlyViewed"
+ isLoading={false}
+ isLoadingMessage={false}
onToggle={[Function]}
- title="Recently viewed"
+ paddingSize="none"
>
-
-
-
-
- Recently viewed
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
+
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
+
+
-
-
- Recently viewed
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+ dashboard
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+ kibana
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Undock navigation
+
+ ,
+ }
+ }
+ color="subdued"
+ data-test-subj="collapsible-nav-lock"
+ iconType="lock"
+ label="Undock navigation"
onClick={[Function]}
- size="s"
+ size="xs"
>
+
+
+
- kibana
+ Undock navigation
@@ -5445,163 +5540,11 @@ exports[`Header renders 1`] = `
-
-
-
-
-
-
-
-
-
- Undock navigation
-
- ,
- }
- }
- color="subdued"
- data-test-subj="collapsible-nav-lock"
- iconType="lock"
- label="Undock navigation"
- onClick={[Function]}
- size="xs"
- >
-
-
-
-
-
-
- Undock navigation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- close
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx
index 7f338a859e7b4..460770744d53a 100644
--- a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx
+++ b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx
@@ -16,10 +16,6 @@ import { httpServiceMock } from '../../../http/http_service.mock';
import { ChromeRecentlyAccessedHistoryItem } from '../../recently_accessed';
import { CollapsibleNav } from './collapsible_nav';
-jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
- htmlIdGenerator: () => () => 'mockId',
-}));
-
const { kibana, observability, security, management } = DEFAULT_APP_CATEGORIES;
function mockLink({ title = 'discover', category }: Partial) {
diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx
index fdbdde8556eeb..a3a0197b4017e 100644
--- a/src/core/public/chrome/ui/header/header.test.tsx
+++ b/src/core/public/chrome/ui/header/header.test.tsx
@@ -99,7 +99,7 @@ describe('Header', () => {
act(() => isLocked$.next(true));
component.update();
- expect(component.find('nav[aria-label="Primary"]').exists()).toBeTruthy();
+ expect(component.find('[data-test-subj="collapsibleNav"]').exists()).toBeTruthy();
expect(component).toMatchSnapshot();
act(() =>
diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx
index 67cdd24aae848..246ca83ef5ade 100644
--- a/src/core/public/chrome/ui/header/header.tsx
+++ b/src/core/public/chrome/ui/header/header.tsx
@@ -87,6 +87,7 @@ export function Header({
const isVisible = useObservable(observables.isVisible$, false);
const isLocked = useObservable(observables.isLocked$, false);
const [isNavOpen, setIsNavOpen] = useState(false);
+ const [navId] = useState(htmlIdGenerator()());
const breadcrumbsAppendExtension = useObservable(breadcrumbsAppendExtension$);
if (!isVisible) {
@@ -99,7 +100,6 @@ export function Header({
}
const toggleCollapsibleNavRef = createRef void }>();
- const navId = htmlIdGenerator()();
const className = classnames('hide-for-sharing', 'headerGlobalNav');
const Breadcrumbs = (
diff --git a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
index f5a1c51ccbe15..fbd09f3096854 100644
--- a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
+++ b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
@@ -26,7 +26,7 @@ Array [
]
`;
-exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `""`;
+exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `""`;
exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = `
Array [
@@ -59,4 +59,4 @@ Array [
]
`;
-exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `""`;
+exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `""`;
diff --git a/src/core/public/styles/_base.scss b/src/core/public/styles/_base.scss
index 3386fa73f328a..de138cdf402e6 100644
--- a/src/core/public/styles/_base.scss
+++ b/src/core/public/styles/_base.scss
@@ -26,7 +26,7 @@
}
.euiBody--collapsibleNavIsDocked .euiBottomBar {
- margin-left: $euiCollapsibleNavWidth;
+ margin-left: 320px; // Hard-coded for now -- @cchaos
}
// Temporary fix for EuiPageHeader with a bottom border but no tabs or padding
diff --git a/src/plugins/console/public/application/components/welcome_panel.tsx b/src/plugins/console/public/application/components/welcome_panel.tsx
index eb746e313d228..8514d41c04a51 100644
--- a/src/plugins/console/public/application/components/welcome_panel.tsx
+++ b/src/plugins/console/public/application/components/welcome_panel.tsx
@@ -27,7 +27,7 @@ interface Props {
export function WelcomePanel(props: Props) {
return (
-
+
diff --git a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap
index 9f56740fdac22..afe339f3f43a2 100644
--- a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap
+++ b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap
@@ -603,7 +603,7 @@ exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = `
}
>
-
-
+
@@ -950,7 +950,7 @@ exports[`DashboardEmptyScreen renders correctly with view mode 1`] = `
}
>
-
-
+
diff --git a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
index a0a7e54d27532..0ab3f8a4e3466 100644
--- a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
+++ b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
@@ -176,27 +176,27 @@ exports[`Inspector Data View component should render empty state 1`] = `
+
+
+
+ No data available
+
+
+
-
-
-
- No data available
-
-
-
diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx
index 60841799b1398..50be2473a441e 100644
--- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx
+++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx
@@ -144,7 +144,9 @@ describe('Discover flyout', function () {
expect(props.setExpandedDoc.mock.calls[0][0]._id).toBe('4');
});
- it('allows navigating with arrow keys through documents', () => {
+ // EuiFlyout is mocked in Jest environments.
+ // EUI team to reinstate `onKeyDown`: https://github.com/elastic/eui/issues/4883
+ it.skip('allows navigating with arrow keys through documents', () => {
const props = getProps();
const component = mountWithIntl( );
findTestSubject(component, 'docTableDetailsFlyout').simulate('keydown', { key: 'ArrowRight' });
diff --git a/src/plugins/discover/public/application/components/source_viewer/__snapshots__/source_viewer.test.tsx.snap b/src/plugins/discover/public/application/components/source_viewer/__snapshots__/source_viewer.test.tsx.snap
index f40dbbbae1f87..68786871825ac 100644
--- a/src/plugins/discover/public/application/components/source_viewer/__snapshots__/source_viewer.test.tsx.snap
+++ b/src/plugins/discover/public/application/components/source_viewer/__snapshots__/source_viewer.test.tsx.snap
@@ -147,27 +147,27 @@ exports[`Source Viewer component renders error state 1`] = `
/>
+
+
+ An Error Occurred
+
+
-
-
- An Error Occurred
-
-
diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap
index 40170c39942e5..79c1a11cfef84 100644
--- a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap
+++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap
@@ -153,7 +153,7 @@ exports[`UrlFormatEditor should render normally 1`] = `
class="euiFormControlLayout__childrenWrapper"
>
diff --git a/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap b/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap
index 5ad8205365146..67d2cf72c5375 100644
--- a/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap
+++ b/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap
@@ -329,6 +329,7 @@ exports[`InspectorPanel should render as expected 1`] = `
>
& {
+export type KibanaPageTemplateSolutionNavProps = Partial> & {
/**
* Name of the solution, i.e. "Observability"
*/
diff --git a/src/plugins/presentation_util/public/components/labs/labs_flyout.tsx b/src/plugins/presentation_util/public/components/labs/labs_flyout.tsx
index 5b424c7e95f18..1af85da983085 100644
--- a/src/plugins/presentation_util/public/components/labs/labs_flyout.tsx
+++ b/src/plugins/presentation_util/public/components/labs/labs_flyout.tsx
@@ -20,7 +20,6 @@ import {
EuiFlexItem,
EuiFlexGroup,
EuiIcon,
- EuiOverlayMask,
} from '@elastic/eui';
import { SolutionName, ProjectStatus, ProjectID, Project, EnvironmentName } from '../../../common';
@@ -124,30 +123,32 @@ export const LabsFlyout = (props: Props) => {
);
return (
- onClose()} headerZindexLocation="below">
-
-
-
-
-
-
-
-
- {strings.getTitleLabel()}
-
-
-
-
-
- {strings.getDescriptionMessage()}
-
-
-
-
-
- {footer}
-
-
+
+
+
+
+
+
+
+
+ {strings.getTitleLabel()}
+
+
+
+
+
+ {strings.getDescriptionMessage()}
+
+
+
+
+
+ {footer}
+
);
};
diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/intro.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/intro.test.tsx.snap
index 5239a92543539..5a8cd06b8ecc0 100644
--- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/intro.test.tsx.snap
+++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/intro.test.tsx.snap
@@ -47,20 +47,30 @@ exports[`Intro component renders correctly 1`] = `
-
-
+
- Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldn’t be.
-
-
+
+
+ Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldn’t be.
+
+
+
+
diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap
index bddfe000008d4..f977c17df41d3 100644
--- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap
+++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap
@@ -49,29 +49,39 @@ exports[`NotFoundErrors component renders correctly for index-pattern type 1`] =
-
-
- The index pattern associated with this object no longer exists.
-
-
-
-
+
- If you know what this error means, go ahead and fix it — otherwise click the delete button above.
-
-
+
+
+ The index pattern associated with this object no longer exists.
+
+
+
+
+ If you know what this error means, go ahead and fix it — otherwise click the delete button above.
+
+
+
+
@@ -128,29 +138,39 @@ exports[`NotFoundErrors component renders correctly for index-pattern-field type
-
-
- A field associated with this object no longer exists in the index pattern.
-
-
-
-
+
- If you know what this error means, go ahead and fix it — otherwise click the delete button above.
-
-
+
+
+ A field associated with this object no longer exists in the index pattern.
+
+
+
+
+ If you know what this error means, go ahead and fix it — otherwise click the delete button above.
+
+
+
+
@@ -207,29 +227,39 @@ exports[`NotFoundErrors component renders correctly for search type 1`] = `
-
-
- The saved search associated with this object no longer exists.
-
-
-
-
+
- If you know what this error means, go ahead and fix it — otherwise click the delete button above.
-
-
+
+
+ The saved search associated with this object no longer exists.
+
+
+
+
+ If you know what this error means, go ahead and fix it — otherwise click the delete button above.
+
+
+
+
@@ -286,21 +316,31 @@ exports[`NotFoundErrors component renders correctly for unknown type 1`] = `
-
-
-
+
- If you know what this error means, go ahead and fix it — otherwise click the delete button above.
-
-
+
+
+
+ If you know what this error means, go ahead and fix it — otherwise click the delete button above.
+
+
+
+
diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap
index a68e8891b5ad1..bd97f2e6bffb1 100644
--- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap
+++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap
@@ -2,6 +2,7 @@
exports[`Flyout conflicts should allow conflict resolution 1`] = `
@@ -277,6 +278,7 @@ exports[`Flyout conflicts should allow conflict resolution 2`] = `
exports[`Flyout legacy conflicts should allow conflict resolution 1`] = `
@@ -548,6 +550,7 @@ Array [
exports[`Flyout should render import step 1`] = `
diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx
index 62e0cd0504e8e..f6c8d5fb69408 100644
--- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx
+++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx
@@ -960,7 +960,7 @@ export class Flyout extends Component {
}
return (
-
+
diff --git a/src/plugins/vis_type_timeseries/public/application/components/color_picker.test.tsx b/src/plugins/vis_type_timeseries/public/application/components/color_picker.test.tsx
index 8e975f9904256..50d3e8c38e389 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/color_picker.test.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/color_picker.test.tsx
@@ -36,7 +36,7 @@ describe('ColorPicker', () => {
const props = { ...defaultProps, value: '#68BC00' };
component = mount( );
component.find('.tvbColorPicker button').simulate('click');
- const input = findTestSubject(component, 'topColorPickerInput');
+ const input = findTestSubject(component, 'euiColorPickerInput_top');
expect(input.props().value).toBe('#68BC00');
});
@@ -44,7 +44,7 @@ describe('ColorPicker', () => {
const props = { ...defaultProps, value: 'rgba(85,66,177,1)' };
component = mount( );
component.find('.tvbColorPicker button').simulate('click');
- const input = findTestSubject(component, 'topColorPickerInput');
+ const input = findTestSubject(component, 'euiColorPickerInput_top');
expect(input.props().value).toBe('85,66,177,1');
});
diff --git a/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap b/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap
index 25ec05c83a8c6..56e2cb1b60f3c 100644
--- a/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap
+++ b/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap
@@ -14,7 +14,7 @@ exports[`VisualizationNoResults should render according to snapshot 1`] = `
data-euiicon-type="visualizeApp"
/>
{
await PageObjects.settings.clickEditFieldFormat();
await a11y.testAppSnapshot();
+ await PageObjects.settings.clickCloseEditFieldFormatFlyout();
});
it('Advanced settings', async () => {
diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts
index 0278955c577a1..6ef0bfd5a09e8 100644
--- a/test/functional/apps/management/_import_objects.ts
+++ b/test/functional/apps/management/_import_objects.ts
@@ -419,14 +419,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
'index-pattern-test-1'
);
- await testSubjects.click('pagination-button-next');
+ const flyout = await testSubjects.find('importSavedObjectsFlyout');
+
+ await (await flyout.findByTestSubject('pagination-button-next')).click();
await PageObjects.savedObjects.setOverriddenIndexPatternValue(
'missing-index-pattern-7',
'index-pattern-test-2'
);
- await testSubjects.click('pagination-button-previous');
+ await (await flyout.findByTestSubject('pagination-button-previous')).click();
const selectedIdForMissingIndexPattern1 = await testSubjects.getAttribute(
'managementChangeIndexSelection-missing-index-pattern-1',
@@ -435,7 +437,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(selectedIdForMissingIndexPattern1).to.eql('f1e4c910-a2e6-11e7-bb30-233be9be6a20');
- await testSubjects.click('pagination-button-next');
+ await (await flyout.findByTestSubject('pagination-button-next')).click();
const selectedIdForMissingIndexPattern7 = await testSubjects.getAttribute(
'managementChangeIndexSelection-missing-index-pattern-7',
diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts
index 88951bb04c956..cb8f198177017 100644
--- a/test/functional/page_objects/settings_page.ts
+++ b/test/functional/page_objects/settings_page.ts
@@ -739,6 +739,10 @@ export class SettingsPageObject extends FtrService {
await this.testSubjects.click('editFieldFormat');
}
+ async clickCloseEditFieldFormatFlyout() {
+ await this.testSubjects.click('euiFlyoutCloseButton');
+ }
+
async associateIndexPattern(oldIndexPatternId: string, newIndexPatternTitle: string) {
await this.find.clickByCssSelector(
`select[data-test-subj="managementChangeIndexSelection-${oldIndexPatternId}"] >
diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts
index 6e263dd1cdbbf..7f1ea64bcd979 100644
--- a/test/functional/page_objects/visual_builder_page.ts
+++ b/test/functional/page_objects/visual_builder_page.ts
@@ -563,7 +563,7 @@ export class VisualBuilderPageObject extends FtrService {
public async checkColorPickerPopUpIsPresent(): Promise {
this.log.debug(`Check color picker popup is present`);
- await this.testSubjects.existOrFail('colorPickerPopover', { timeout: 5000 });
+ await this.testSubjects.existOrFail('euiColorPickerPopover', { timeout: 5000 });
}
public async changePanelPreview(nth: number = 0): Promise {
diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx
index 8549f09bba248..09fbf07b8ecbd 100644
--- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx
+++ b/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx
@@ -5,10 +5,21 @@
* 2.0.
*/
+import { ReactNode } from 'react';
+import { StyledComponent } from 'styled-components';
import { EuiFlyout } from '@elastic/eui';
-import { euiStyled } from '../../../../../../../../../../src/plugins/kibana_react/common';
+import {
+ euiStyled,
+ EuiTheme,
+} from '../../../../../../../../../../src/plugins/kibana_react/common';
-export const ResponsiveFlyout = euiStyled(EuiFlyout)`
+// TODO: EUI team follow up on complex types and styled-components `styled`
+// https://github.com/elastic/eui/issues/4855
+export const ResponsiveFlyout: StyledComponent<
+ typeof EuiFlyout,
+ EuiTheme,
+ { children?: ReactNode }
+> = euiStyled(EuiFlyout)`
width: 100%;
@media (min-width: 800px) {
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot
index 34b6b333f3ef5..d567d3cf85f13 100644
--- a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot
@@ -116,20 +116,13 @@ exports[`Storyshots components/Assets/AssetManager no assets 1`] = `
size="xxl"
/>
-
-
- Import your assets to get started
-
-
-
+ Import your assets to get started
+
diff --git a/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/__snapshots__/custom_element_modal.stories.storyshot b/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/__snapshots__/custom_element_modal.stories.storyshot
index 18f86aca24302..dc66eef809050 100644
--- a/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/__snapshots__/custom_element_modal.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/__snapshots__/custom_element_modal.stories.storyshot
@@ -80,7 +80,7 @@ exports[`Storyshots components/Elements/CustomElementModal with description 1`]
className="euiFormControlLayout__childrenWrapper"
>
40 characters remaining
@@ -119,7 +119,7 @@ exports[`Storyshots components/Elements/CustomElementModal with description 1`]
className="euiFormRow__fieldWrapper"
>
83 characters remaining
@@ -389,7 +389,7 @@ exports[`Storyshots components/Elements/CustomElementModal with image 1`] = `
className="euiFormControlLayout__childrenWrapper"
>
40 characters remaining
@@ -428,7 +428,7 @@ exports[`Storyshots components/Elements/CustomElementModal with image 1`] = `
className="euiFormRow__fieldWrapper"
>
100 characters remaining
@@ -695,7 +695,7 @@ exports[`Storyshots components/Elements/CustomElementModal with name 1`] = `
className="euiFormControlLayout__childrenWrapper"
>
32 characters remaining
@@ -734,7 +734,7 @@ exports[`Storyshots components/Elements/CustomElementModal with name 1`] = `
className="euiFormRow__fieldWrapper"
>
100 characters remaining
@@ -996,7 +996,7 @@ exports[`Storyshots components/Elements/CustomElementModal with title 1`] = `
className="euiFormControlLayout__childrenWrapper"
>
40 characters remaining
@@ -1035,7 +1035,7 @@ exports[`Storyshots components/Elements/CustomElementModal with title 1`] = `
className="euiFormRow__fieldWrapper"
>
100 characters remaining
diff --git a/x-pack/plugins/canvas/public/components/datasource/__stories__/__snapshots__/datasource_component.stories.storyshot b/x-pack/plugins/canvas/public/components/datasource/__stories__/__snapshots__/datasource_component.stories.storyshot
index 6d170d78dd01d..836047959caee 100644
--- a/x-pack/plugins/canvas/public/components/datasource/__stories__/__snapshots__/datasource_component.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/datasource/__stories__/__snapshots__/datasource_component.stories.storyshot
@@ -39,9 +39,13 @@ exports[`Storyshots components/datasource/DatasourceComponent datasource with ex
-
- The datasource has an argument controlled by an expression. Use the expression editor to modify the datasource.
-
+
+
+ The datasource has an argument controlled by an expression. Use the expression editor to modify the datasource.
+
+
diff --git a/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/__stories__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot b/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/__stories__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot
index 5e833944046a4..bc6430c4c0357 100644
--- a/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/__stories__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/__stories__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot
@@ -2,1103 +2,1088 @@
exports[`Storyshots components/KeyboardShortcutsDoc default 1`] = `
+
-
-
-
-
-
- Keyboard shortcuts
-
-
+ Keyboard shortcuts
+
+
+
-
+ Element controls
+
+
+
-
- Element controls
-
-
-
-
- Cut
-
-
-
-
-
- CTRL
-
-
-
-
-
- X
-
-
-
-
-
- Copy
-
-
-
-
-
- CTRL
-
-
-
-
-
- C
-
-
-
-
-
- Paste
-
-
-
-
-
- CTRL
-
-
-
-
-
- V
-
-
-
-
-
- Clone
-
-
-
-
-
- CTRL
-
-
-
-
-
- D
-
-
-
-
-
- Delete
-
-
-
-
-
- DEL
-
-
-
-
-
- or
-
-
-
-
-
- BACKSPACE
-
-
-
-
-
- Bring to front
-
-
-
-
-
- CTRL
-
-
-
-
-
- ↑
-
-
-
-
-
- Bring forward
-
-
-
-
-
- CTRL
-
-
-
-
-
- SHIFT
-
-
-
-
-
- ↑
-
-
-
-
-
- Send backward
-
-
-
-
-
- CTRL
-
-
-
-
-
- ↓
-
-
-
-
-
- Send to back
-
-
-
-
-
- CTRL
-
-
-
-
-
- SHIFT
-
-
-
-
-
- ↓
-
-
-
-
-
- Group
-
-
-
-
-
- G
-
-
-
-
-
- Ungroup
-
-
-
-
-
- U
-
-
-
-
-
- Shift up by 10px
-
-
-
-
-
- ↑
-
-
-
-
-
- Shift down by 10px
-
-
-
-
-
- ↓
-
-
-
-
-
- Shift left by 10px
-
-
-
-
-
- ←
-
-
-
-
-
- Shift right by 10px
-
-
-
-
-
- →
-
-
-
-
-
- Shift up by 1px
-
-
-
-
-
- SHIFT
-
-
-
-
-
- ↑
-
-
-
-
-
- Shift down by 1px
-
-
-
-
-
- SHIFT
-
-
-
-
-
- ↓
-
-
-
-
-
- Shift left by 1px
-
-
-
-
-
- SHIFT
-
-
-
-
-
- ←
-
-
-
-
-
- Shift right by 1px
-
-
-
-
-
- SHIFT
-
-
-
-
-
- →
-
-
-
-
-
-
-
+
+ Cut
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ X
+
+
+
+
+
+ Copy
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ C
+
+
+
+
+
+ Paste
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ V
+
+
+
+
+
+ Clone
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ D
+
+
+
+
+
+ Delete
+
+
+
+
+
+ DEL
+
+
+
+
+
+ or
+
+
+
+
+
+ BACKSPACE
+
+
+
+
+
+ Bring to front
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ ↑
+
+
+
+
+
+ Bring forward
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ ↑
+
+
+
+
+
+ Send backward
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ ↓
+
+
+
+
+
+ Send to back
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ ↓
+
+
+
+
+
+ Group
+
+
+
+
+
+ G
+
+
+
+
+
+ Ungroup
+
+
+
+
+
+ U
+
+
+
+
+
+ Shift up by 10px
+
+
+
+
+
+ ↑
+
+
+
+
+
+ Shift down by 10px
+
+
+
+
+
+ ↓
+
+
+
+
+
+ Shift left by 10px
+
+
+
+
+
+ ←
+
+
+
+
+
+ Shift right by 10px
+
+
+
+
+
+ →
+
+
+
+
+
+ Shift up by 1px
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ ↑
+
+
+
+
+
+ Shift down by 1px
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ ↓
+
+
+
+
+
+ Shift left by 1px
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ ←
+
+
+
+
+
+ Shift right by 1px
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ →
+
+
+
+
+
+
+
+
+ Expression controls
+
+
+
-
- Expression controls
-
-
-
-
- Run whole expression
-
-
-
-
-
- CTRL
-
-
-
-
-
- ENTER
-
-
-
-
-
-
-
+
+ Run whole expression
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ ENTER
+
+
+
+
+
+
+
+
-
- Editor controls
-
-
-
-
- Select multiple elements
-
-
-
-
-
- SHIFT
-
-
-
-
-
- CLICK
-
-
-
-
-
- Resize from center
-
-
-
-
-
- ALT
-
-
-
-
-
- DRAG
-
-
-
-
-
- Move, resize, and rotate without snapping
-
-
-
-
-
- CTRL
-
-
-
-
-
- DRAG
-
-
-
-
-
- Select element below
-
-
-
-
-
- CTRL
-
-
-
-
-
- CLICK
-
-
-
-
-
- Undo last action
-
-
-
-
-
- CTRL
-
-
-
-
-
- Z
-
-
-
-
-
- Redo last action
-
-
-
-
-
- CTRL
-
-
-
-
-
- SHIFT
-
-
-
-
-
- Z
-
-
-
-
-
- Go to previous page
-
-
-
-
-
- ALT
-
-
-
-
-
- [
-
-
-
-
-
- Go to next page
-
-
-
-
-
- ALT
-
-
-
-
-
- ]
-
-
-
-
-
- Toggle edit mode
-
-
-
-
-
- ALT
-
-
-
-
-
- E
-
-
-
-
-
- Show grid
-
-
-
-
-
- ALT
-
-
-
-
-
- G
-
-
-
-
-
- Refresh workpad
-
-
-
-
-
- ALT
-
-
-
-
-
- R
-
-
-
-
-
- Zoom in
-
-
-
-
-
- CTRL
-
-
-
-
-
- ALT
-
-
-
-
-
- +
-
-
-
-
-
- Zoom out
-
-
-
-
-
- CTRL
-
-
-
-
-
- ALT
-
-
-
-
-
- -
-
-
-
-
-
- Reset zoom to 100%
-
-
-
-
-
- CTRL
-
-
-
-
-
- ALT
-
-
-
-
-
- [
-
-
-
-
-
- Enter presentation mode
-
-
-
-
-
- ALT
-
-
-
-
-
- F
-
-
-
-
-
- or
-
-
-
-
-
- ALT
-
-
-
-
-
- P
-
-
-
-
-
-
-
+ Editor controls
+
+
+
+
+ Select multiple elements
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ CLICK
+
+
+
+
+
+ Resize from center
+
+
+
+
+
+ ALT
+
+
+
+
+
+ DRAG
+
+
+
+
+
+ Move, resize, and rotate without snapping
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ DRAG
+
+
+
+
+
+ Select element below
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ CLICK
+
+
+
+
+
+ Undo last action
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ Z
+
+
+
+
+
+ Redo last action
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ Z
+
+
+
+
+
+ Go to previous page
+
+
+
+
+
+ ALT
+
+
+
+
+
+ [
+
+
+
+
+
+ Go to next page
+
+
+
+
+
+ ALT
+
+
+
+
+
+ ]
+
+
+
+
+
+ Toggle edit mode
+
+
+
+
+
+ ALT
+
+
+
+
+
+ E
+
+
+
+
+
+ Show grid
+
+
+
+
+
+ ALT
+
+
+
+
+
+ G
+
+
+
+
+
+ Refresh workpad
+
+
+
+
+
+ ALT
+
+
+
+
+
+ R
+
+
+
+
+
+ Zoom in
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ ALT
+
+
+
+
+
+ +
+
+
+
+
+
+ Zoom out
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ ALT
+
+
+
+
+
+ -
+
+
+
+
+
+ Reset zoom to 100%
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ ALT
+
+
+
+
+
+ [
+
+
+
+
+
+ Enter presentation mode
+
+
+
+
+
+ ALT
+
+
+
+
+
+ F
+
+
+
+
+
+ or
+
+
+
+
+
+ ALT
+
+
+
+
+
+ P
+
+
+
+
+
+
+
+
+ Presentation controls
+
+
+
-
- Presentation controls
-
-
-
-
- Enter presentation mode
-
-
-
-
-
- ALT
-
-
-
-
-
- F
-
-
-
-
-
- or
-
-
-
-
-
- ALT
-
-
-
-
-
- P
-
-
-
-
-
- Exit presentation mode
-
-
-
-
-
- ESC
-
-
-
-
-
- Go to previous page
-
-
-
-
-
- ALT
-
-
-
-
-
- [
-
-
-
-
-
- or
-
-
-
-
-
- BACKSPACE
-
-
-
-
-
- or
-
-
-
-
-
- ←
-
-
-
-
-
- Go to next page
-
-
-
-
-
- ALT
-
-
-
-
-
- ]
-
-
-
-
-
- or
-
-
-
-
-
- SPACE
-
-
-
-
-
- or
-
-
-
-
-
- →
-
-
-
-
-
- Refresh workpad
-
-
-
-
-
- ALT
-
-
-
-
-
- R
-
-
-
-
-
- Toggle page cycling
-
-
-
-
-
- P
-
-
-
-
-
-
-
+
+ Enter presentation mode
+
+
+
+
+
+ ALT
+
+
+
+
+
+ F
+
+
+
+
+
+ or
+
+
+
+
+
+ ALT
+
+
+
+
+
+ P
+
+
+
+
+
+ Exit presentation mode
+
+
+
+
+
+ ESC
+
+
+
+
+
+ Go to previous page
+
+
+
+
+
+ ALT
+
+
+
+
+
+ [
+
+
+
+
+
+ or
+
+
+
+
+
+ BACKSPACE
+
+
+
+
+
+ or
+
+
+
+
+
+ ←
+
+
+
+
+
+ Go to next page
+
+
+
+
+
+ ALT
+
+
+
+
+
+ ]
+
+
+
+
+
+ or
+
+
+
+
+
+ SPACE
+
+
+
+
+
+ or
+
+
+
+
+
+ →
+
+
+
+
+
+ Refresh workpad
+
+
+
+
+
+ ALT
+
+
+
+
+
+ R
+
+
+
+
+
+ Toggle page cycling
+
+
+
+
+
+ P
+
+
+
+
+
+
diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/saved_elements_modal.stories.storyshot b/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/saved_elements_modal.stories.storyshot
index bbf8b5dcca896..6cd18b83c3351 100644
--- a/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/saved_elements_modal.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/saved_elements_modal.stories.storyshot
@@ -94,16 +94,16 @@ exports[`Storyshots components/SavedElementsModal no custom elements 1`] = `
size="xxl"
/>
+
+ Add new elements
+
-
- Add new elements
-
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/__snapshots__/pdf_panel.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/__snapshots__/pdf_panel.stories.storyshot
index 010037bee4a0f..75ee0fcae78f3 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/__snapshots__/pdf_panel.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/__snapshots__/pdf_panel.stories.storyshot
@@ -37,7 +37,7 @@ exports[`Storyshots components/WorkpadHeader/ShareMenu/PDFPanel default 1`] = `
>
Remove borders and footer logo
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__snapshots__/settings.test.tsx.snap b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__snapshots__/settings.test.tsx.snap
index 621a6bb211fe9..27f0d3610fb9f 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__snapshots__/settings.test.tsx.snap
+++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__snapshots__/settings.test.tsx.snap
@@ -294,7 +294,7 @@ exports[` can navigate Autoplay Settings 2`] = `
class="euiFormControlLayout__childrenWrapper"
>
can navigate Autoplay Settings 2`] = `
Use shorthand notation, like 30s, 10m, or 1h
@@ -585,7 +585,7 @@ exports[` can navigate Toolbar Settings, closes when activated 2`] =
>
can navigate Toolbar Settings, closes when activated 2`] =
Hide the toolbar when the mouse is not within the Canvas?
@@ -640,4 +640,4 @@ exports[` can navigate Toolbar Settings, closes when activated 2`] =
`;
-exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
You are in a dialog. To close this dialog, hit escape.
"`;
+exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
You are in a dialog. To close this dialog, hit escape.
"`;
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__stories__/__snapshots__/autoplay_settings.stories.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__stories__/__snapshots__/autoplay_settings.stories.storyshot
index f5823874db73e..b32ae3fc2f49f 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__stories__/__snapshots__/autoplay_settings.stories.storyshot
+++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__stories__/__snapshots__/autoplay_settings.stories.storyshot
@@ -101,7 +101,7 @@ exports[`Storyshots shareables/Footer/Settings/AutoplaySettings component: off,
className="euiFormControlLayout__childrenWrapper"
>
Use shorthand notation, like 30s, 10m, or 1h
@@ -264,7 +264,7 @@ exports[`Storyshots shareables/Footer/Settings/AutoplaySettings component: on, 5
className="euiFormControlLayout__childrenWrapper"
>
Use shorthand notation, like 30s, 10m, or 1h
@@ -427,7 +427,7 @@ exports[`Storyshots shareables/Footer/Settings/AutoplaySettings contextual 1`] =
className="euiFormControlLayout__childrenWrapper"
>
Use shorthand notation, like 30s, 10m, or 1h
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__stories__/__snapshots__/toolbar_settings.stories.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__stories__/__snapshots__/toolbar_settings.stories.storyshot
index a8a3f584bbf51..1aafb9cc6b664 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__stories__/__snapshots__/toolbar_settings.stories.storyshot
+++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__stories__/__snapshots__/toolbar_settings.stories.storyshot
@@ -34,7 +34,7 @@ exports[`Storyshots shareables/Footer/Settings/ToolbarSettings component: off 1`
>
Hide the toolbar when the mouse is not within the Canvas?
@@ -122,7 +122,7 @@ exports[`Storyshots shareables/Footer/Settings/ToolbarSettings component: on 1`]
>
Hide the toolbar when the mouse is not within the Canvas?
@@ -210,7 +210,7 @@ exports[`Storyshots shareables/Footer/Settings/ToolbarSettings contextual 1`] =
>
Hide the toolbar when the mouse is not within the Canvas?
diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/filebeat_config_flyout/filebeat_config_flyout.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/filebeat_config_flyout/filebeat_config_flyout.tsx
index 238cdcc2f8d9e..6c9df5cf2eba7 100644
--- a/x-pack/plugins/data_visualizer/public/application/common/components/filebeat_config_flyout/filebeat_config_flyout.tsx
+++ b/x-pack/plugins/data_visualizer/public/application/common/components/filebeat_config_flyout/filebeat_config_flyout.tsx
@@ -64,7 +64,7 @@ export const FilebeatConfigFlyout: FC = ({
}, [username, index, ingestPipelineId, results]);
return (
-
+
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.tsx
index 36496b83b3123..3f6863175e29b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.tsx
@@ -43,7 +43,6 @@ export const PrivateSourcesSidebar = () => {
return (
<>
- {/* @ts-expect-error: TODO, uncomment this once EUI 34.x lands in Kibana & `mobileBreakpoints` is a valid prop */}
{id && }
>
);
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx
index 33dbbb25c5d42..5992888564e7f 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx
@@ -5,7 +5,9 @@
* 2.0.
*/
+import type { ReactNode } from 'react';
import React, { useState } from 'react';
+import type { StyledComponent } from 'styled-components';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -29,7 +31,13 @@ import type { NewAgentPolicy, AgentPolicy } from '../../../../types';
import { useCapabilities, useStartServices, sendCreateAgentPolicy } from '../../../../hooks';
import { AgentPolicyForm, agentPolicyFormValidation } from '../../components';
-const FlyoutWithHigherZIndex = styled(EuiFlyout)`
+// TODO: EUI team follow up on complex types and styled-components `styled`
+// https://github.com/elastic/eui/issues/4855
+const FlyoutWithHigherZIndex: StyledComponent<
+ typeof EuiFlyout,
+ {},
+ { children?: ReactNode }
+> = styled(EuiFlyout)`
z-index: ${(props) => props.theme.eui.euiZLevel5};
`;
@@ -39,6 +47,7 @@ interface Props extends EuiFlyoutProps {
export const CreateAgentPolicyFlyout: React.FunctionComponent = ({
onClose,
+ as,
...restOfProps
}) => {
const { notifications } = useStartServices();
diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.tsx.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.tsx.snap
index 6254a6512efb5..9595009347259 100644
--- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.tsx.snap
+++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.tsx.snap
@@ -314,89 +314,99 @@ exports[`extend index management ilm summary extension should return extension w
- illegal_argument_exception
- :
- setting [index.lifecycle.rollover_alias] for index [testy3] is empty or not defined
-
-
-
+ illegal_argument_exception
+ :
+ setting [index.lifecycle.rollover_alias] for index [testy3] is empty or not defined
+
-
-
- }
- closePopover={[Function]}
- display="inlineBlock"
- hasArrow={true}
- id="stackPopover"
- isOpen={false}
- ownFocus={true}
- panelPaddingSize="m"
- >
-
-
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ id="stackPopover"
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
>
-
-
-
-
-
-
- Stack trace
-
-
-
-
-
-
-
+
+
+ Stack trace
+
+
+
+
+
+
+
+
+
-
+
diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap
index 556ac35d0565e..4d2b47c8a6039 100644
--- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap
+++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap
@@ -58,16 +58,16 @@ exports[`policy table should show empty state when there are not any policies 1`
data-euiicon-type="managementApp"
/>
+
+ Create your first index lifecycle policy
+
-
- Create your first index lifecycle policy
-
@@ -82,9 +82,6 @@ exports[`policy table should show empty state when there are not any policies 1`
-
{
+ await new Promise((r) => setTimeout(r, 0));
+ });
+ wrapper.update();
+}
+
describe('DatatableComponent', () => {
let onDispatchEvent: jest.Mock;
@@ -149,7 +157,7 @@ describe('DatatableComponent', () => {
).toMatchSnapshot();
});
- test('it invokes executeTriggerActions with correct context on click on top value', () => {
+ test('it invokes executeTriggerActions with correct context on click on top value', async () => {
const { args, data } = sampleArgs();
const wrapper = mountWithIntl(
@@ -173,6 +181,8 @@ describe('DatatableComponent', () => {
wrapper.find('[data-test-subj="dataGridRowCell"]').first().simulate('focus');
+ await waitForWrapperUpdate(wrapper);
+
wrapper.find('[data-test-subj="lensDatatableFilterOut"]').first().simulate('click');
expect(onDispatchEvent).toHaveBeenCalledWith({
@@ -192,7 +202,7 @@ describe('DatatableComponent', () => {
});
});
- test('it invokes executeTriggerActions with correct context on click on timefield', () => {
+ test('it invokes executeTriggerActions with correct context on click on timefield', async () => {
const { args, data } = sampleArgs();
const wrapper = mountWithIntl(
@@ -216,6 +226,8 @@ describe('DatatableComponent', () => {
wrapper.find('[data-test-subj="dataGridRowCell"]').at(1).simulate('focus');
+ await waitForWrapperUpdate(wrapper);
+
wrapper.find('[data-test-subj="lensDatatableFilterFor"]').first().simulate('click');
expect(onDispatchEvent).toHaveBeenCalledWith({
@@ -235,7 +247,7 @@ describe('DatatableComponent', () => {
});
});
- test('it invokes executeTriggerActions with correct context on click on timefield from range', () => {
+ test('it invokes executeTriggerActions with correct context on click on timefield from range', async () => {
const data: LensMultiTable = {
type: 'lens_multitable',
tables: {
@@ -298,6 +310,8 @@ describe('DatatableComponent', () => {
wrapper.find('[data-test-subj="dataGridRowCell"]').at(0).simulate('focus');
+ await waitForWrapperUpdate(wrapper);
+
wrapper.find('[data-test-subj="lensDatatableFilterFor"]').first().simulate('click');
expect(onDispatchEvent).toHaveBeenCalledWith({
diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap
index 29ec3ddbfdc02..45e7055f4db2b 100644
--- a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap
+++ b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap
@@ -1417,19 +1417,29 @@ exports[`UploadLicense should display an error when ES says license is expired 1
-
-
+
- The supplied license has expired.
-
-
+
+
+ The supplied license has expired.
+
+
+
+
@@ -2149,19 +2159,29 @@ exports[`UploadLicense should display an error when ES says license is invalid 1
-
-
+
- The supplied license is not valid for this product.
-
-
+
+
+ The supplied license is not valid for this product.
+
+
+
+
@@ -2881,19 +2901,29 @@ exports[`UploadLicense should display an error when submitting invalid JSON 1`]
-
-
+
- Error encountered uploading license: Check your license file.
-
-
+
+
+ Error encountered uploading license: Check your license file.
+
+
+
+
@@ -3613,19 +3643,29 @@ exports[`UploadLicense should display error when ES returns error 1`] = `
-
-
+
- Error encountered uploading license: Can not upgrade to a production license unless TLS is configured or security is disabled
-
-
+
+
+ Error encountered uploading license: Can not upgrade to a production license unless TLS is configured or security is disabled
+
+
+
+
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_action_flyout.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_action_flyout.tsx
index d24ec2126aee8..766f1bda64d5e 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_action_flyout.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_action_flyout.tsx
@@ -22,7 +22,6 @@ import {
EuiFlyoutHeader,
EuiForm,
EuiFormRow,
- EuiOverlayMask,
EuiSelect,
EuiTitle,
} from '@elastic/eui';
@@ -129,188 +128,180 @@ export const EditActionFlyout: FC> = ({ closeFlyout, item }
};
return (
-
-
-
-
-
- {i18n.translate('xpack.ml.dataframe.analyticsList.editFlyoutTitle', {
- defaultMessage: 'Edit {jobId}',
- values: {
- jobId,
- },
- })}
-
-
-
-
-
-
+
+
+
+ {i18n.translate('xpack.ml.dataframe.analyticsList.editFlyoutTitle', {
+ defaultMessage: 'Edit {jobId}',
+ values: {
+ jobId,
+ },
+ })}
+
+
+
+
+
+
+
- ) =>
- setAllowLazyStart(e.target.value)
- }
- />
-
- ) =>
+ setAllowLazyStart(e.target.value)
+ }
+ />
+
+
+ setDescription(e.target.value)}
+ aria-label={i18n.translate(
+ 'xpack.ml.dataframe.analyticsList.editFlyout.descriptionAriaLabel',
+ {
+ defaultMessage: 'Update the job description.',
}
)}
- >
- setDescription(e.target.value)}
- aria-label={i18n.translate(
- 'xpack.ml.dataframe.analyticsList.editFlyout.descriptionAriaLabel',
- {
- defaultMessage: 'Update the job description.',
- }
- )}
- />
-
-
+
+
+ setModelMemoryLimit(e.target.value)}
+ aria-label={i18n.translate(
+ 'xpack.ml.dataframe.analyticsList.editFlyout.modelMemoryLimitAriaLabel',
{
- defaultMessage: 'Model memory limit',
+ defaultMessage: 'Update the model memory limit.',
}
)}
- isInvalid={mmlValidationError !== undefined}
- error={mmlValidationError}
- >
- setModelMemoryLimit(e.target.value)}
- aria-label={i18n.translate(
- 'xpack.ml.dataframe.analyticsList.editFlyout.modelMemoryLimitAriaLabel',
- {
- defaultMessage: 'Update the model memory limit.',
- }
- )}
- />
-
-
+
+
+
+ setMaxNumThreads(e.target.value === '' ? undefined : +e.target.value)
}
+ step={1}
+ min={1}
+ readOnly={state !== DATA_FRAME_TASK_STATE.STOPPED}
+ value={maxNumThreads}
+ />
+
+
+
+
+
+
+
+ {i18n.translate('xpack.ml.dataframe.analyticsList.editFlyoutCancelButtonText', {
+ defaultMessage: 'Cancel',
+ })}
+
+
+
+
-
- setMaxNumThreads(e.target.value === '' ? undefined : +e.target.value)
- }
- step={1}
- min={1}
- readOnly={state !== DATA_FRAME_TASK_STATE.STOPPED}
- value={maxNumThreads}
- />
-
-
-
-
-
-
-
- {i18n.translate('xpack.ml.dataframe.analyticsList.editFlyoutCancelButtonText', {
- defaultMessage: 'Cancel',
- })}
-
-
-
-
- {i18n.translate('xpack.ml.dataframe.analyticsList.editFlyoutUpdateButtonText', {
- defaultMessage: 'Update',
- })}
-
-
-
-
-
-
+ {i18n.translate('xpack.ml.dataframe.analyticsList.editFlyoutUpdateButtonText', {
+ defaultMessage: 'Update',
+ })}
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/checker_errors.test.js.snap b/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/checker_errors.test.js.snap
index 5018bad317708..e3fa9da6639b3 100644
--- a/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/checker_errors.test.js.snap
+++ b/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/checker_errors.test.js.snap
@@ -22,33 +22,37 @@ Array [
-
- There were some errors encountered in trying to check Elasticsearch settings. You need administrator rights to check the settings and, if needed, to enable the monitoring collection setting.
-
-
-
+ There were some errors encountered in trying to check Elasticsearch settings. You need administrator rights to check the settings and, if needed, to enable the monitoring collection setting.
+
+
- 403 Forbidden
-
-
- no access for you
-
-
- 500 Internal Server Error
-
-
- An internal server error occurred
-
-
+
+ 403 Forbidden
+
+
+ no access for you
+
+
+ 500 Internal Server Error
+
+
+ An internal server error occurred
+
+
+
,
]
diff --git a/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/no_data.test.js.snap b/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/no_data.test.js.snap
index fe277062bc95a..34a4c049dddcc 100644
--- a/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/no_data.test.js.snap
+++ b/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/no_data.test.js.snap
@@ -9,7 +9,7 @@ exports[`NoData should show a default message if reason is unknown 1`] = `
>
No monitoring data found.
-
@@ -87,7 +87,7 @@ exports[`NoData should show a default message if reason is unknown 1`] = `
-
+
`;
@@ -100,7 +100,7 @@ exports[`NoData should show text next to the spinner while checking a setting 1`
>
No monitoring data found.
-
@@ -178,6 +178,6 @@ exports[`NoData should show text next to the spinner while checking a setting 1`
-
+
`;
diff --git a/x-pack/plugins/monitoring/public/components/page_loading/__snapshots__/page_loading.test.js.snap b/x-pack/plugins/monitoring/public/components/page_loading/__snapshots__/page_loading.test.js.snap
index 7f38a92beae8f..7b04e6410d996 100644
--- a/x-pack/plugins/monitoring/public/components/page_loading/__snapshots__/page_loading.test.js.snap
+++ b/x-pack/plugins/monitoring/public/components/page_loading/__snapshots__/page_loading.test.js.snap
@@ -5,7 +5,7 @@ exports[`PageLoading should show a simple page loading component 1`] = `
class="euiPage euiPage--paddingMedium euiPage--grow"
style="height:calc(100vh - 50px)"
>
-
-
+
`;
diff --git a/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx b/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx
index c358067123747..f92f12c79a56d 100644
--- a/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx
+++ b/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx
@@ -49,7 +49,7 @@ describe('CreateCaseFlyout', () => {
);
- wrapper.find('.euiFlyout__closeButton').first().simulate('click');
+ wrapper.find(`[data-test-subj='euiFlyoutCloseButton']`).first().simulate('click');
expect(onCloseFlyout).toBeCalled();
});
});
diff --git a/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx b/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx
index df29d02e8d830..b6cdcf3111672 100644
--- a/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx
+++ b/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx
@@ -5,8 +5,8 @@
* 2.0.
*/
-import React, { memo } from 'react';
-import styled from 'styled-components';
+import React, { memo, ReactNode } from 'react';
+import styled, { StyledComponent } from 'styled-components';
import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui';
import * as i18n from '../translations';
@@ -20,7 +20,11 @@ export interface CreateCaseModalProps {
onSuccess: (theCase: Case) => Promise;
}
-const StyledFlyout = styled(EuiFlyout)`
+// TODO: EUI team follow up on complex types and styled-components `styled`
+// https://github.com/elastic/eui/issues/4855
+const StyledFlyout: StyledComponent = styled(
+ EuiFlyout
+)`
${({ theme }) => `
z-index: ${theme.eui.euiZModal};
`}
diff --git a/x-pack/plugins/observability/public/components/shared/page_template/page_template.tsx b/x-pack/plugins/observability/public/components/shared/page_template/page_template.tsx
index 5a7ce3502ce84..896aca79114d7 100644
--- a/x-pack/plugins/observability/public/components/shared/page_template/page_template.tsx
+++ b/x-pack/plugins/observability/public/components/shared/page_template/page_template.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiSideNavItemType, ExclusiveUnion } from '@elastic/eui';
+import { EuiSideNavItemType } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
import { matchPath, useLocation } from 'react-router-dom';
@@ -28,13 +28,9 @@ export type WrappedPageTemplateProps = Pick<
| 'pageContentProps'
| 'pageHeader'
| 'restrictWidth'
+ | 'template'
| 'isEmptyState'
-> &
- // recreate the exclusivity of bottomBar-related props
- ExclusiveUnion<
- { template?: 'default' } & Pick,
- { template: KibanaPageTemplateProps['template'] }
- >;
+>;
export interface ObservabilityPageTemplateDependencies {
currentAppId$: Observable;
diff --git a/x-pack/plugins/reporting/public/components/__snapshots__/screen_capture_panel_content.test.tsx.snap b/x-pack/plugins/reporting/public/components/__snapshots__/screen_capture_panel_content.test.tsx.snap
index a6753211fba3b..01a8be98bc4be 100644
--- a/x-pack/plugins/reporting/public/components/__snapshots__/screen_capture_panel_content.test.tsx.snap
+++ b/x-pack/plugins/reporting/public/components/__snapshots__/screen_capture_panel_content.test.tsx.snap
@@ -64,7 +64,7 @@ exports[`ScreenCapturePanelContent properly renders a view with "canvas" layout
className="euiFormRow__fieldWrapper"
>
-
-
-
- }
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
- Unable to fetch report info
-
-
-
-
-
-
-
+
-
-
- Could not fetch the job info
-
-
+ Could not fetch the job info
-
+
-
+
-
-
+
+
,
-
-
-
-
-
-
-
-
-
+
@@ -215,6 +122,7 @@ Array [
>
-
-
-
- }
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
- Job Info
-
-
-
-
-
-
-
+ className="euiText euiText--medium"
+ />
+
-
+
-
-
+
+
,
-
-
-
-
-
-
-
-
-
+
@@ -420,6 +235,7 @@ Array [
>
{
/>
{isOpen && (
-
- setIsOpen(false)} size={flyoutSize}>
-
-
-
-
-
-
-
-
-
-
-
- setIsOpen(false)}>
+ setIsOpen(false)}
+ size={flyoutSize}
+ maskProps={{ headerZindexLocation: 'below' }}
+ >
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+ setIsOpen(false)}>
+
+
+
+
)}
);
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx
index b290cb301866d..8f62acd463e6a 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx
@@ -20,7 +20,6 @@ import {
EuiFlyoutHeader,
EuiForm,
EuiFormRow,
- EuiOverlayMask,
EuiSpacer,
EuiText,
EuiTitle,
@@ -93,64 +92,67 @@ export class PrivilegeSpaceForm extends Component
{
public render() {
return (
-
-
-
-
-
-
-
-
-
-
- {this.getForm()}
-
-
- {this.state.privilegeCalculator.hasSupersededInheritedPrivileges(
- this.state.privilegeIndex
- ) && (
-
-
- }
- >
-
-
-
-
- )}
-
-
-
+
+
+
+
+
+
+
+
+
+ {this.getForm()}
+
+
+ {this.state.privilegeCalculator.hasSupersededInheritedPrivileges(
+ this.state.privilegeIndex
+ ) && (
+
+
-
-
- {this.getSaveButton()}
-
-
-
-
+ }
+ >
+
+
+
+
+ )}
+
+
+
+
+
+
+ {this.getSaveButton()}
+
+
+
);
}
diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap b/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap
index eb266ce93338c..f36a1bf477b06 100644
--- a/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap
+++ b/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap
@@ -58,33 +58,33 @@ exports[` renders permission denied if required 1`] = `
/>
+
+
+
+ You need permission to manage roles
+
+
+
-
-
-
- You need permission to manage roles
-
-
-
diff --git a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap
index bcb97538b4f05..2ee2337fc9aeb 100644
--- a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap
+++ b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap
@@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`PromptPage renders as expected with additional scripts 1`] = `"Elastic MockedFonts "`;
+exports[`PromptPage renders as expected with additional scripts 1`] = `"Elastic MockedFonts "`;
-exports[`PromptPage renders as expected without additional scripts 1`] = `"Elastic MockedFonts "`;
+exports[`PromptPage renders as expected without additional scripts 1`] = `"Elastic MockedFonts "`;
diff --git a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap
index 55168401992f7..2e7f3d49e478f 100644
--- a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap
+++ b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`UnauthenticatedPage renders as expected 1`] = `"Elastic MockedFonts
We couldn't log you in
We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.
"`;
+exports[`UnauthenticatedPage renders as expected 1`] = `"Elastic MockedFonts
We couldn't log you in
We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.
"`;
diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap
index 1011d82eb1f73..8d31770cd9385 100644
--- a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap
+++ b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`ResetSessionPage renders as expected 1`] = `"Elastic MockedFonts
You do not have permission to access the requested page
Either go back to the previous page or log in as a different user.
"`;
+exports[`ResetSessionPage renders as expected 1`] = `"Elastic MockedFonts
You do not have permission to access the requested page
Either go back to the previous page or log in as a different user.
"`;
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts
index a0e7e77f89b67..a89ddf3e0b250 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts
@@ -12,7 +12,7 @@ import {
TIMELINE_DATA_PROVIDERS_ACTION_MENU,
IS_DRAGGING_DATA_PROVIDERS,
TIMELINE_FLYOUT_HEADER,
- TIMELINE_BOTTOM_BAR_CONTAINER,
+ TIMELINE_FLYOUT,
} from '../../screens/timeline';
import { HOSTS_NAMES_DRAGGABLE } from '../../screens/hosts/all_hosts';
@@ -47,7 +47,7 @@ describe('timeline data providers', () => {
it('renders the data provider of a host dragged from the All Hosts widget on the hosts page', () => {
dragAndDropFirstHostToTimeline();
openTimelineUsingToggle();
- cy.get(`${TIMELINE_BOTTOM_BAR_CONTAINER} ${TIMELINE_DROPPED_DATA_PROVIDERS}`)
+ cy.get(`${TIMELINE_FLYOUT} ${TIMELINE_DROPPED_DATA_PROVIDERS}`)
.first()
.invoke('text')
.then((dataProviderText) => {
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/pagination.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/pagination.spec.ts
index 8b65f99eb04b8..b569ea7cc082f 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/pagination.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/pagination.spec.ts
@@ -6,13 +6,13 @@
*/
import {
- TIMELINE_BOTTOM_BAR_CONTAINER,
TIMELINE_EVENT,
TIMELINE_EVENTS_COUNT_NEXT_PAGE,
TIMELINE_EVENTS_COUNT_PER_PAGE,
TIMELINE_EVENTS_COUNT_PER_PAGE_BTN,
TIMELINE_EVENTS_COUNT_PER_PAGE_OPTION,
TIMELINE_EVENTS_COUNT_PREV_PAGE,
+ TIMELINE_FLYOUT,
} from '../../screens/timeline';
import { cleanKibana } from '../../tasks/common';
@@ -51,10 +51,10 @@ describe('Pagination', () => {
it('should be able to go to next / previous page', () => {
cy.intercept('POST', '/internal/bsearch').as('refetch');
- cy.get(`${TIMELINE_BOTTOM_BAR_CONTAINER} ${TIMELINE_EVENTS_COUNT_NEXT_PAGE}`).first().click();
+ cy.get(`${TIMELINE_FLYOUT} ${TIMELINE_EVENTS_COUNT_NEXT_PAGE}`).first().click();
cy.wait('@refetch').its('response.statusCode').should('eq', 200);
- cy.get(`${TIMELINE_BOTTOM_BAR_CONTAINER} ${TIMELINE_EVENTS_COUNT_PREV_PAGE}`).first().click();
+ cy.get(`${TIMELINE_FLYOUT} ${TIMELINE_EVENTS_COUNT_PREV_PAGE}`).first().click();
cy.wait('@refetch').its('response.statusCode').should('eq', 200);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts
index 25cd2357fe02b..63c4c1364fcd0 100644
--- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts
@@ -177,9 +177,11 @@ export const TIMELINE_EVENTS_COUNT_PER_PAGE_BTN = '[data-test-subj="local-events
export const TIMELINE_EVENTS_COUNT_PER_PAGE_OPTION = (itemsPerPage: number) =>
`[data-test-subj="items-per-page-option-${itemsPerPage}"]`;
-export const TIMELINE_EVENTS_COUNT_NEXT_PAGE = '[data-test-subj="pagination-button-next"]';
+export const TIMELINE_EVENTS_COUNT_NEXT_PAGE =
+ '[data-test-subj="timeline"] [data-test-subj="pagination-button-next"]';
-export const TIMELINE_EVENTS_COUNT_PREV_PAGE = '[data-test-subj="pagination-button-previous"]';
+export const TIMELINE_EVENTS_COUNT_PREV_PAGE =
+ '[data-test-subj="timeline"] [data-test-subj="pagination-button-previous"]';
export const TIMELINE_FIELDS_BUTTON =
'[data-test-subj="timeline"] [data-test-subj="show-field-browser"]';
@@ -236,7 +238,7 @@ export const TIMELINE_EDIT_MODAL_SAVE_BUTTON = '[data-test-subj="save-button"]';
export const TIMELINE_EXIT_FULL_SCREEN_BUTTON = '[data-test-subj="exit-full-screen"]';
-export const TIMELINE_FLYOUT_WRAPPER = '[data-test-subj="flyout-pane-wrapper"]';
+export const TIMELINE_FLYOUT_WRAPPER = '[data-test-subj="flyout-pane"]';
export const TIMELINE_FULL_SCREEN_BUTTON = '[data-test-subj="full-screen-active"]';
diff --git a/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx
index caa88a8fd1c2a..d22aafa450694 100644
--- a/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx
@@ -50,7 +50,7 @@ describe('CreateCaseFlyout', () => {
);
- wrapper.find('.euiFlyout__closeButton').first().simulate('click');
+ wrapper.find(`[data-test-subj='euiFlyoutCloseButton']`).first().simulate('click');
expect(onCloseFlyout).toBeCalled();
});
});
diff --git a/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx b/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx
index 1023bfc8b0206..f01ae342f8547 100644
--- a/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx
@@ -5,8 +5,8 @@
* 2.0.
*/
-import React, { memo } from 'react';
-import styled from 'styled-components';
+import React, { memo, ReactNode } from 'react';
+import styled, { StyledComponent } from 'styled-components';
import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui';
import * as i18n from '../../translations';
@@ -20,7 +20,11 @@ export interface CreateCaseModalProps {
onSuccess: (theCase: Case) => Promise;
}
-const StyledFlyout = styled(EuiFlyout)`
+// TODO: EUI team follow up on complex types and styled-components `styled`
+// https://github.com/elastic/eui/issues/4855
+const StyledFlyout: StyledComponent = styled(
+ EuiFlyout
+)`
${({ theme }) => `
z-index: ${theme.eui.euiZModal};
`}
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_comments.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_comments.tsx
index c13a1b011ccbd..87f7f5fe2f507 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_comments.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_comments.tsx
@@ -106,7 +106,7 @@ export const AddExceptionComments = memo(function AddExceptionComments({
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 48d3cfb5abcc1..ef00bef841305 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
@@ -177,7 +177,7 @@ describe('useSecuritySolutionNavigation', () => {
useSecuritySolutionNavigation()
);
- const caseNavItem = result.current?.items[0].items?.find(
+ const caseNavItem = (result.current?.items || [])[0].items?.find(
(item) => item['data-test-subj'] === 'navigation-case'
);
expect(caseNavItem).toMatchInlineSnapshot(`
@@ -204,7 +204,7 @@ describe('useSecuritySolutionNavigation', () => {
useSecuritySolutionNavigation()
);
- const caseNavItem = result.current?.items[0].items?.find(
+ const caseNavItem = (result.current?.items || [])[0].items?.find(
(item) => item['data-test-subj'] === 'navigation-case'
);
expect(caseNavItem).toBeFalsy();
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx
index e295ea145edcb..edfa410ee5237 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx
@@ -154,6 +154,7 @@ export const EndpointDetailsFlyout = memo(() => {
data-test-subj="endpointDetailsFlyout"
size="m"
paddingSize="l"
+ ownFocus={false}
>
{(show === 'policy_response' || show === 'isolate' || show === 'unisolate') && (
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap
index 51b60c8ff292b..7b3ae2e2b3b27 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap
@@ -34,15 +34,8 @@ exports[`TrustedAppsGrid renders correctly initially 1`] = `
-
-
- No items found
-
-
+
+ No items found
diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx
index ec46985450d89..ad1d126e3c853 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx
@@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n';
import { EuiFocusTrap, EuiOutsideClickDetector } from '@elastic/eui';
import React, { useEffect, useMemo, useCallback, useState, useRef } from 'react';
import { useDispatch } from 'react-redux';
-import styled from 'styled-components';
import { AppLeaveHandler } from '../../../../../../../src/core/public';
import { TimelineId, TimelineStatus, TimelineTabs } from '../../../../common/types/timeline';
@@ -19,12 +18,6 @@ import { FlyoutBottomBar } from './bottom_bar';
import { Pane } from './pane';
import { getTimelineShowStatusByIdSelector } from './selectors';
-const Visible = styled.div<{ show?: boolean }>`
- visibility: ${({ show }) => (show ? 'visible' : 'hidden')};
-`;
-
-Visible.displayName = 'Visible';
-
interface OwnProps {
timelineId: TimelineId;
onAppLeave: (handler: AppLeaveHandler) => void;
@@ -124,9 +117,7 @@ const FlyoutComponent: React.FC
= ({ timelineId, onAppLeave }) => {
<>
-
-
-
+
>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx
index 459706de36569..35a13aba471fd 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx
@@ -20,6 +20,7 @@ import { focusActiveTimelineButton } from '../../timeline/helpers';
interface FlyoutPaneComponentProps {
timelineId: TimelineId;
+ visible?: boolean;
}
const EuiFlyoutContainer = styled.div`
@@ -31,7 +32,10 @@ const EuiFlyoutContainer = styled.div`
}
`;
-const FlyoutPaneComponent: React.FC = ({ timelineId }) => {
+const FlyoutPaneComponent: React.FC = ({
+ timelineId,
+ visible = true,
+}) => {
const dispatch = useDispatch();
const handleClose = useCallback(() => {
dispatch(timelineActions.showTimeline({ id: timelineId, show: false }));
@@ -39,7 +43,10 @@ const FlyoutPaneComponent: React.FC = ({ timelineId })
}, [dispatch, timelineId]);
return (
-
+
= ({ timelineId })
hideCloseButton={true}
onClose={handleClose}
size="l"
+ ownFocus={false}
+ style={{ visibility: visible ? 'visible' : 'hidden' }}
>
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ className="euiLoadingContent__singleLineBackground"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
,
.c1 {
@@ -554,249 +509,204 @@ Array [
onClose={[Function]}
size="m"
>
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ className="euiLoadingContent__singleLineBackground"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
,
- .c1 {
+ .c0 {
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
margin-top: 8px;
}
-.c2 .euiFlyoutBody__overflow {
+.c1 .euiFlyoutBody__overflow {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@@ -807,7 +717,7 @@ Array [
overflow: hidden;
}
-.c2 .euiFlyoutBody__overflow .euiFlyoutBody__overflowContent {
+.c1 .euiFlyoutBody__overflow .euiFlyoutBody__overflowContent {
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
@@ -815,54 +725,16 @@ Array [
padding: 4px 16px 50px;
}
-.c0 {
- z-index: 7000;
-}
-
-
-
-
-
-
-
-
-
-
+
= styled(
+ EuiFlyout
+)`
z-index: ${({ theme }) => theme.eui.euiZLevel7};
`;
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx
index 2f956db07f2ac..faa304678c0fa 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx
@@ -19,7 +19,6 @@ import {
EuiFlyoutBody,
EuiFlyoutFooter,
EuiFlyoutHeader,
- EuiOverlayMask,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
@@ -78,70 +77,68 @@ export const EditTransformFlyout: FC
= ({ closeFlyout,
const isUpdateButtonDisabled = !state.isFormValid || !state.isFormTouched;
return (
-
-
-
-
-
-
-
- }>
-
- {errorMessage !== undefined && (
- <>
-
-
- {errorMessage}
-
- >
- )}
-
-
-
-
-
- {i18n.translate('xpack.transform.transformList.editFlyoutCancelButtonText', {
- defaultMessage: 'Cancel',
- })}
-
-
-
-
- {i18n.translate('xpack.transform.transformList.editFlyoutUpdateButtonText', {
- defaultMessage: 'Update',
- })}
-
-
-
-
-
-
+
+
+
+
+ {i18n.translate('xpack.transform.transformList.editFlyoutUpdateButtonText', {
+ defaultMessage: 'Update',
+ })}
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx
index 2d111d5405230..26f11fa3326ab 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx
@@ -175,6 +175,7 @@ const AlertAdd = ({
aria-labelledby="flyoutAlertAddTitle"
size="m"
maxWidth={620}
+ ownFocus={false}
>
diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/license_info.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/license_info.test.tsx.snap
index b1d2e53997e36..98414f82bf197 100644
--- a/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/license_info.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/license_info.test.tsx.snap
@@ -24,24 +24,28 @@ Array [
,
+
-
-
-
+ Enable anomaly detection
+
+ class="euiSpacer euiSpacer--s"
+ />
+
+
-
+
+ Start free 14-day trial
+
+
+
In order to access duration anomaly detection, you have to be subscribed to an Elastic Platinum license.
@@ -165,85 +156,85 @@ exports[`ML Flyout component shows license info if no ml available 1`] = `
-
-
-
- Here you can create a machine learning job to calculate anomaly scores on
+
+
+
+
+ Here you can create a machine learning job to calculate anomaly scores on
response durations for Uptime Monitor. Once enabled, the monitor duration chart on the details page
will show the expected bounds and annotate the graph with anomalies. You can also potentially
identify periods of increased latency across geographical regions.
-
-
- Once a job is created, you can manage it and see more details in the
-
- Machine Learning jobs management page
-
- .
-
-
-
- Note: It might take a few minutes for the job to begin calculating results.
-
-
-
-
+
+
+ Once a job is created, you can manage it and see more details in the
+
+ Machine Learning jobs management page
+
+ .
+
+
+
+ Note: It might take a few minutes for the job to begin calculating results.
+
+
+
+
+
diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/__snapshots__/expanded_row.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ping_list/__snapshots__/expanded_row.test.tsx.snap
index 377a2c9389bbd..51753d2ce8bb3 100644
--- a/x-pack/plugins/uptime/public/components/monitor/ping_list/__snapshots__/expanded_row.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/__snapshots__/expanded_row.test.tsx.snap
@@ -145,51 +145,55 @@ exports[`PingListExpandedRow renders link to docs if body is not recorded but it
-
-
- Response Body
-
-
-
- Body size is 1MB.
-
-
-
+
+
+
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_flyout.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_flyout.tsx
index 53e6583e00cc2..2242e692b4222 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_flyout.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_flyout.tsx
@@ -5,9 +5,9 @@
* 2.0.
*/
-import React, { useEffect, useRef } from 'react';
+import React, { useEffect, useRef, ReactNode } from 'react';
-import styled from 'styled-components';
+import styled, { StyledComponent } from 'styled-components';
import {
EuiFlyout,
@@ -49,7 +49,11 @@ export const RESPONSE_HEADERS = i18n.translate(
}
);
-const FlyoutContainer = styled(EuiFlyout)`
+// TODO: EUI team follow up on complex types and styled-components `styled`
+// https://github.com/elastic/eui/issues/4855
+const FlyoutContainer: StyledComponent = styled(
+ EuiFlyout
+)`
z-index: ${(props) => props.theme.eui.euiZLevel5};
`;
diff --git a/x-pack/test/functional/apps/lens/lens_tagging.ts b/x-pack/test/functional/apps/lens/lens_tagging.ts
index b659515a6031c..cbe04b26830d6 100644
--- a/x-pack/test/functional/apps/lens/lens_tagging.ts
+++ b/x-pack/test/functional/apps/lens/lens_tagging.ts
@@ -107,7 +107,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
`tag-searchbar-option-${PageObjects.tagManagement.testSubjFriendly(lensTag)}`
);
// click elsewhere to close the filter dropdown
- const searchFilter = await find.byCssSelector('main .euiFieldSearch');
+ const searchFilter = await find.byCssSelector('.euiPageBody .euiFieldSearch');
await searchFilter.click();
// wait until the table refreshes
await listingTable.waitUntilTableIsLoaded();
diff --git a/x-pack/test/functional/page_objects/graph_page.ts b/x-pack/test/functional/page_objects/graph_page.ts
index bd9e5100e0c57..b0389510e5ef5 100644
--- a/x-pack/test/functional/page_objects/graph_page.ts
+++ b/x-pack/test/functional/page_objects/graph_page.ts
@@ -201,7 +201,9 @@ export class GraphPageObject extends FtrService {
}
async getSearchFilter() {
- const searchFilter = await this.find.allByCssSelector('main .euiFieldSearch');
+ const searchFilter = await this.find.allByCssSelector(
+ '[data-test-subj="graphLandingPage"] .euiFieldSearch'
+ );
return searchFilter[0];
}
diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts
index 9953ab3dfcead..0fc85f78ac90b 100644
--- a/x-pack/test/functional/page_objects/lens_page.ts
+++ b/x-pack/test/functional/page_objects/lens_page.ts
@@ -500,7 +500,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
await comboBox.setElement(formatInput, format);
},
async editDimensionColor(color: string) {
- const colorPickerInput = await testSubjects.find('colorPickerAnchor');
+ const colorPickerInput = await testSubjects.find('~indexPattern-dimension-colorPicker');
await colorPickerInput.type(color);
await PageObjects.common.sleep(1000); // give time for debounced components to rerender
},
@@ -873,7 +873,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
},
async assertColor(color: string) {
// TODO: target dimensionTrigger color element after merging https://github.com/elastic/kibana/pull/76871
- await testSubjects.getAttribute('colorPickerAnchor', color);
+ await testSubjects.getAttribute('~indexPattern-dimension-colorPicker', color);
},
/**
diff --git a/x-pack/test/functional/page_objects/space_selector_page.ts b/x-pack/test/functional/page_objects/space_selector_page.ts
index 9e05ae1f2c42b..313916fd3b07e 100644
--- a/x-pack/test/functional/page_objects/space_selector_page.ts
+++ b/x-pack/test/functional/page_objects/space_selector_page.ts
@@ -79,11 +79,11 @@ export class SpaceSelectorPageObject extends FtrService {
}
async clickColorPicker() {
- await this.testSubjects.click('colorPickerAnchor');
+ await this.testSubjects.click('euiColorPickerAnchor');
}
async setColorinPicker(hexValue: string) {
- await this.testSubjects.setValue('colorPickerAnchor', hexValue);
+ await this.testSubjects.setValue('euiColorPickerAnchor', hexValue);
}
async clickShowFeatures() {
diff --git a/x-pack/test/functional/page_objects/tag_management_page.ts b/x-pack/test/functional/page_objects/tag_management_page.ts
index b3f6622b04a57..253f91ff0496d 100644
--- a/x-pack/test/functional/page_objects/tag_management_page.ts
+++ b/x-pack/test/functional/page_objects/tag_management_page.ts
@@ -56,8 +56,7 @@ class TagModal extends FtrService {
await this.testSubjects.setValue('createModalField-name', fields.name);
}
if (fields.color !== undefined) {
- // EuiColorPicker does not allow to specify data-test-subj for the colorpicker input
- await this.testSubjects.setValue('colorPickerAnchor', fields.color);
+ await this.testSubjects.setValue('~createModalField-color', fields.color);
}
if (fields.description !== undefined) {
await this.testSubjects.click('createModalField-description');
@@ -75,7 +74,7 @@ class TagModal extends FtrService {
async getFormValues(): Promise> {
return {
name: await this.testSubjects.getAttribute('createModalField-name', 'value'),
- color: await this.testSubjects.getAttribute('colorPickerAnchor', 'value'),
+ color: await this.testSubjects.getAttribute('~createModalField-color', 'value'),
description: await this.testSubjects.getAttribute('createModalField-description', 'value'),
};
}
diff --git a/x-pack/test/saved_object_tagging/functional/tests/dashboard_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/dashboard_integration.ts
index ffaa595c16bec..1bd95ca9f16e4 100644
--- a/x-pack/test/saved_object_tagging/functional/tests/dashboard_integration.ts
+++ b/x-pack/test/saved_object_tagging/functional/tests/dashboard_integration.ts
@@ -30,7 +30,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
);
}
// click elsewhere to close the filter dropdown
- const searchFilter = await find.byCssSelector('main .euiFieldSearch');
+ const searchFilter = await find.byCssSelector('.euiPageBody .euiFieldSearch');
await searchFilter.click();
// wait until the table refreshes
await listingTable.waitUntilTableIsLoaded();
diff --git a/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts
index 632610fee0f53..6494eba66a437 100644
--- a/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts
+++ b/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts
@@ -30,7 +30,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
);
}
// click elsewhere to close the filter dropdown
- const searchFilter = await find.byCssSelector('main .euiFieldSearch');
+ const searchFilter = await find.byCssSelector('.euiPageBody .euiFieldSearch');
await searchFilter.click();
};
diff --git a/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts
index 9789b4146c05d..4bd95cd9a7f42 100644
--- a/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts
+++ b/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts
@@ -30,7 +30,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
);
}
// click elsewhere to close the filter dropdown
- const searchFilter = await find.byCssSelector('main .euiFieldSearch');
+ const searchFilter = await find.byCssSelector('.euiPageBody .euiFieldSearch');
await searchFilter.click();
// wait until the table refreshes
await listingTable.waitUntilTableIsLoaded();
diff --git a/yarn.lock b/yarn.lock
index 7b0cd4dfe67ac..64e01ac0475d5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1436,10 +1436,10 @@
resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314"
integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ==
-"@elastic/eui@33.0.0":
- version "33.0.0"
- resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-33.0.0.tgz#525cf5ea6e6ab16c32bb09766e9c23806640240f"
- integrity sha512-BQ6GJxKVaOF7Fm+IvH2iFeeGnepzygDr4xmmZ8PH2BEfrtkxzGP9YDpqmr/Drg5beVynJ3+72k1AEQT/JE6NTw==
+"@elastic/eui@34.3.0":
+ version "34.3.0"
+ resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-34.3.0.tgz#5cd7d66c86d9644991c4391682f84824126361df"
+ integrity sha512-cUFh5H7Y95lzbsyRP1yL88mVVe5MAODOhV+O+8jYmuDQH6WrEiDhtSljTKfVYssbP7Bv+qFraVwyUj623dfr2w==
dependencies:
"@types/chroma-js" "^2.0.0"
"@types/lodash" "^4.14.160"
@@ -1454,6 +1454,7 @@
chroma-js "^2.1.0"
classnames "^2.2.6"
lodash "^4.17.21"
+ mdast-util-to-hast "^10.0.0"
numeral "^2.0.6"
prop-types "^15.6.0"
react-ace "^7.0.5"
@@ -19027,7 +19028,7 @@ mdast-util-from-markdown@^0.8.0:
parse-entities "^2.0.0"
unist-util-stringify-position "^2.0.0"
-mdast-util-to-hast@10.0.1:
+mdast-util-to-hast@10.0.1, mdast-util-to-hast@^10.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb"
integrity sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA==
@@ -19041,20 +19042,6 @@ mdast-util-to-hast@10.0.1:
unist-util-position "^3.0.0"
unist-util-visit "^2.0.0"
-mdast-util-to-hast@^10.0.0:
- version "10.0.0"
- resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.0.tgz#744dfe7907bac0263398a68af5aba16d104a9a08"
- integrity sha512-dRyAC5S4eDcIOdkz4jg0wXbUdlf+5YFu7KppJNHOsMaD7ql5bKIqVcvXYYkcrKjzUkfX8JsKFVMthsU8OWxQ+w==
- dependencies:
- "@types/mdast" "^3.0.0"
- "@types/unist" "^2.0.0"
- mdast-util-definitions "^4.0.0"
- mdurl "^1.0.0"
- unist-builder "^2.0.0"
- unist-util-generated "^1.0.0"
- unist-util-position "^3.0.0"
- unist-util-visit "^2.0.0"
-
mdast-util-to-markdown@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.1.tgz#0e07d3f871e056bffc38a0cf50c7298b56d9e0d6"
From 1e7ef987bebc611fe4b8bc7bd8da194e924f75a0 Mon Sep 17 00:00:00 2001
From: Nathan Reese
Date: Wed, 23 Jun 2021 14:24:24 -0600
Subject: [PATCH 03/86] [Maps] fix save to maps for by_value map embeddables
(#102968)
* [Maps] fix save to maps for by_value map embeddables
* show the save as dialog with the option to update Panel on dashboard
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../routes/map_page/saved_map/saved_map.ts | 4 ++++
.../public/routes/map_page/top_nav_config.tsx | 16 +++++++++++++---
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts
index f7e0012fdd9c2..45d3e0352acf6 100644
--- a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts
+++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts
@@ -241,6 +241,10 @@ export class SavedMap {
return this._originatingApp;
}
+ public getOriginatingAppName(): string | undefined {
+ return this._originatingApp ? this.getAppNameFromId(this._originatingApp) : undefined;
+ }
+
public getAppNameFromId = (appId: string): string | undefined => {
return this._getStateTransfer().getAppNameFromId(appId);
};
diff --git a/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx
index 7ac8c3070eb9d..79bc820d67b46 100644
--- a/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx
+++ b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx
@@ -151,9 +151,8 @@ export function getTopNavConfig({
const saveModalProps = {
onSave: async (
props: OnSaveProps & {
- returnToOrigin?: boolean;
dashboardId?: string | null;
- addToLibrary?: boolean;
+ addToLibrary: boolean;
}
) => {
try {
@@ -181,7 +180,7 @@ export function getTopNavConfig({
await savedMap.save({
...props,
newTags: selectedTags,
- saveByReference: Boolean(props.addToLibrary),
+ saveByReference: props.addToLibrary,
});
// showSaveModal wrapper requires onSave to return an object with an id to close the modal after successful save
return { id: 'id' };
@@ -204,8 +203,19 @@ export function getTopNavConfig({
saveModal = (
{
+ return saveModalProps.onSave({ ...props, addToLibrary: true });
+ }}
originatingApp={savedMap.getOriginatingApp()}
getAppNameFromId={savedMap.getAppNameFromId}
+ returnToOriginSwitchLabel={
+ savedMap.isByValue()
+ ? i18n.translate('xpack.maps.topNav.updatePanel', {
+ defaultMessage: 'Update panel on {originatingAppName}',
+ values: { originatingAppName: savedMap.getOriginatingAppName() },
+ })
+ : undefined
+ }
options={tagSelector}
/>
);
From 1a2284fad68b69c8d9e703326e5e9ac9d454959b Mon Sep 17 00:00:00 2001
From: Liza K
Date: Wed, 23 Jun 2021 23:47:39 +0300
Subject: [PATCH 04/86] fix types
---
src/plugins/data/common/es_query/filters/meta_filter.ts | 4 +---
src/plugins/data/common/es_query/filters/types.ts | 2 --
.../data/public/query/filter_manager/lib/get_display_value.ts | 4 +++-
3 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/src/plugins/data/common/es_query/filters/meta_filter.ts b/src/plugins/data/common/es_query/filters/meta_filter.ts
index b6714b5b8d02a..87455cf1cb763 100644
--- a/src/plugins/data/common/es_query/filters/meta_filter.ts
+++ b/src/plugins/data/common/es_query/filters/meta_filter.ts
@@ -6,8 +6,6 @@
* Side Public License, v 1.
*/
-import { ConvertFn } from './types';
-
export enum FilterStateStore {
APP_STATE = 'appState',
GLOBAL_STATE = 'globalState',
@@ -37,7 +35,7 @@ export type FilterMeta = {
type?: string;
key?: string;
params?: any;
- value?: string | ConvertFn;
+ value?: string;
};
// eslint-disable-next-line
diff --git a/src/plugins/data/common/es_query/filters/types.ts b/src/plugins/data/common/es_query/filters/types.ts
index 364400e6a2009..a007189d81a03 100644
--- a/src/plugins/data/common/es_query/filters/types.ts
+++ b/src/plugins/data/common/es_query/filters/types.ts
@@ -40,5 +40,3 @@ export enum FILTERS {
GEO_POLYGON = 'geo_polygon',
SPATIAL_FILTER = 'spatial_filter',
}
-
-export type ConvertFn = (value: any) => string;
diff --git a/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts b/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts
index 17853d1e93cda..1ccfaacb24e4b 100644
--- a/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts
+++ b/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts
@@ -32,7 +32,9 @@ export function getDisplayValueFromFilter(filter: Filter, indexPatterns: IIndexP
if (typeof value === 'function') {
const indexPattern = getIndexPatternFromFilter(filter, indexPatterns);
const valueFormatter = getValueFormatter(indexPattern, key);
- return value(valueFormatter);
+ // TODO: distinguish between FilterMeta which is serializable to mapped FilterMeta
+ // Where value can be a function.
+ return (value as any)(valueFormatter);
} else {
return value || '';
}
From 0669895137b903a15e86622a64d39f3f5a936355 Mon Sep 17 00:00:00 2001
From: Clint Andrew Hall
Date: Wed, 23 Jun 2021 17:04:19 -0500
Subject: [PATCH 05/86] [canvas] Reduce bundle size by co-locating strings with
components (#103013)
---
x-pack/plugins/canvas/CONTRIBUTING.md | 8 +-
.../component/advanced_filter.tsx | 18 +-
.../component/dropdown_filter.tsx | 14 +-
x-pack/plugins/canvas/i18n/components.ts | 1543 -----------------
x-pack/plugins/canvas/i18n/index.ts | 1 -
.../arg_add_popover/arg_add_popover.tsx | 11 +-
.../components/arg_form/advanced_failure.js | 19 +-
.../components/arg_form/arg_simple_form.tsx | 16 +-
.../components/arg_form/pending_arg_value.js | 10 +-
.../components/arg_form/simple_failure.tsx | 12 +-
.../asset_manager/asset.component.tsx | 38 +-
.../asset_manager/asset_manager.component.tsx | 37 +-
.../components/asset_picker/asset_picker.tsx | 10 +-
.../canvas_loading.component.tsx | 9 +-
.../color_manager/color_manager.tsx | 19 +-
.../custom_element_modal.tsx | 47 +-
.../datasource/datasource_component.js | 20 +-
.../datasource_preview/datasource_preview.js | 29 +-
.../components/datasource/no_datasource.js | 14 +-
.../element_config/element_config.tsx | 37 +-
.../embeddable_flyout/flyout.component.tsx | 15 +-
.../canvas/public/components/error/error.tsx | 15 +-
.../expression/element_not_selected.js | 13 +-
.../components/expression/expression.tsx | 39 +-
.../components/expression_input/reference.ts | 55 +-
.../function_form_context_error.tsx | 13 +-
.../function_form/function_unknown.tsx | 13 +-
.../public/components/help_menu/help_menu.tsx | 11 +-
.../keyboard_shortcuts_doc.tsx | 20 +-
.../public/components/page_config/index.js | 13 +-
.../components/page_config/page_config.js | 29 +-
.../page_manager/page_manager.component.tsx | 23 +-
.../components/page_preview/page_controls.tsx | 22 +-
.../palette_picker/palette_picker.tsx | 14 +-
.../saved_elements_modal/element_controls.tsx | 21 +-
.../saved_elements_modal.component.tsx | 49 +-
.../element_settings.component.tsx | 20 +-
.../components/sidebar/group_settings.tsx | 16 +-
.../sidebar/multi_element_settings.tsx | 18 +-
.../components/sidebar/sidebar_content.js | 28 +-
.../sidebar_header/sidebar_header.tsx | 23 +-
.../text_style_picker/text_style_picker.tsx | 42 +-
.../components/toolbar/toolbar.component.tsx | 25 +-
.../public/components/toolbar/tray/tray.tsx | 9 +-
.../components/var_config/delete_var.tsx | 23 +-
.../public/components/var_config/edit_var.tsx | 55 +-
.../public/components/var_config/index.tsx | 14 +-
.../components/var_config/var_config.tsx | 56 +-
.../components/var_config/var_value_field.tsx | 17 +-
.../workpad_color_picker.component.tsx | 9 +-
.../workpad_config.component.tsx | 62 +-
.../edit_menu/edit_menu.component.tsx | 92 +-
.../element_menu/element_menu.component.tsx | 54 +-
.../labs_control/labs_control.tsx | 10 +-
.../refresh_control.component.tsx | 14 +-
.../share_menu/flyout/flyout.component.tsx | 34 +-
.../share_menu/flyout/flyout.ts | 27 +-
.../share_menu/flyout/runtime_step.tsx | 18 +-
.../share_menu/flyout/snippets_step.tsx | 82 +-
.../share_menu/flyout/workpad_step.tsx | 18 +-
.../share_menu/share_menu.component.tsx | 37 +-
.../workpad_header/share_menu/share_menu.ts | 13 +-
.../view_menu/auto_refresh_controls.tsx | 24 +-
.../view_menu/custom_interval.tsx | 23 +-
.../view_menu/kiosk_controls.tsx | 24 +-
.../view_menu/view_menu.component.tsx | 66 +-
.../workpad_header.component.tsx | 26 +-
.../translations/translations/ja-JP.json | 35 -
.../translations/translations/zh-CN.json | 35 -
69 files changed, 1546 insertions(+), 1780 deletions(-)
delete mode 100644 x-pack/plugins/canvas/i18n/components.ts
diff --git a/x-pack/plugins/canvas/CONTRIBUTING.md b/x-pack/plugins/canvas/CONTRIBUTING.md
index d3bff67771244..d8a657ea73c40 100644
--- a/x-pack/plugins/canvas/CONTRIBUTING.md
+++ b/x-pack/plugins/canvas/CONTRIBUTING.md
@@ -36,8 +36,8 @@ To keep the code terse, Canvas uses i18n "dictionaries": abstracted, static sing
```js
-// i18n/components.ts
-export const ComponentStrings = {
+// asset_manager.tsx
+const strings = {
// ...
AssetManager: {
getCopyAssetMessage: (id: string) =>
@@ -52,10 +52,6 @@ export const ComponentStrings = {
// ...
};
-// asset_manager.tsx
-import { ComponentStrings } from '../../../i18n';
-const { AssetManager: strings } = ComponentStrings;
-
const text = (
{strings.getSpaceUsedText(percentageUsed)}
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/advanced_filter/component/advanced_filter.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/advanced_filter/component/advanced_filter.tsx
index 4dfb4c3f09273..b5c009abc2768 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/advanced_filter/component/advanced_filter.tsx
+++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/advanced_filter/component/advanced_filter.tsx
@@ -5,12 +5,22 @@
* 2.0.
*/
-import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import PropTypes from 'prop-types';
import React, { FunctionComponent } from 'react';
-import { ComponentStrings } from '../../../../../i18n';
+import PropTypes from 'prop-types';
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
-const { AdvancedFilter: strings } = ComponentStrings;
+const strings = {
+ getApplyButtonLabel: () =>
+ i18n.translate('xpack.canvas.renderer.advancedFilter.applyButtonLabel', {
+ defaultMessage: 'Apply',
+ description: 'This refers to applying the filter to the Canvas workpad',
+ }),
+ getInputPlaceholder: () =>
+ i18n.translate('xpack.canvas.renderer.advancedFilter.inputPlaceholder', {
+ defaultMessage: 'Enter filter expression',
+ }),
+};
export interface Props {
/** Optional value for the component */
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/dropdown_filter.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/dropdown_filter.tsx
index 86517c897f02d..43f2e1ecc84f3 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/dropdown_filter.tsx
+++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/dropdown_filter.tsx
@@ -5,12 +5,18 @@
* 2.0.
*/
-import { EuiIcon } from '@elastic/eui';
-import PropTypes from 'prop-types';
import React, { ChangeEvent, FocusEvent, FunctionComponent } from 'react';
-import { ComponentStrings } from '../../../../../i18n';
+import PropTypes from 'prop-types';
+import { EuiIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
-const { DropdownFilter: strings } = ComponentStrings;
+const strings = {
+ getMatchAllOptionLabel: () =>
+ i18n.translate('xpack.canvas.renderer.dropdownFilter.matchAllOptionLabel', {
+ defaultMessage: 'ANY',
+ description: 'The dropdown filter option to match any value in the field.',
+ }),
+};
export interface Props {
/**
diff --git a/x-pack/plugins/canvas/i18n/components.ts b/x-pack/plugins/canvas/i18n/components.ts
deleted file mode 100644
index 6f011bb73e3b0..0000000000000
--- a/x-pack/plugins/canvas/i18n/components.ts
+++ /dev/null
@@ -1,1543 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { i18n } from '@kbn/i18n';
-import { BOLD_MD_TOKEN, CANVAS, HTML, JSON, PDF, URL, ZIP } from './constants';
-
-export const ComponentStrings = {
- AddEmbeddableFlyout: {
- getNoItemsText: () =>
- i18n.translate('xpack.canvas.embedObject.noMatchingObjectsMessage', {
- defaultMessage: 'No matching objects found.',
- }),
- getTitleText: () =>
- i18n.translate('xpack.canvas.embedObject.titleText', {
- defaultMessage: 'Add from Kibana',
- }),
- },
- AdvancedFilter: {
- getApplyButtonLabel: () =>
- i18n.translate('xpack.canvas.renderer.advancedFilter.applyButtonLabel', {
- defaultMessage: 'Apply',
- description: 'This refers to applying the filter to the Canvas workpad',
- }),
- getInputPlaceholder: () =>
- i18n.translate('xpack.canvas.renderer.advancedFilter.inputPlaceholder', {
- defaultMessage: 'Enter filter expression',
- }),
- },
- App: {
- getLoadErrorMessage: (error: string) =>
- i18n.translate('xpack.canvas.app.loadErrorMessage', {
- defaultMessage: 'Message: {error}',
- values: {
- error,
- },
- }),
- getLoadErrorTitle: () =>
- i18n.translate('xpack.canvas.app.loadErrorTitle', {
- defaultMessage: 'Canvas failed to load',
- }),
- getLoadingMessage: () =>
- i18n.translate('xpack.canvas.app.loadingMessage', {
- defaultMessage: 'Canvas is loading',
- }),
- },
- ArgAddPopover: {
- getAddAriaLabel: () =>
- i18n.translate('xpack.canvas.argAddPopover.addAriaLabel', {
- defaultMessage: 'Add argument',
- }),
- },
- ArgFormAdvancedFailure: {
- getApplyButtonLabel: () =>
- i18n.translate('xpack.canvas.argFormAdvancedFailure.applyButtonLabel', {
- defaultMessage: 'Apply',
- }),
- getResetButtonLabel: () =>
- i18n.translate('xpack.canvas.argFormAdvancedFailure.resetButtonLabel', {
- defaultMessage: 'Reset',
- }),
- getRowErrorMessage: () =>
- i18n.translate('xpack.canvas.argFormAdvancedFailure.rowErrorMessage', {
- defaultMessage: 'Invalid Expression',
- }),
- },
- ArgFormArgSimpleForm: {
- getRemoveAriaLabel: () =>
- i18n.translate('xpack.canvas.argFormArgSimpleForm.removeAriaLabel', {
- defaultMessage: 'Remove',
- }),
- getRequiredTooltip: () =>
- i18n.translate('xpack.canvas.argFormArgSimpleForm.requiredTooltip', {
- defaultMessage: 'This argument is required, you should specify a value.',
- }),
- },
- ArgFormPendingArgValue: {
- getLoadingMessage: () =>
- i18n.translate('xpack.canvas.argFormPendingArgValue.loadingMessage', {
- defaultMessage: 'Loading',
- }),
- },
- ArgFormSimpleFailure: {
- getFailureTooltip: () =>
- i18n.translate('xpack.canvas.argFormSimpleFailure.failureTooltip', {
- defaultMessage:
- 'The interface for this argument could not parse the value, so a fallback input is being used',
- }),
- },
- Asset: {
- getCopyAssetTooltip: () =>
- i18n.translate('xpack.canvas.asset.copyAssetTooltip', {
- defaultMessage: 'Copy id to clipboard',
- }),
- getCreateImageTooltip: () =>
- i18n.translate('xpack.canvas.asset.createImageTooltip', {
- defaultMessage: 'Create image element',
- }),
- getDeleteAssetTooltip: () =>
- i18n.translate('xpack.canvas.asset.deleteAssetTooltip', {
- defaultMessage: 'Delete',
- }),
- getDownloadAssetTooltip: () =>
- i18n.translate('xpack.canvas.asset.downloadAssetTooltip', {
- defaultMessage: 'Download',
- }),
- getThumbnailAltText: () =>
- i18n.translate('xpack.canvas.asset.thumbnailAltText', {
- defaultMessage: 'Asset thumbnail',
- }),
- getConfirmModalButtonLabel: () =>
- i18n.translate('xpack.canvas.asset.confirmModalButtonLabel', {
- defaultMessage: 'Remove',
- }),
- getConfirmModalMessageText: () =>
- i18n.translate('xpack.canvas.asset.confirmModalDetail', {
- defaultMessage: 'Are you sure you want to remove this asset?',
- }),
- getConfirmModalTitle: () =>
- i18n.translate('xpack.canvas.asset.confirmModalTitle', {
- defaultMessage: 'Remove Asset',
- }),
- },
- AssetManager: {
- getButtonLabel: () =>
- i18n.translate('xpack.canvas.assetManager.manageButtonLabel', {
- defaultMessage: 'Manage assets',
- }),
- getDescription: () =>
- i18n.translate('xpack.canvas.assetModal.modalDescription', {
- defaultMessage:
- 'Below are the image assets in this workpad. Any assets that are currently in use cannot be determined at this time. To reclaim space, delete assets.',
- }),
- getEmptyAssetsDescription: () =>
- i18n.translate('xpack.canvas.assetModal.emptyAssetsDescription', {
- defaultMessage: 'Import your assets to get started',
- }),
- getFilePickerPromptText: () =>
- i18n.translate('xpack.canvas.assetModal.filePickerPromptText', {
- defaultMessage: 'Select or drag and drop images',
- }),
- getLoadingText: () =>
- i18n.translate('xpack.canvas.assetModal.loadingText', {
- defaultMessage: 'Uploading images',
- }),
- getModalCloseButtonLabel: () =>
- i18n.translate('xpack.canvas.assetModal.modalCloseButtonLabel', {
- defaultMessage: 'Close',
- }),
- getModalTitle: () =>
- i18n.translate('xpack.canvas.assetModal.modalTitle', {
- defaultMessage: 'Manage workpad assets',
- }),
- getSpaceUsedText: (percentageUsed: number) =>
- i18n.translate('xpack.canvas.assetModal.spacedUsedText', {
- defaultMessage: '{percentageUsed}% space used',
- values: {
- percentageUsed,
- },
- }),
- getCopyAssetMessage: (id: string) =>
- i18n.translate('xpack.canvas.assetModal.copyAssetMessage', {
- defaultMessage: `Copied '{id}' to clipboard`,
- values: {
- id,
- },
- }),
- },
- AssetPicker: {
- getAssetAltText: () =>
- i18n.translate('xpack.canvas.assetpicker.assetAltText', {
- defaultMessage: 'Asset thumbnail',
- }),
- },
- CanvasLoading: {
- getLoadingLabel: () =>
- i18n.translate('xpack.canvas.canvasLoading.loadingMessage', {
- defaultMessage: 'Loading',
- }),
- },
- ColorManager: {
- getAddAriaLabel: () =>
- i18n.translate('xpack.canvas.colorManager.addAriaLabel', {
- defaultMessage: 'Add Color',
- }),
- getCodePlaceholder: () =>
- i18n.translate('xpack.canvas.colorManager.codePlaceholder', {
- defaultMessage: 'Color code',
- }),
- getRemoveAriaLabel: () =>
- i18n.translate('xpack.canvas.colorManager.removeAriaLabel', {
- defaultMessage: 'Remove Color',
- }),
- },
- CustomElementModal: {
- getCancelButtonLabel: () =>
- i18n.translate('xpack.canvas.customElementModal.cancelButtonLabel', {
- defaultMessage: 'Cancel',
- }),
- getCharactersRemainingDescription: (numberOfRemainingCharacter: number) =>
- i18n.translate('xpack.canvas.customElementModal.remainingCharactersDescription', {
- defaultMessage: '{numberOfRemainingCharacter} characters remaining',
- values: {
- numberOfRemainingCharacter,
- },
- }),
- getDescriptionInputLabel: () =>
- i18n.translate('xpack.canvas.customElementModal.descriptionInputLabel', {
- defaultMessage: 'Description',
- }),
- getElementPreviewTitle: () =>
- i18n.translate('xpack.canvas.customElementModal.elementPreviewTitle', {
- defaultMessage: 'Element preview',
- }),
- getImageFilePickerPlaceholder: () =>
- i18n.translate('xpack.canvas.customElementModal.imageFilePickerPlaceholder', {
- defaultMessage: 'Select or drag and drop an image',
- }),
- getImageInputDescription: () =>
- i18n.translate('xpack.canvas.customElementModal.imageInputDescription', {
- defaultMessage:
- 'Take a screenshot of your element and upload it here. This can also be done after saving.',
- }),
- getImageInputLabel: () =>
- i18n.translate('xpack.canvas.customElementModal.imageInputLabel', {
- defaultMessage: 'Thumbnail image',
- }),
- getNameInputLabel: () =>
- i18n.translate('xpack.canvas.customElementModal.nameInputLabel', {
- defaultMessage: 'Name',
- }),
- getSaveButtonLabel: () =>
- i18n.translate('xpack.canvas.customElementModal.saveButtonLabel', {
- defaultMessage: 'Save',
- }),
- },
- DatasourceDatasourceComponent: {
- getChangeButtonLabel: () =>
- i18n.translate('xpack.canvas.datasourceDatasourceComponent.changeButtonLabel', {
- defaultMessage: 'Change element data source',
- }),
- getExpressionArgDescription: () =>
- i18n.translate('xpack.canvas.datasourceDatasourceComponent.expressionArgDescription', {
- defaultMessage:
- 'The datasource has an argument controlled by an expression. Use the expression editor to modify the datasource.',
- }),
- getPreviewButtonLabel: () =>
- i18n.translate('xpack.canvas.datasourceDatasourceComponent.previewButtonLabel', {
- defaultMessage: 'Preview data',
- }),
- getSaveButtonLabel: () =>
- i18n.translate('xpack.canvas.datasourceDatasourceComponent.saveButtonLabel', {
- defaultMessage: 'Save',
- }),
- },
- DatasourceDatasourcePreview: {
- getEmptyFirstLineDescription: () =>
- i18n.translate('xpack.canvas.datasourceDatasourcePreview.emptyFirstLineDescription', {
- defaultMessage: "We couldn't find any documents matching your search criteria.",
- }),
- getEmptySecondLineDescription: () =>
- i18n.translate('xpack.canvas.datasourceDatasourcePreview.emptySecondLineDescription', {
- defaultMessage: 'Check your datasource settings and try again.',
- }),
- getEmptyTitle: () =>
- i18n.translate('xpack.canvas.datasourceDatasourcePreview.emptyTitle', {
- defaultMessage: 'No documents found',
- }),
- getModalTitle: () =>
- i18n.translate('xpack.canvas.datasourceDatasourcePreview.modalTitle', {
- defaultMessage: 'Datasource preview',
- }),
- },
- DatasourceNoDatasource: {
- getPanelDescription: () =>
- i18n.translate('xpack.canvas.datasourceNoDatasource.panelDescription', {
- defaultMessage:
- "This element does not have an attached data source. This is usually because the element is an image or other static asset. If that's not the case you might want to check your expression to make sure it is not malformed.",
- }),
- getPanelTitle: () =>
- i18n.translate('xpack.canvas.datasourceNoDatasource.panelTitle', {
- defaultMessage: 'No data source present',
- }),
- },
- DropdownFilter: {
- getMatchAllOptionLabel: () =>
- i18n.translate('xpack.canvas.renderer.dropdownFilter.matchAllOptionLabel', {
- defaultMessage: 'ANY',
- description: 'The dropdown filter option to match any value in the field.',
- }),
- },
- ElementConfig: {
- getFailedLabel: () =>
- i18n.translate('xpack.canvas.elementConfig.failedLabel', {
- defaultMessage: 'Failed',
- description:
- 'The label for the total number of elements in a workpad that have thrown an error or failed to load',
- }),
- getLoadedLabel: () =>
- i18n.translate('xpack.canvas.elementConfig.loadedLabel', {
- defaultMessage: 'Loaded',
- description: 'The label for the number of elements in a workpad that have loaded',
- }),
- getProgressLabel: () =>
- i18n.translate('xpack.canvas.elementConfig.progressLabel', {
- defaultMessage: 'Progress',
- description: 'The label for the percentage of elements that have finished loading',
- }),
- getTitle: () =>
- i18n.translate('xpack.canvas.elementConfig.title', {
- defaultMessage: 'Element status',
- description:
- '"Elements" refers to the individual text, images, or visualizations that you can add to a Canvas workpad',
- }),
- getTotalLabel: () =>
- i18n.translate('xpack.canvas.elementConfig.totalLabel', {
- defaultMessage: 'Total',
- description: 'The label for the total number of elements in a workpad',
- }),
- },
- ElementControls: {
- getDeleteAriaLabel: () =>
- i18n.translate('xpack.canvas.elementControls.deleteAriaLabel', {
- defaultMessage: 'Delete element',
- }),
- getDeleteTooltip: () =>
- i18n.translate('xpack.canvas.elementControls.deleteToolTip', {
- defaultMessage: 'Delete',
- }),
- getEditAriaLabel: () =>
- i18n.translate('xpack.canvas.elementControls.editAriaLabel', {
- defaultMessage: 'Edit element',
- }),
- getEditTooltip: () =>
- i18n.translate('xpack.canvas.elementControls.editToolTip', {
- defaultMessage: 'Edit',
- }),
- },
- ElementSettings: {
- getDataTabLabel: () =>
- i18n.translate('xpack.canvas.elementSettings.dataTabLabel', {
- defaultMessage: 'Data',
- description:
- 'This tab contains the settings for the data (i.e. Elasticsearch query) used as ' +
- 'the source for a Canvas element',
- }),
- getDisplayTabLabel: () =>
- i18n.translate('xpack.canvas.elementSettings.displayTabLabel', {
- defaultMessage: 'Display',
- description: 'This tab contains the settings for how data is displayed in a Canvas element',
- }),
- },
- Error: {
- getDescription: () =>
- i18n.translate('xpack.canvas.errorComponent.description', {
- defaultMessage: 'Expression failed with the message:',
- }),
- getTitle: () =>
- i18n.translate('xpack.canvas.errorComponent.title', {
- defaultMessage: 'Whoops! Expression failed',
- }),
- },
- Expression: {
- getCancelButtonLabel: () =>
- i18n.translate('xpack.canvas.expression.cancelButtonLabel', {
- defaultMessage: 'Cancel',
- }),
- getCloseButtonLabel: () =>
- i18n.translate('xpack.canvas.expression.closeButtonLabel', {
- defaultMessage: 'Close',
- }),
- getLearnLinkText: () =>
- i18n.translate('xpack.canvas.expression.learnLinkText', {
- defaultMessage: 'Learn expression syntax',
- }),
- getMaximizeButtonLabel: () =>
- i18n.translate('xpack.canvas.expression.maximizeButtonLabel', {
- defaultMessage: 'Maximize editor',
- }),
- getMinimizeButtonLabel: () =>
- i18n.translate('xpack.canvas.expression.minimizeButtonLabel', {
- defaultMessage: 'Minimize Editor',
- }),
- getRunButtonLabel: () =>
- i18n.translate('xpack.canvas.expression.runButtonLabel', {
- defaultMessage: 'Run',
- }),
- getRunTooltip: () =>
- i18n.translate('xpack.canvas.expression.runTooltip', {
- defaultMessage: 'Run the expression',
- }),
- },
- ExpressionElementNotSelected: {
- getCloseButtonLabel: () =>
- i18n.translate('xpack.canvas.expressionElementNotSelected.closeButtonLabel', {
- defaultMessage: 'Close',
- }),
- getSelectDescription: () =>
- i18n.translate('xpack.canvas.expressionElementNotSelected.selectDescription', {
- defaultMessage: 'Select an element to show expression input',
- }),
- },
- ExpressionInput: {
- getArgReferenceAliasesDetail: (aliases: string) =>
- i18n.translate('xpack.canvas.expressionInput.argReferenceAliasesDetail', {
- defaultMessage: '{BOLD_MD_TOKEN}Aliases{BOLD_MD_TOKEN}: {aliases}',
- values: {
- BOLD_MD_TOKEN,
- aliases,
- },
- }),
- getArgReferenceDefaultDetail: (defaultVal: string) =>
- i18n.translate('xpack.canvas.expressionInput.argReferenceDefaultDetail', {
- defaultMessage: '{BOLD_MD_TOKEN}Default{BOLD_MD_TOKEN}: {defaultVal}',
- values: {
- BOLD_MD_TOKEN,
- defaultVal,
- },
- }),
- getArgReferenceRequiredDetail: (required: string) =>
- i18n.translate('xpack.canvas.expressionInput.argReferenceRequiredDetail', {
- defaultMessage: '{BOLD_MD_TOKEN}Required{BOLD_MD_TOKEN}: {required}',
- values: {
- BOLD_MD_TOKEN,
- required,
- },
- }),
- getArgReferenceTypesDetail: (types: string) =>
- i18n.translate('xpack.canvas.expressionInput.argReferenceTypesDetail', {
- defaultMessage: '{BOLD_MD_TOKEN}Types{BOLD_MD_TOKEN}: {types}',
- values: {
- BOLD_MD_TOKEN,
- types,
- },
- }),
- getFunctionReferenceAcceptsDetail: (acceptTypes: string) =>
- i18n.translate('xpack.canvas.expressionInput.functionReferenceAccepts', {
- defaultMessage: '{BOLD_MD_TOKEN}Accepts{BOLD_MD_TOKEN}: {acceptTypes}',
- values: {
- BOLD_MD_TOKEN,
- acceptTypes,
- },
- }),
- getFunctionReferenceReturnsDetail: (returnType: string) =>
- i18n.translate('xpack.canvas.expressionInput.functionReferenceReturns', {
- defaultMessage: '{BOLD_MD_TOKEN}Returns{BOLD_MD_TOKEN}: {returnType}',
- values: {
- BOLD_MD_TOKEN,
- returnType,
- },
- }),
- },
- FunctionFormContextError: {
- getContextErrorMessage: (errorMessage: string) =>
- i18n.translate('xpack.canvas.functionForm.contextError', {
- defaultMessage: 'ERROR: {errorMessage}',
- values: {
- errorMessage,
- },
- }),
- },
- FunctionFormFunctionUnknown: {
- getUnknownArgumentTypeErrorMessage: (expressionType: string) =>
- i18n.translate('xpack.canvas.functionForm.functionUnknown.unknownArgumentTypeError', {
- defaultMessage: 'Unknown expression type "{expressionType}"',
- values: {
- expressionType,
- },
- }),
- },
- GroupSettings: {
- getSaveGroupDescription: () =>
- i18n.translate('xpack.canvas.groupSettings.saveGroupDescription', {
- defaultMessage: 'Save this group as a new element to re-use it throughout your workpad.',
- }),
- getUngroupDescription: () =>
- i18n.translate('xpack.canvas.groupSettings.ungroupDescription', {
- defaultMessage: 'Ungroup ({uKey}) to edit individual element settings.',
- values: {
- uKey: 'U',
- },
- }),
- },
- HelpMenu: {
- getDocumentationLinkLabel: () =>
- i18n.translate('xpack.canvas.helpMenu.documentationLinkLabel', {
- defaultMessage: '{CANVAS} documentation',
- values: {
- CANVAS,
- },
- }),
- getHelpMenuDescription: () =>
- i18n.translate('xpack.canvas.helpMenu.description', {
- defaultMessage: 'For {CANVAS} specific information',
- values: {
- CANVAS,
- },
- }),
- getKeyboardShortcutsLinkLabel: () =>
- i18n.translate('xpack.canvas.helpMenu.keyboardShortcutsLinkLabel', {
- defaultMessage: 'Keyboard shortcuts',
- }),
- },
- KeyboardShortcutsDoc: {
- getFlyoutCloseButtonAriaLabel: () =>
- i18n.translate('xpack.canvas.keyboardShortcutsDoc.flyout.closeButtonAriaLabel', {
- defaultMessage: 'Closes keyboard shortcuts reference',
- }),
- getShortcutSeparator: () =>
- i18n.translate('xpack.canvas.keyboardShortcutsDoc.shortcutListSeparator', {
- defaultMessage: 'or',
- description:
- 'Separates which keyboard shortcuts can be used for a single action. Example: "{shortcut1} or {shortcut2} or {shortcut3}"',
- }),
- getTitle: () =>
- i18n.translate('xpack.canvas.keyboardShortcutsDoc.flyoutHeaderTitle', {
- defaultMessage: 'Keyboard shortcuts',
- }),
- },
- LabsControl: {
- getLabsButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderLabsControlSettings.labsButtonLabel', {
- defaultMessage: 'Labs',
- }),
- getAriaLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderLabsControlSettings.labsAriaLabel', {
- defaultMessage: 'View labs projects',
- }),
- getTooltip: () =>
- i18n.translate('xpack.canvas.workpadHeaderLabsControlSettings.labsTooltip', {
- defaultMessage: 'View labs projects',
- }),
- },
- Link: {
- getErrorMessage: (message: string) =>
- i18n.translate('xpack.canvas.link.errorMessage', {
- defaultMessage: 'LINK ERROR: {message}',
- values: {
- message,
- },
- }),
- },
- MultiElementSettings: {
- getMultipleElementsActionsDescription: () =>
- i18n.translate('xpack.canvas.groupSettings.multipleElementsActionsDescription', {
- defaultMessage:
- 'Deselect these elements to edit their individual settings, press ({gKey}) to group them, or save this selection as a new ' +
- 'element to re-use it throughout your workpad.',
- values: {
- gKey: 'G',
- },
- }),
- getMultipleElementsDescription: () =>
- i18n.translate('xpack.canvas.groupSettings.multipleElementsDescription', {
- defaultMessage: 'Multiple elements are currently selected.',
- }),
- },
- PageConfig: {
- getBackgroundColorDescription: () =>
- i18n.translate('xpack.canvas.pageConfig.backgroundColorDescription', {
- defaultMessage: 'Accepts HEX, RGB or HTML color names',
- }),
- getBackgroundColorLabel: () =>
- i18n.translate('xpack.canvas.pageConfig.backgroundColorLabel', {
- defaultMessage: 'Background',
- }),
- getNoTransitionDropDownOptionLabel: () =>
- i18n.translate('xpack.canvas.pageConfig.transitions.noneDropDownOptionLabel', {
- defaultMessage: 'None',
- description:
- 'This is the option the user should choose if they do not want any page transition (i.e. fade in, fade out, etc) to ' +
- 'be applied to the current page.',
- }),
- getTitle: () =>
- i18n.translate('xpack.canvas.pageConfig.title', {
- defaultMessage: 'Page settings',
- }),
- getTransitionLabel: () =>
- i18n.translate('xpack.canvas.pageConfig.transitionLabel', {
- defaultMessage: 'Transition',
- description:
- 'This refers to the transition effect, such as fade in or rotate, applied to a page in presentation mode.',
- }),
- getTransitionPreviewLabel: () =>
- i18n.translate('xpack.canvas.pageConfig.transitionPreviewLabel', {
- defaultMessage: 'Preview',
- description: 'This is the label for a preview of the transition effect selected.',
- }),
- },
- PageManager: {
- getPageNumberAriaLabel: (pageNumber: number) =>
- i18n.translate('xpack.canvas.pageManager.pageNumberAriaLabel', {
- defaultMessage: 'Load page number {pageNumber}',
- values: {
- pageNumber,
- },
- }),
- getAddPageTooltip: () =>
- i18n.translate('xpack.canvas.pageManager.addPageTooltip', {
- defaultMessage: 'Add a new page to this workpad',
- }),
- getConfirmRemoveTitle: () =>
- i18n.translate('xpack.canvas.pageManager.confirmRemoveTitle', {
- defaultMessage: 'Remove Page',
- }),
- getConfirmRemoveDescription: () =>
- i18n.translate('xpack.canvas.pageManager.confirmRemoveDescription', {
- defaultMessage: 'Are you sure you want to remove this page?',
- }),
- getConfirmRemoveButtonLabel: () =>
- i18n.translate('xpack.canvas.pageManager.removeButtonLabel', {
- defaultMessage: 'Remove',
- }),
- },
- PagePreviewPageControls: {
- getClonePageAriaLabel: () =>
- i18n.translate('xpack.canvas.pagePreviewPageControls.clonePageAriaLabel', {
- defaultMessage: 'Clone page',
- }),
- getClonePageTooltip: () =>
- i18n.translate('xpack.canvas.pagePreviewPageControls.clonePageTooltip', {
- defaultMessage: 'Clone',
- }),
- getDeletePageAriaLabel: () =>
- i18n.translate('xpack.canvas.pagePreviewPageControls.deletePageAriaLabel', {
- defaultMessage: 'Delete page',
- }),
- getDeletePageTooltip: () =>
- i18n.translate('xpack.canvas.pagePreviewPageControls.deletePageTooltip', {
- defaultMessage: 'Delete',
- }),
- },
- PalettePicker: {
- getEmptyPaletteLabel: () =>
- i18n.translate('xpack.canvas.palettePicker.emptyPaletteLabel', {
- defaultMessage: 'None',
- }),
- getNoPaletteFoundErrorTitle: () =>
- i18n.translate('xpack.canvas.palettePicker.noPaletteFoundErrorTitle', {
- defaultMessage: 'Color palette not found',
- }),
- },
- SavedElementsModal: {
- getAddNewElementDescription: () =>
- i18n.translate('xpack.canvas.savedElementsModal.addNewElementDescription', {
- defaultMessage: 'Group and save workpad elements to create new elements',
- }),
- getAddNewElementTitle: () =>
- i18n.translate('xpack.canvas.savedElementsModal.addNewElementTitle', {
- defaultMessage: 'Add new elements',
- }),
- getCancelButtonLabel: () =>
- i18n.translate('xpack.canvas.savedElementsModal.cancelButtonLabel', {
- defaultMessage: 'Cancel',
- }),
- getDeleteButtonLabel: () =>
- i18n.translate('xpack.canvas.savedElementsModal.deleteButtonLabel', {
- defaultMessage: 'Delete',
- }),
- getDeleteElementDescription: () =>
- i18n.translate('xpack.canvas.savedElementsModal.deleteElementDescription', {
- defaultMessage: 'Are you sure you want to delete this element?',
- }),
- getDeleteElementTitle: (elementName: string) =>
- i18n.translate('xpack.canvas.savedElementsModal.deleteElementTitle', {
- defaultMessage: `Delete element '{elementName}'?`,
- values: {
- elementName,
- },
- }),
- getEditElementTitle: () =>
- i18n.translate('xpack.canvas.savedElementsModal.editElementTitle', {
- defaultMessage: 'Edit element',
- }),
- getElementsTitle: () =>
- i18n.translate('xpack.canvas.savedElementsModal.elementsTitle', {
- defaultMessage: 'Elements',
- description: 'Title for the "Elements" tab when adding a new element',
- }),
- getFindElementPlaceholder: () =>
- i18n.translate('xpack.canvas.savedElementsModal.findElementPlaceholder', {
- defaultMessage: 'Find element',
- }),
- getModalTitle: () =>
- i18n.translate('xpack.canvas.savedElementsModal.modalTitle', {
- defaultMessage: 'My elements',
- }),
- getMyElementsTitle: () =>
- i18n.translate('xpack.canvas.savedElementsModal.myElementsTitle', {
- defaultMessage: 'My elements',
- description: 'Title for the "My elements" tab when adding a new element',
- }),
- getSavedElementsModalCloseButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadHeader.addElementModalCloseButtonLabel', {
- defaultMessage: 'Close',
- }),
- },
- ShareWebsiteFlyout: {
- getRuntimeStepTitle: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.downloadRuntimeTitle', {
- defaultMessage: 'Download runtime',
- }),
- getSnippentsStepTitle: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.addSnippetsTitle', {
- defaultMessage: 'Add snippets to website',
- }),
- getStepsDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.description', {
- defaultMessage:
- 'Follow these steps to share a static version of this workpad on an external website. It will be a visual snapshot of the current workpad, and will not have access to live data.',
- }),
- getTitle: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.flyoutTitle', {
- defaultMessage: 'Share on a website',
- }),
- getUnsupportedRendererWarning: () =>
- i18n.translate('xpack.canvas.workpadHeaderShareMenu.unsupportedRendererWarning', {
- defaultMessage:
- 'This workpad contains render functions that are not supported by the {CANVAS} Shareable Workpad Runtime. These elements will not be rendered:',
- values: {
- CANVAS,
- },
- }),
- getWorkpadStepTitle: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.downloadWorkpadTitle', {
- defaultMessage: 'Download workpad',
- }),
- },
- ShareWebsiteRuntimeStep: {
- getDownloadLabel: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.runtimeStep.downloadLabel', {
- defaultMessage: 'Download runtime',
- }),
- getStepDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.runtimeStep.description', {
- defaultMessage:
- 'In order to render a Shareable Workpad, you also need to include the {CANVAS} Shareable Workpad Runtime. You can skip this step if the runtime is already included on your website.',
- values: {
- CANVAS,
- },
- }),
- },
- ShareWebsiteSnippetsStep: {
- getAutoplayParameterDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.autoplayParameterDescription', {
- defaultMessage: 'Should the runtime automatically move through the pages of the workpad?',
- }),
- getCallRuntimeLabel: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.callRuntimeLabel', {
- defaultMessage: 'Call Runtime',
- }),
- getHeightParameterDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.heightParameterDescription', {
- defaultMessage: 'The height of the Workpad. Defaults to the Workpad height.',
- }),
- getIncludeRuntimeLabel: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.includeRuntimeLabel', {
- defaultMessage: 'Include Runtime',
- }),
- getIntervalParameterDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.intervalParameterDescription', {
- defaultMessage:
- 'The interval upon which the pages will advance in time format, (e.g. {twoSeconds}, {oneMinute})',
- values: {
- twoSeconds: '2s',
- oneMinute: '1m',
- },
- }),
- getPageParameterDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.pageParameterDescription', {
- defaultMessage: 'The page to display. Defaults to the page specified by the Workpad.',
- }),
- getParametersDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.parametersDescription', {
- defaultMessage:
- 'There are a number of inline parameters to configure the Shareable Workpad.',
- }),
- getParametersTitle: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.parametersLabel', {
- defaultMessage: 'Parameters',
- }),
- getPlaceholderLabel: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.placeholderLabel', {
- defaultMessage: 'Placeholder',
- }),
- getRequiredLabel: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.requiredLabel', {
- defaultMessage: 'required',
- }),
- getShareableParameterDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.shareableParameterDescription', {
- defaultMessage: 'The type of shareable. In this case, a {CANVAS} Workpad.',
- values: {
- CANVAS,
- },
- }),
- getSnippetsStepDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.description', {
- defaultMessage:
- 'The Workpad is placed within the {HTML} of the site by using an {HTML} placeholder. Parameters for the runtime are included inline. See the full list of parameters below. You can include more than one workpad on the page.',
- values: {
- HTML,
- },
- }),
- getToolbarParameterDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.toolbarParameterDescription', {
- defaultMessage: 'Should the toolbar be hidden?',
- }),
- getUrlParameterDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.urlParameterDescription', {
- defaultMessage: 'The {URL} of the Shareable Workpad {JSON} file.',
- values: {
- URL,
- JSON,
- },
- }),
- getWidthParameterDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.widthParameterDescription', {
- defaultMessage: 'The width of the Workpad. Defaults to the Workpad width.',
- }),
- },
- ShareWebsiteWorkpadStep: {
- getDownloadLabel: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.workpadStep.downloadLabel', {
- defaultMessage: 'Download workpad',
- }),
- getStepDescription: () =>
- i18n.translate('xpack.canvas.shareWebsiteFlyout.workpadStep.description', {
- defaultMessage:
- 'The workpad will be exported as a single {JSON} file for sharing in another site.',
- values: {
- JSON,
- },
- }),
- },
- SidebarContent: {
- getGroupedElementSidebarTitle: () =>
- i18n.translate('xpack.canvas.sidebarContent.groupedElementSidebarTitle', {
- defaultMessage: 'Grouped element',
- description:
- 'The title displayed when a grouped element is selected. "elements" refer to the different visualizations, images, ' +
- 'text, etc that can be added in a Canvas workpad. These elements can be grouped into a larger "grouped element" ' +
- 'that contains multiple individual elements.',
- }),
- getMultiElementSidebarTitle: () =>
- i18n.translate('xpack.canvas.sidebarContent.multiElementSidebarTitle', {
- defaultMessage: 'Multiple elements',
- description:
- 'The title displayed when multiple elements are selected. "elements" refer to the different visualizations, images, ' +
- 'text, etc that can be added in a Canvas workpad.',
- }),
- getSingleElementSidebarTitle: () =>
- i18n.translate('xpack.canvas.sidebarContent.singleElementSidebarTitle', {
- defaultMessage: 'Selected element',
- description:
- 'The title displayed when a single element are selected. "element" refer to the different visualizations, images, ' +
- 'text, etc that can be added in a Canvas workpad.',
- }),
- },
- SidebarHeader: {
- getBringForwardAriaLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.bringForwardArialLabel', {
- defaultMessage: 'Move element up one layer',
- }),
- getBringToFrontAriaLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.bringToFrontArialLabel', {
- defaultMessage: 'Move element to top layer',
- }),
- getSendBackwardAriaLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.sendBackwardArialLabel', {
- defaultMessage: 'Move element down one layer',
- }),
- getSendToBackAriaLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.sendToBackArialLabel', {
- defaultMessage: 'Move element to bottom layer',
- }),
- },
- TextStylePicker: {
- getAlignCenterOption: () =>
- i18n.translate('xpack.canvas.textStylePicker.alignCenterOption', {
- defaultMessage: 'Align center',
- }),
- getAlignLeftOption: () =>
- i18n.translate('xpack.canvas.textStylePicker.alignLeftOption', {
- defaultMessage: 'Align left',
- }),
- getAlignRightOption: () =>
- i18n.translate('xpack.canvas.textStylePicker.alignRightOption', {
- defaultMessage: 'Align right',
- }),
- getAlignmentOptionsControlLegend: () =>
- i18n.translate('xpack.canvas.textStylePicker.alignmentOptionsControl', {
- defaultMessage: 'Alignment options',
- }),
- getFontColorLabel: () =>
- i18n.translate('xpack.canvas.textStylePicker.fontColorLabel', {
- defaultMessage: 'Font Color',
- }),
- getStyleBoldOption: () =>
- i18n.translate('xpack.canvas.textStylePicker.styleBoldOption', {
- defaultMessage: 'Bold',
- }),
- getStyleItalicOption: () =>
- i18n.translate('xpack.canvas.textStylePicker.styleItalicOption', {
- defaultMessage: 'Italic',
- }),
- getStyleUnderlineOption: () =>
- i18n.translate('xpack.canvas.textStylePicker.styleUnderlineOption', {
- defaultMessage: 'Underline',
- }),
- getStyleOptionsControlLegend: () =>
- i18n.translate('xpack.canvas.textStylePicker.styleOptionsControl', {
- defaultMessage: 'Style options',
- }),
- },
- TimePicker: {
- getApplyButtonLabel: () =>
- i18n.translate('xpack.canvas.timePicker.applyButtonLabel', {
- defaultMessage: 'Apply',
- }),
- },
- Toolbar: {
- getEditorButtonLabel: () =>
- i18n.translate('xpack.canvas.toolbar.editorButtonLabel', {
- defaultMessage: 'Expression editor',
- }),
- getNextPageAriaLabel: () =>
- i18n.translate('xpack.canvas.toolbar.nextPageAriaLabel', {
- defaultMessage: 'Next Page',
- }),
- getPageButtonLabel: (pageNum: number, totalPages: number) =>
- i18n.translate('xpack.canvas.toolbar.pageButtonLabel', {
- defaultMessage: 'Page {pageNum}{rest}',
- values: {
- pageNum,
- rest: totalPages > 1 ? ` of ${totalPages}` : '',
- },
- }),
- getPreviousPageAriaLabel: () =>
- i18n.translate('xpack.canvas.toolbar.previousPageAriaLabel', {
- defaultMessage: 'Previous Page',
- }),
- getWorkpadManagerCloseButtonLabel: () =>
- i18n.translate('xpack.canvas.toolbar.workpadManagerCloseButtonLabel', {
- defaultMessage: 'Close',
- }),
- getErrorMessage: (message: string) =>
- i18n.translate('xpack.canvas.toolbar.errorMessage', {
- defaultMessage: 'TOOLBAR ERROR: {message}',
- values: {
- message,
- },
- }),
- },
- ToolbarTray: {
- getCloseTrayAriaLabel: () =>
- i18n.translate('xpack.canvas.toolbarTray.closeTrayAriaLabel', {
- defaultMessage: 'Close tray',
- }),
- },
- VarConfig: {
- getAddButtonLabel: () =>
- i18n.translate('xpack.canvas.varConfig.addButtonLabel', {
- defaultMessage: 'Add a variable',
- }),
- getAddTooltipLabel: () =>
- i18n.translate('xpack.canvas.varConfig.addTooltipLabel', {
- defaultMessage: 'Add a variable',
- }),
- getCopyActionButtonLabel: () =>
- i18n.translate('xpack.canvas.varConfig.copyActionButtonLabel', {
- defaultMessage: 'Copy snippet',
- }),
- getCopyActionTooltipLabel: () =>
- i18n.translate('xpack.canvas.varConfig.copyActionTooltipLabel', {
- defaultMessage: 'Copy variable syntax to clipboard',
- }),
- getCopyNotificationDescription: () =>
- i18n.translate('xpack.canvas.varConfig.copyNotificationDescription', {
- defaultMessage: 'Variable syntax copied to clipboard',
- }),
- getDeleteActionButtonLabel: () =>
- i18n.translate('xpack.canvas.varConfig.deleteActionButtonLabel', {
- defaultMessage: 'Delete variable',
- }),
- getDeleteNotificationDescription: () =>
- i18n.translate('xpack.canvas.varConfig.deleteNotificationDescription', {
- defaultMessage: 'Variable successfully deleted',
- }),
- getEditActionButtonLabel: () =>
- i18n.translate('xpack.canvas.varConfig.editActionButtonLabel', {
- defaultMessage: 'Edit variable',
- }),
- getEmptyDescription: () =>
- i18n.translate('xpack.canvas.varConfig.emptyDescription', {
- defaultMessage:
- 'This workpad has no variables currently. You may add variables to store and edit common values. These variables can then be used in elements or within the expression editor.',
- }),
- getTableNameLabel: () =>
- i18n.translate('xpack.canvas.varConfig.tableNameLabel', {
- defaultMessage: 'Name',
- }),
- getTableTypeLabel: () =>
- i18n.translate('xpack.canvas.varConfig.tableTypeLabel', {
- defaultMessage: 'Type',
- }),
- getTableValueLabel: () =>
- i18n.translate('xpack.canvas.varConfig.tableValueLabel', {
- defaultMessage: 'Value',
- }),
- getTitle: () =>
- i18n.translate('xpack.canvas.varConfig.titleLabel', {
- defaultMessage: 'Variables',
- }),
- getTitleTooltip: () =>
- i18n.translate('xpack.canvas.varConfig.titleTooltip', {
- defaultMessage: 'Add variables to store and edit common values',
- }),
- },
- VarConfigDeleteVar: {
- getCancelButtonLabel: () =>
- i18n.translate('xpack.canvas.varConfigDeleteVar.cancelButtonLabel', {
- defaultMessage: 'Cancel',
- }),
- getDeleteButtonLabel: () =>
- i18n.translate('xpack.canvas.varConfigDeleteVar.deleteButtonLabel', {
- defaultMessage: 'Delete variable',
- }),
- getTitle: () =>
- i18n.translate('xpack.canvas.varConfigDeleteVar.titleLabel', {
- defaultMessage: 'Delete variable?',
- }),
- getWarningDescription: () =>
- i18n.translate('xpack.canvas.varConfigDeleteVar.warningDescription', {
- defaultMessage:
- 'Deleting this variable may adversely affect the workpad. Are you sure you wish to continue?',
- }),
- },
- VarConfigEditVar: {
- getAddTitle: () =>
- i18n.translate('xpack.canvas.varConfigEditVar.addTitleLabel', {
- defaultMessage: 'Add variable',
- }),
- getCancelButtonLabel: () =>
- i18n.translate('xpack.canvas.varConfigEditVar.cancelButtonLabel', {
- defaultMessage: 'Cancel',
- }),
- getDuplicateNameError: () =>
- i18n.translate('xpack.canvas.varConfigEditVar.duplicateNameError', {
- defaultMessage: 'Variable name already in use',
- }),
- getEditTitle: () =>
- i18n.translate('xpack.canvas.varConfigEditVar.editTitleLabel', {
- defaultMessage: 'Edit variable',
- }),
- getEditWarning: () =>
- i18n.translate('xpack.canvas.varConfigEditVar.editWarning', {
- defaultMessage: 'Editing a variable in use may adversely affect your workpad',
- }),
- getNameFieldLabel: () =>
- i18n.translate('xpack.canvas.varConfigEditVar.nameFieldLabel', {
- defaultMessage: 'Name',
- }),
- getSaveButtonLabel: () =>
- i18n.translate('xpack.canvas.varConfigEditVar.saveButtonLabel', {
- defaultMessage: 'Save changes',
- }),
- getTypeBooleanLabel: () =>
- i18n.translate('xpack.canvas.varConfigEditVar.typeBooleanLabel', {
- defaultMessage: 'Boolean',
- }),
- getTypeFieldLabel: () =>
- i18n.translate('xpack.canvas.varConfigEditVar.typeFieldLabel', {
- defaultMessage: 'Type',
- }),
- getTypeNumberLabel: () =>
- i18n.translate('xpack.canvas.varConfigEditVar.typeNumberLabel', {
- defaultMessage: 'Number',
- }),
- getTypeStringLabel: () =>
- i18n.translate('xpack.canvas.varConfigEditVar.typeStringLabel', {
- defaultMessage: 'String',
- }),
- getValueFieldLabel: () =>
- i18n.translate('xpack.canvas.varConfigEditVar.valueFieldLabel', {
- defaultMessage: 'Value',
- }),
- },
- VarConfigVarValueField: {
- getBooleanOptionsLegend: () =>
- i18n.translate('xpack.canvas.varConfigVarValueField.booleanOptionsLegend', {
- defaultMessage: 'Boolean value',
- }),
- getFalseOption: () =>
- i18n.translate('xpack.canvas.varConfigVarValueField.falseOption', {
- defaultMessage: 'False',
- }),
- getTrueOption: () =>
- i18n.translate('xpack.canvas.varConfigVarValueField.trueOption', {
- defaultMessage: 'True',
- }),
- },
- WorkpadConfig: {
- getApplyStylesheetButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadConfig.applyStylesheetButtonLabel', {
- defaultMessage: `Apply stylesheet`,
- description:
- '"stylesheet" refers to the collection of CSS style rules entered by the user.',
- }),
- getBackgroundColorLabel: () =>
- i18n.translate('xpack.canvas.workpadConfig.backgroundColorLabel', {
- defaultMessage: 'Background color',
- }),
- getFlipDimensionAriaLabel: () =>
- i18n.translate('xpack.canvas.workpadConfig.swapDimensionsAriaLabel', {
- defaultMessage: `Swap the page's width and height`,
- }),
- getFlipDimensionTooltip: () =>
- i18n.translate('xpack.canvas.workpadConfig.swapDimensionsTooltip', {
- defaultMessage: 'Swap the width and height',
- }),
- getGlobalCSSLabel: () =>
- i18n.translate('xpack.canvas.workpadConfig.globalCSSLabel', {
- defaultMessage: `Global CSS overrides`,
- }),
- getGlobalCSSTooltip: () =>
- i18n.translate('xpack.canvas.workpadConfig.globalCSSTooltip', {
- defaultMessage: `Apply styles to all pages in this workpad`,
- }),
- getNameLabel: () =>
- i18n.translate('xpack.canvas.workpadConfig.nameLabel', {
- defaultMessage: 'Name',
- }),
- getPageHeightLabel: () =>
- i18n.translate('xpack.canvas.workpadConfig.heightLabel', {
- defaultMessage: 'Height',
- }),
- getPageSizeBadgeAriaLabel: (sizeName: string) =>
- i18n.translate('xpack.canvas.workpadConfig.pageSizeBadgeAriaLabel', {
- defaultMessage: `Preset page size: {sizeName}`,
- values: {
- sizeName,
- },
- }),
- getPageSizeBadgeOnClickAriaLabel: (sizeName: string) =>
- i18n.translate('xpack.canvas.workpadConfig.pageSizeBadgeOnClickAriaLabel', {
- defaultMessage: `Set page size to {sizeName}`,
- values: {
- sizeName,
- },
- }),
- getPageWidthLabel: () =>
- i18n.translate('xpack.canvas.workpadConfig.widthLabel', {
- defaultMessage: 'Width',
- }),
- getTitle: () =>
- i18n.translate('xpack.canvas.workpadConfig.title', {
- defaultMessage: 'Workpad settings',
- }),
- getUSLetterButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadConfig.USLetterButtonLabel', {
- defaultMessage: 'US Letter',
- description: 'This is referring to the dimensions of U.S. standard letter paper.',
- }),
- },
- WorkpadHeader: {
- getAddElementButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadHeader.addElementButtonLabel', {
- defaultMessage: 'Add element',
- }),
- getFullScreenButtonAriaLabel: () =>
- i18n.translate('xpack.canvas.workpadHeader.fullscreenButtonAriaLabel', {
- defaultMessage: 'View fullscreen',
- }),
- getFullScreenTooltip: () =>
- i18n.translate('xpack.canvas.workpadHeader.fullscreenTooltip', {
- defaultMessage: 'Enter fullscreen mode',
- }),
- getHideEditControlTooltip: () =>
- i18n.translate('xpack.canvas.workpadHeader.hideEditControlTooltip', {
- defaultMessage: 'Hide editing controls',
- }),
- getNoWritePermissionTooltipText: () =>
- i18n.translate('xpack.canvas.workpadHeader.noWritePermissionTooltip', {
- defaultMessage: "You don't have permission to edit this workpad",
- }),
- getShowEditControlTooltip: () =>
- i18n.translate('xpack.canvas.workpadHeader.showEditControlTooltip', {
- defaultMessage: 'Show editing controls',
- }),
- },
- WorkpadHeaderAutoRefreshControls: {
- getDisableTooltip: () =>
- i18n.translate('xpack.canvas.workpadHeaderAutoRefreshControls.disableTooltip', {
- defaultMessage: 'Disable auto-refresh',
- }),
- getIntervalFormLabelText: () =>
- i18n.translate('xpack.canvas.workpadHeaderAutoRefreshControls.intervalFormLabel', {
- defaultMessage: 'Change auto-refresh interval',
- }),
- getRefreshListDurationManualText: () =>
- i18n.translate(
- 'xpack.canvas.workpadHeaderAutoRefreshControls.refreshListDurationManualText',
- {
- defaultMessage: 'Manually',
- }
- ),
- getRefreshListTitle: () =>
- i18n.translate('xpack.canvas.workpadHeaderAutoRefreshControls.refreshListTitle', {
- defaultMessage: 'Refresh elements',
- }),
- },
- WorkpadHeaderCustomInterval: {
- getButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderCustomInterval.confirmButtonLabel', {
- defaultMessage: 'Set',
- }),
- getFormDescription: () =>
- i18n.translate('xpack.canvas.workpadHeaderCustomInterval.formDescription', {
- defaultMessage:
- 'Use shorthand notation, like {secondsExample}, {minutesExample}, or {hoursExample}',
- values: {
- secondsExample: '30s',
- minutesExample: '10m',
- hoursExample: '1h',
- },
- }),
- getFormLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderCustomInterval.formLabel', {
- defaultMessage: 'Set a custom interval',
- }),
- },
- WorkpadHeaderEditMenu: {
- getAlignmentMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.alignmentMenuItemLabel', {
- defaultMessage: 'Alignment',
- description:
- 'This refers to the vertical (i.e. left, center, right) and horizontal (i.e. top, middle, bottom) ' +
- 'alignment options of the selected elements',
- }),
- getBottomAlignMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.bottomAlignMenuItemLabel', {
- defaultMessage: 'Bottom',
- }),
- getCenterAlignMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.centerAlignMenuItemLabel', {
- defaultMessage: 'Center',
- description: 'This refers to alignment centered horizontally.',
- }),
- getCreateElementModalTitle: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.createElementModalTitle', {
- defaultMessage: 'Create new element',
- }),
- getDistributionMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.distributionMenutItemLabel', {
- defaultMessage: 'Distribution',
- description:
- 'This refers to the options to evenly spacing the selected elements horizontall or vertically.',
- }),
- getEditMenuButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.editMenuButtonLabel', {
- defaultMessage: 'Edit',
- }),
- getEditMenuLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.editMenuLabel', {
- defaultMessage: 'Edit options',
- }),
- getGroupMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.groupMenuItemLabel', {
- defaultMessage: 'Group',
- description: 'This refers to grouping multiple selected elements.',
- }),
- getHorizontalDistributionMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.horizontalDistributionMenutItemLabel', {
- defaultMessage: 'Horizontal',
- }),
- getLeftAlignMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.leftAlignMenuItemLabel', {
- defaultMessage: 'Left',
- }),
- getMiddleAlignMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.middleAlignMenuItemLabel', {
- defaultMessage: 'Middle',
- description: 'This refers to alignment centered vertically.',
- }),
- getOrderMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.orderMenuItemLabel', {
- defaultMessage: 'Order',
- description: 'Refers to the order of the elements displayed on the page from front to back',
- }),
- getRedoMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.redoMenuItemLabel', {
- defaultMessage: 'Redo',
- }),
- getRightAlignMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.rightAlignMenuItemLabel', {
- defaultMessage: 'Right',
- }),
- getSaveElementMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.savedElementMenuItemLabel', {
- defaultMessage: 'Save as new element',
- }),
- getTopAlignMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.topAlignMenuItemLabel', {
- defaultMessage: 'Top',
- }),
- getUndoMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.undoMenuItemLabel', {
- defaultMessage: 'Undo',
- }),
- getUngroupMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.ungroupMenuItemLabel', {
- defaultMessage: 'Ungroup',
- description: 'This refers to ungrouping a grouped element',
- }),
- getVerticalDistributionMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderEditMenu.verticalDistributionMenutItemLabel', {
- defaultMessage: 'Vertical',
- }),
- },
- WorkpadHeaderElementMenu: {
- getAssetsMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderElementMenu.manageAssetsMenuItemLabel', {
- defaultMessage: 'Manage assets',
- }),
- getChartMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderElementMenu.chartMenuItemLabel', {
- defaultMessage: 'Chart',
- }),
- getElementMenuButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderElementMenu.elementMenuButtonLabel', {
- defaultMessage: 'Add element',
- }),
- getElementMenuLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderElementMenu.elementMenuLabel', {
- defaultMessage: 'Add an element',
- }),
- getEmbedObjectMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderElementMenu.embedObjectMenuItemLabel', {
- defaultMessage: 'Add from Kibana',
- }),
- getFilterMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderElementMenu.filterMenuItemLabel', {
- defaultMessage: 'Filter',
- }),
- getImageMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderElementMenu.imageMenuItemLabel', {
- defaultMessage: 'Image',
- }),
- getMyElementsMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderElementMenu.myElementsMenuItemLabel', {
- defaultMessage: 'My elements',
- }),
- getOtherMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderElementMenu.otherMenuItemLabel', {
- defaultMessage: 'Other',
- }),
- getProgressMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderElementMenu.progressMenuItemLabel', {
- defaultMessage: 'Progress',
- }),
- getShapeMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderElementMenu.shapeMenuItemLabel', {
- defaultMessage: 'Shape',
- }),
- getTextMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderElementMenu.textMenuItemLabel', {
- defaultMessage: 'Text',
- }),
- },
- WorkpadHeaderKioskControls: {
- getCycleFormLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderKioskControl.cycleFormLabel', {
- defaultMessage: 'Change cycling interval',
- }),
- getCycleToggleSwitch: () =>
- i18n.translate('xpack.canvas.workpadHeaderKioskControl.cycleToggleSwitch', {
- defaultMessage: 'Cycle slides automatically',
- }),
- getTitle: () =>
- i18n.translate('xpack.canvas.workpadHeaderKioskControl.controlTitle', {
- defaultMessage: 'Cycle fullscreen pages',
- }),
- getAutoplayListDurationManualText: () =>
- i18n.translate('xpack.canvas.workpadHeaderKioskControl.autoplayListDurationManual', {
- defaultMessage: 'Manually',
- }),
- getDisableTooltip: () =>
- i18n.translate('xpack.canvas.workpadHeaderKioskControl.disableTooltip', {
- defaultMessage: 'Disable auto-play',
- }),
- },
- WorkpadHeaderRefreshControlSettings: {
- getRefreshAriaLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderRefreshControlSettings.refreshAriaLabel', {
- defaultMessage: 'Refresh Elements',
- }),
- getRefreshTooltip: () =>
- i18n.translate('xpack.canvas.workpadHeaderRefreshControlSettings.refreshTooltip', {
- defaultMessage: 'Refresh data',
- }),
- },
- WorkpadHeaderShareMenu: {
- getCopyPDFMessage: () =>
- i18n.translate('xpack.canvas.workpadHeaderShareMenu.copyPDFMessage', {
- defaultMessage: 'The {PDF} generation {URL} was copied to your clipboard.',
- values: {
- PDF,
- URL,
- },
- }),
- getCopyShareConfigMessage: () =>
- i18n.translate('xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage', {
- defaultMessage: 'Copied share markup to clipboard',
- }),
- getShareableZipErrorTitle: (workpadName: string) =>
- i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWebsiteErrorTitle', {
- defaultMessage:
- "Failed to create {ZIP} file for '{workpadName}'. The workpad may be too large. You'll need to download the files separately.",
- values: {
- ZIP,
- workpadName,
- },
- }),
- getShareDownloadJSONTitle: () =>
- i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareDownloadJSONTitle', {
- defaultMessage: 'Download as {JSON}',
- values: {
- JSON,
- },
- }),
- getShareDownloadPDFTitle: () =>
- i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareDownloadPDFTitle', {
- defaultMessage: '{PDF} reports',
- values: {
- PDF,
- },
- }),
- getShareMenuButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareMenuButtonLabel', {
- defaultMessage: 'Share',
- }),
- getShareWebsiteTitle: () =>
- i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWebsiteTitle', {
- defaultMessage: 'Share on a website',
- }),
- getShareWorkpadMessage: () =>
- i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWorkpadMessage', {
- defaultMessage: 'Share this workpad',
- }),
- getUnknownExportErrorMessage: (type: string) =>
- i18n.translate('xpack.canvas.workpadHeaderShareMenu.unknownExportErrorMessage', {
- defaultMessage: 'Unknown export type: {type}',
- values: {
- type,
- },
- }),
- },
- WorkpadHeaderViewMenu: {
- getAutoplayOffMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.autoplayOffMenuItemLabel', {
- defaultMessage: 'Turn autoplay off',
- }),
- getAutoplayOnMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.autoplayOnMenuItemLabel', {
- defaultMessage: 'Turn autoplay on',
- }),
- getAutoplaySettingsMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.autoplaySettingsMenuItemLabel', {
- defaultMessage: 'Autoplay settings',
- }),
- getFullscreenMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.fullscreenMenuLabel', {
- defaultMessage: 'Enter fullscreen mode',
- }),
- getHideEditModeLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.hideEditModeLabel', {
- defaultMessage: 'Hide editing controls',
- }),
- getRefreshMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.refreshMenuItemLabel', {
- defaultMessage: 'Refresh data',
- }),
- getRefreshSettingsMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.refreshSettingsMenuItemLabel', {
- defaultMessage: 'Auto refresh settings',
- }),
- getShowEditModeLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.showEditModeLabel', {
- defaultMessage: 'Show editing controls',
- }),
- getViewMenuButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.viewMenuButtonLabel', {
- defaultMessage: 'View',
- }),
- getViewMenuLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.viewMenuLabel', {
- defaultMessage: 'View options',
- }),
- getZoomControlsAriaLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomControlsAriaLabel', {
- defaultMessage: 'Zoom controls',
- }),
- getZoomControlsTooltip: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomControlsTooltip', {
- defaultMessage: 'Zoom controls',
- }),
- getZoomFitToWindowText: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomFitToWindowText', {
- defaultMessage: 'Fit to window',
- }),
- getZoomInText: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomInText', {
- defaultMessage: 'Zoom in',
- }),
- getZoomMenuItemLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomMenuItemLabel', {
- defaultMessage: 'Zoom',
- }),
- getZoomOutText: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomOutText', {
- defaultMessage: 'Zoom out',
- }),
- getZoomPanelTitle: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomPanelTitle', {
- defaultMessage: 'Zoom',
- }),
- getZoomPercentage: (scale: number) =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomResetText', {
- defaultMessage: '{scalePercentage}%',
- values: {
- scalePercentage: scale * 100,
- },
- }),
- getZoomResetText: () =>
- i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomPrecentageValue', {
- defaultMessage: 'Reset',
- }),
- },
-};
diff --git a/x-pack/plugins/canvas/i18n/index.ts b/x-pack/plugins/canvas/i18n/index.ts
index 14c9e5d221b79..d35b915ea7fb6 100644
--- a/x-pack/plugins/canvas/i18n/index.ts
+++ b/x-pack/plugins/canvas/i18n/index.ts
@@ -6,7 +6,6 @@
*/
export * from './capabilities';
-export * from './components';
export * from './constants';
export * from './errors';
export * from './expression_types';
diff --git a/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx b/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx
index 194d2d8b3ddf5..d9df1e4661fbf 100644
--- a/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx
+++ b/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx
@@ -8,15 +8,20 @@
import React, { MouseEventHandler, FC } from 'react';
import PropTypes from 'prop-types';
import { EuiButtonIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
// @ts-expect-error untyped local
import { Popover, PopoverChildrenProps } from '../popover';
import { ArgAdd } from '../arg_add';
// @ts-expect-error untyped local
import { Arg } from '../../expression_types/arg';
-import { ComponentStrings } from '../../../i18n';
-
-const { ArgAddPopover: strings } = ComponentStrings;
+const strings = {
+ getAddAriaLabel: () =>
+ i18n.translate('xpack.canvas.argAddPopover.addAriaLabel', {
+ defaultMessage: 'Add argument',
+ }),
+};
interface ArgOptions {
arg: Arg;
diff --git a/x-pack/plugins/canvas/public/components/arg_form/advanced_failure.js b/x-pack/plugins/canvas/public/components/arg_form/advanced_failure.js
index c40e74186e87e..14f47553002ac 100644
--- a/x-pack/plugins/canvas/public/components/arg_form/advanced_failure.js
+++ b/x-pack/plugins/canvas/public/components/arg_form/advanced_failure.js
@@ -9,12 +9,25 @@ import React from 'react';
import PropTypes from 'prop-types';
import { compose, withProps, withPropsOnChange } from 'recompose';
import { EuiTextArea, EuiButton, EuiButtonEmpty, EuiFormRow, EuiSpacer } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { fromExpression, toExpression } from '@kbn/interpreter/common';
-import { createStatefulPropHoc } from '../../components/enhance/stateful_prop';
-import { ComponentStrings } from '../../../i18n';
+import { createStatefulPropHoc } from '../../components/enhance/stateful_prop';
-const { ArgFormAdvancedFailure: strings } = ComponentStrings;
+const strings = {
+ getApplyButtonLabel: () =>
+ i18n.translate('xpack.canvas.argFormAdvancedFailure.applyButtonLabel', {
+ defaultMessage: 'Apply',
+ }),
+ getResetButtonLabel: () =>
+ i18n.translate('xpack.canvas.argFormAdvancedFailure.resetButtonLabel', {
+ defaultMessage: 'Reset',
+ }),
+ getRowErrorMessage: () =>
+ i18n.translate('xpack.canvas.argFormAdvancedFailure.rowErrorMessage', {
+ defaultMessage: 'Invalid Expression',
+ }),
+};
export const AdvancedFailureComponent = (props) => {
const {
diff --git a/x-pack/plugins/canvas/public/components/arg_form/arg_simple_form.tsx b/x-pack/plugins/canvas/public/components/arg_form/arg_simple_form.tsx
index 2ae772cdc197a..84b87373c1c5a 100644
--- a/x-pack/plugins/canvas/public/components/arg_form/arg_simple_form.tsx
+++ b/x-pack/plugins/canvas/public/components/arg_form/arg_simple_form.tsx
@@ -8,12 +8,20 @@
import React, { ReactNode, MouseEventHandler } from 'react';
import PropTypes from 'prop-types';
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
-import { TooltipIcon, IconType } from '../tooltip_icon';
-
-import { ComponentStrings } from '../../../i18n';
+import { i18n } from '@kbn/i18n';
-const { ArgFormArgSimpleForm: strings } = ComponentStrings;
+import { TooltipIcon, IconType } from '../tooltip_icon';
+const strings = {
+ getRemoveAriaLabel: () =>
+ i18n.translate('xpack.canvas.argFormArgSimpleForm.removeAriaLabel', {
+ defaultMessage: 'Remove',
+ }),
+ getRequiredTooltip: () =>
+ i18n.translate('xpack.canvas.argFormArgSimpleForm.requiredTooltip', {
+ defaultMessage: 'This argument is required, you should specify a value.',
+ }),
+};
interface Props {
children?: ReactNode;
required?: boolean;
diff --git a/x-pack/plugins/canvas/public/components/arg_form/pending_arg_value.js b/x-pack/plugins/canvas/public/components/arg_form/pending_arg_value.js
index ff390a770f80e..f933230f39928 100644
--- a/x-pack/plugins/canvas/public/components/arg_form/pending_arg_value.js
+++ b/x-pack/plugins/canvas/public/components/arg_form/pending_arg_value.js
@@ -7,11 +7,17 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { ComponentStrings } from '../../../i18n';
+import { i18n } from '@kbn/i18n';
+
import { Loading } from '../loading';
import { ArgLabel } from './arg_label';
-const { ArgFormPendingArgValue: strings } = ComponentStrings;
+const strings = {
+ getLoadingMessage: () =>
+ i18n.translate('xpack.canvas.argFormPendingArgValue.loadingMessage', {
+ defaultMessage: 'Loading',
+ }),
+};
export class PendingArgValue extends React.PureComponent {
static propTypes = {
diff --git a/x-pack/plugins/canvas/public/components/arg_form/simple_failure.tsx b/x-pack/plugins/canvas/public/components/arg_form/simple_failure.tsx
index cc4e92679a870..57173fa413e8f 100644
--- a/x-pack/plugins/canvas/public/components/arg_form/simple_failure.tsx
+++ b/x-pack/plugins/canvas/public/components/arg_form/simple_failure.tsx
@@ -6,11 +6,17 @@
*/
import React from 'react';
-import { TooltipIcon, IconType } from '../tooltip_icon';
+import { i18n } from '@kbn/i18n';
-import { ComponentStrings } from '../../../i18n';
+import { TooltipIcon, IconType } from '../tooltip_icon';
-const { ArgFormSimpleFailure: strings } = ComponentStrings;
+const strings = {
+ getFailureTooltip: () =>
+ i18n.translate('xpack.canvas.argFormSimpleFailure.failureTooltip', {
+ defaultMessage:
+ 'The interface for this argument could not parse the value, so a fallback input is being used',
+ }),
+};
// This is what is being generated by render() from the Arg class. It is called in FunctionForm
export const SimpleFailure = () => (
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset.component.tsx b/x-pack/plugins/canvas/public/components/asset_manager/asset.component.tsx
index 8f9d90ccbe1d8..024137f640636 100644
--- a/x-pack/plugins/canvas/public/components/asset_manager/asset.component.tsx
+++ b/x-pack/plugins/canvas/public/components/asset_manager/asset.component.tsx
@@ -17,6 +17,7 @@ import {
EuiTextColor,
EuiToolTip,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { useNotifyService } from '../../services';
@@ -25,9 +26,40 @@ import { Clipboard } from '../clipboard';
import { Download } from '../download';
import { AssetType } from '../../../types';
-import { ComponentStrings } from '../../../i18n';
-
-const { Asset: strings } = ComponentStrings;
+const strings = {
+ getCopyAssetTooltip: () =>
+ i18n.translate('xpack.canvas.asset.copyAssetTooltip', {
+ defaultMessage: 'Copy id to clipboard',
+ }),
+ getCreateImageTooltip: () =>
+ i18n.translate('xpack.canvas.asset.createImageTooltip', {
+ defaultMessage: 'Create image element',
+ }),
+ getDeleteAssetTooltip: () =>
+ i18n.translate('xpack.canvas.asset.deleteAssetTooltip', {
+ defaultMessage: 'Delete',
+ }),
+ getDownloadAssetTooltip: () =>
+ i18n.translate('xpack.canvas.asset.downloadAssetTooltip', {
+ defaultMessage: 'Download',
+ }),
+ getThumbnailAltText: () =>
+ i18n.translate('xpack.canvas.asset.thumbnailAltText', {
+ defaultMessage: 'Asset thumbnail',
+ }),
+ getConfirmModalButtonLabel: () =>
+ i18n.translate('xpack.canvas.asset.confirmModalButtonLabel', {
+ defaultMessage: 'Remove',
+ }),
+ getConfirmModalMessageText: () =>
+ i18n.translate('xpack.canvas.asset.confirmModalDetail', {
+ defaultMessage: 'Are you sure you want to remove this asset?',
+ }),
+ getConfirmModalTitle: () =>
+ i18n.translate('xpack.canvas.asset.confirmModalTitle', {
+ defaultMessage: 'Remove Asset',
+ }),
+};
export interface Props {
/** The asset to be rendered */
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx
index 7795aa9671b83..7b004d5ab5099 100644
--- a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx
+++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx
@@ -24,14 +24,47 @@ import {
EuiSpacer,
EuiText,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { ASSET_MAX_SIZE } from '../../../common/lib/constants';
import { Loading } from '../loading';
import { Asset } from './asset';
import { AssetType } from '../../../types';
-import { ComponentStrings } from '../../../i18n';
-const { AssetManager: strings } = ComponentStrings;
+const strings = {
+ getDescription: () =>
+ i18n.translate('xpack.canvas.assetModal.modalDescription', {
+ defaultMessage:
+ 'Below are the image assets in this workpad. Any assets that are currently in use cannot be determined at this time. To reclaim space, delete assets.',
+ }),
+ getEmptyAssetsDescription: () =>
+ i18n.translate('xpack.canvas.assetModal.emptyAssetsDescription', {
+ defaultMessage: 'Import your assets to get started',
+ }),
+ getFilePickerPromptText: () =>
+ i18n.translate('xpack.canvas.assetModal.filePickerPromptText', {
+ defaultMessage: 'Select or drag and drop images',
+ }),
+ getLoadingText: () =>
+ i18n.translate('xpack.canvas.assetModal.loadingText', {
+ defaultMessage: 'Uploading images',
+ }),
+ getModalCloseButtonLabel: () =>
+ i18n.translate('xpack.canvas.assetModal.modalCloseButtonLabel', {
+ defaultMessage: 'Close',
+ }),
+ getModalTitle: () =>
+ i18n.translate('xpack.canvas.assetModal.modalTitle', {
+ defaultMessage: 'Manage workpad assets',
+ }),
+ getSpaceUsedText: (percentageUsed: number) =>
+ i18n.translate('xpack.canvas.assetModal.spacedUsedText', {
+ defaultMessage: '{percentageUsed}% space used',
+ values: {
+ percentageUsed,
+ },
+ }),
+};
export interface Props {
/** The assets to display within the modal */
diff --git a/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx b/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx
index c2e2d8a053247..4bf13577aff53 100644
--- a/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx
+++ b/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx
@@ -8,12 +8,16 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { EuiFlexGrid, EuiFlexItem, EuiLink, EuiImage, EuiIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { CanvasAsset } from '../../../types';
-import { ComponentStrings } from '../../../i18n';
-
-const { AssetPicker: strings } = ComponentStrings;
+const strings = {
+ getAssetAltText: () =>
+ i18n.translate('xpack.canvas.assetpicker.assetAltText', {
+ defaultMessage: 'Asset thumbnail',
+ }),
+};
interface Props {
assets: CanvasAsset[];
diff --git a/x-pack/plugins/canvas/public/components/canvas_loading/canvas_loading.component.tsx b/x-pack/plugins/canvas/public/components/canvas_loading/canvas_loading.component.tsx
index 38e62f46c945a..8f55c31933291 100644
--- a/x-pack/plugins/canvas/public/components/canvas_loading/canvas_loading.component.tsx
+++ b/x-pack/plugins/canvas/public/components/canvas_loading/canvas_loading.component.tsx
@@ -7,9 +7,14 @@
import React, { FC } from 'react';
import { EuiPanel, EuiLoadingChart, EuiSpacer, EuiText } from '@elastic/eui';
-import { ComponentStrings } from '../../../i18n/components';
+import { i18n } from '@kbn/i18n';
-const { CanvasLoading: strings } = ComponentStrings;
+const strings = {
+ getLoadingLabel: () =>
+ i18n.translate('xpack.canvas.canvasLoading.loadingMessage', {
+ defaultMessage: 'Loading',
+ }),
+};
export const CanvasLoading: FC<{ msg?: string }> = ({
msg = `${strings.getLoadingLabel()}...`,
diff --git a/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx b/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx
index ae5cfac85bdc9..50c679c2a1e51 100644
--- a/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx
+++ b/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx
@@ -9,11 +9,24 @@ import React, { FC } from 'react';
import PropTypes from 'prop-types';
import { EuiButtonIcon, EuiFieldText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import tinycolor from 'tinycolor2';
-import { ColorDot } from '../color_dot/color_dot';
+import { i18n } from '@kbn/i18n';
-import { ComponentStrings } from '../../../i18n/components';
+import { ColorDot } from '../color_dot/color_dot';
-const { ColorManager: strings } = ComponentStrings;
+const strings = {
+ getAddAriaLabel: () =>
+ i18n.translate('xpack.canvas.colorManager.addAriaLabel', {
+ defaultMessage: 'Add Color',
+ }),
+ getCodePlaceholder: () =>
+ i18n.translate('xpack.canvas.colorManager.codePlaceholder', {
+ defaultMessage: 'Color code',
+ }),
+ getRemoveAriaLabel: () =>
+ i18n.translate('xpack.canvas.colorManager.removeAriaLabel', {
+ defaultMessage: 'Remove Color',
+ }),
+};
export interface Props {
/**
diff --git a/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx b/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx
index 5d9cccba924a9..86d9cab4eeea1 100644
--- a/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx
+++ b/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx
@@ -26,16 +26,57 @@ import {
EuiTextArea,
EuiTitle,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { VALID_IMAGE_TYPES } from '../../../common/lib/constants';
import { encode } from '../../../common/lib/dataurl';
import { ElementCard } from '../element_card';
-import { ComponentStrings } from '../../../i18n/components';
const MAX_NAME_LENGTH = 40;
const MAX_DESCRIPTION_LENGTH = 100;
-const { CustomElementModal: strings } = ComponentStrings;
-
+const strings = {
+ getCancelButtonLabel: () =>
+ i18n.translate('xpack.canvas.customElementModal.cancelButtonLabel', {
+ defaultMessage: 'Cancel',
+ }),
+ getCharactersRemainingDescription: (numberOfRemainingCharacter: number) =>
+ i18n.translate('xpack.canvas.customElementModal.remainingCharactersDescription', {
+ defaultMessage: '{numberOfRemainingCharacter} characters remaining',
+ values: {
+ numberOfRemainingCharacter,
+ },
+ }),
+ getDescriptionInputLabel: () =>
+ i18n.translate('xpack.canvas.customElementModal.descriptionInputLabel', {
+ defaultMessage: 'Description',
+ }),
+ getElementPreviewTitle: () =>
+ i18n.translate('xpack.canvas.customElementModal.elementPreviewTitle', {
+ defaultMessage: 'Element preview',
+ }),
+ getImageFilePickerPlaceholder: () =>
+ i18n.translate('xpack.canvas.customElementModal.imageFilePickerPlaceholder', {
+ defaultMessage: 'Select or drag and drop an image',
+ }),
+ getImageInputDescription: () =>
+ i18n.translate('xpack.canvas.customElementModal.imageInputDescription', {
+ defaultMessage:
+ 'Take a screenshot of your element and upload it here. This can also be done after saving.',
+ }),
+ getImageInputLabel: () =>
+ i18n.translate('xpack.canvas.customElementModal.imageInputLabel', {
+ defaultMessage: 'Thumbnail image',
+ }),
+ getNameInputLabel: () =>
+ i18n.translate('xpack.canvas.customElementModal.nameInputLabel', {
+ defaultMessage: 'Name',
+ }),
+ getSaveButtonLabel: () =>
+ i18n.translate('xpack.canvas.customElementModal.saveButtonLabel', {
+ defaultMessage: 'Save',
+ }),
+};
interface Props {
/**
* initial value of the name of the custom element
diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_component.js b/x-pack/plugins/canvas/public/components/datasource/datasource_component.js
index faddc3a60b990..f09ce4c925820 100644
--- a/x-pack/plugins/canvas/public/components/datasource/datasource_component.js
+++ b/x-pack/plugins/canvas/public/components/datasource/datasource_component.js
@@ -18,13 +18,27 @@ import {
EuiHorizontalRule,
} from '@elastic/eui';
import { isEqual } from 'lodash';
-import { ComponentStrings } from '../../../i18n';
+import { i18n } from '@kbn/i18n';
+
import { getDefaultIndex } from '../../lib/es_service';
import { DatasourceSelector } from './datasource_selector';
import { DatasourcePreview } from './datasource_preview';
-const { DatasourceDatasourceComponent: strings } = ComponentStrings;
-
+const strings = {
+ getExpressionArgDescription: () =>
+ i18n.translate('xpack.canvas.datasourceDatasourceComponent.expressionArgDescription', {
+ defaultMessage:
+ 'The datasource has an argument controlled by an expression. Use the expression editor to modify the datasource.',
+ }),
+ getPreviewButtonLabel: () =>
+ i18n.translate('xpack.canvas.datasourceDatasourceComponent.previewButtonLabel', {
+ defaultMessage: 'Preview data',
+ }),
+ getSaveButtonLabel: () =>
+ i18n.translate('xpack.canvas.datasourceDatasourceComponent.saveButtonLabel', {
+ defaultMessage: 'Save',
+ }),
+};
export class DatasourceComponent extends PureComponent {
static propTypes = {
args: PropTypes.object.isRequired,
diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js b/x-pack/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js
index a55f73a087467..2eb42c5cb98dc 100644
--- a/x-pack/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js
+++ b/x-pack/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js
@@ -18,12 +18,33 @@ import {
EuiSpacer,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+
import { Datatable } from '../../datatable';
import { Error } from '../../error';
-import { ComponentStrings } from '../../../../i18n';
-const { DatasourceDatasourcePreview: strings } = ComponentStrings;
-const { DatasourceDatasourceComponent: datasourceStrings } = ComponentStrings;
+const strings = {
+ getEmptyFirstLineDescription: () =>
+ i18n.translate('xpack.canvas.datasourceDatasourcePreview.emptyFirstLineDescription', {
+ defaultMessage: "We couldn't find any documents matching your search criteria.",
+ }),
+ getEmptySecondLineDescription: () =>
+ i18n.translate('xpack.canvas.datasourceDatasourcePreview.emptySecondLineDescription', {
+ defaultMessage: 'Check your datasource settings and try again.',
+ }),
+ getEmptyTitle: () =>
+ i18n.translate('xpack.canvas.datasourceDatasourcePreview.emptyTitle', {
+ defaultMessage: 'No documents found',
+ }),
+ getModalTitle: () =>
+ i18n.translate('xpack.canvas.datasourceDatasourcePreview.modalTitle', {
+ defaultMessage: 'Datasource preview',
+ }),
+ getSaveButtonLabel: () =>
+ i18n.translate('xpack.canvas.datasourceDatasourcePreview.saveButtonLabel', {
+ defaultMessage: 'Save',
+ }),
+};
export const DatasourcePreview = ({ done, datatable }) => (
@@ -37,7 +58,7 @@ export const DatasourcePreview = ({ done, datatable }) => (
id="xpack.canvas.datasourceDatasourcePreview.modalDescription"
defaultMessage="The following data will be available to the selected element upon clicking {saveLabel} in the sidebar."
values={{
- saveLabel: {datasourceStrings.getSaveButtonLabel()} ,
+ saveLabel: {strings.getSaveButtonLabel()} ,
}}
/>
diff --git a/x-pack/plugins/canvas/public/components/datasource/no_datasource.js b/x-pack/plugins/canvas/public/components/datasource/no_datasource.js
index ef86361a4a3a0..f496d493e9d94 100644
--- a/x-pack/plugins/canvas/public/components/datasource/no_datasource.js
+++ b/x-pack/plugins/canvas/public/components/datasource/no_datasource.js
@@ -8,9 +8,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import { EuiCallOut } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
-import { ComponentStrings } from '../../../i18n';
-const { DatasourceNoDatasource: strings } = ComponentStrings;
+const strings = {
+ getPanelDescription: () =>
+ i18n.translate('xpack.canvas.datasourceNoDatasource.panelDescription', {
+ defaultMessage:
+ "This element does not have an attached data source. This is usually because the element is an image or other static asset. If that's not the case you might want to check your expression to make sure it is not malformed.",
+ }),
+ getPanelTitle: () =>
+ i18n.translate('xpack.canvas.datasourceNoDatasource.panelTitle', {
+ defaultMessage: 'No data source present',
+ }),
+};
export const NoDatasource = () => (
diff --git a/x-pack/plugins/canvas/public/components/element_config/element_config.tsx b/x-pack/plugins/canvas/public/components/element_config/element_config.tsx
index 683c12f13f0f9..bf09ac3c5ab77 100644
--- a/x-pack/plugins/canvas/public/components/element_config/element_config.tsx
+++ b/x-pack/plugins/canvas/public/components/element_config/element_config.tsx
@@ -5,13 +5,42 @@
* 2.0.
*/
-import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiAccordion } from '@elastic/eui';
-import PropTypes from 'prop-types';
import React from 'react';
-import { ComponentStrings } from '../../../i18n';
+import PropTypes from 'prop-types';
+import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiAccordion } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { State } from '../../../types';
-const { ElementConfig: strings } = ComponentStrings;
+const strings = {
+ getFailedLabel: () =>
+ i18n.translate('xpack.canvas.elementConfig.failedLabel', {
+ defaultMessage: 'Failed',
+ description:
+ 'The label for the total number of elements in a workpad that have thrown an error or failed to load',
+ }),
+ getLoadedLabel: () =>
+ i18n.translate('xpack.canvas.elementConfig.loadedLabel', {
+ defaultMessage: 'Loaded',
+ description: 'The label for the number of elements in a workpad that have loaded',
+ }),
+ getProgressLabel: () =>
+ i18n.translate('xpack.canvas.elementConfig.progressLabel', {
+ defaultMessage: 'Progress',
+ description: 'The label for the percentage of elements that have finished loading',
+ }),
+ getTitle: () =>
+ i18n.translate('xpack.canvas.elementConfig.title', {
+ defaultMessage: 'Element status',
+ description:
+ '"Elements" refers to the individual text, images, or visualizations that you can add to a Canvas workpad',
+ }),
+ getTotalLabel: () =>
+ i18n.translate('xpack.canvas.elementConfig.totalLabel', {
+ defaultMessage: 'Total',
+ description: 'The label for the total number of elements in a workpad',
+ }),
+};
interface Props {
elementStats: State['transient']['elementStats'];
diff --git a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx
index c86b1d6405e24..716f757b7c25e 100644
--- a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx
+++ b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx
@@ -7,15 +7,24 @@
import React, { FC } from 'react';
import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiTitle } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import {
SavedObjectFinderUi,
SavedObjectMetaData,
} from '../../../../../../src/plugins/saved_objects/public/';
-import { ComponentStrings } from '../../../i18n';
import { useServices } from '../../services';
-const { AddEmbeddableFlyout: strings } = ComponentStrings;
-
+const strings = {
+ getNoItemsText: () =>
+ i18n.translate('xpack.canvas.embedObject.noMatchingObjectsMessage', {
+ defaultMessage: 'No matching objects found.',
+ }),
+ getTitleText: () =>
+ i18n.translate('xpack.canvas.embedObject.titleText', {
+ defaultMessage: 'Add from Kibana',
+ }),
+};
export interface Props {
onClose: () => void;
onSelect: (id: string, embeddableType: string) => void;
diff --git a/x-pack/plugins/canvas/public/components/error/error.tsx b/x-pack/plugins/canvas/public/components/error/error.tsx
index b4cc85ba336e9..cb2c2cd5d58c1 100644
--- a/x-pack/plugins/canvas/public/components/error/error.tsx
+++ b/x-pack/plugins/canvas/public/components/error/error.tsx
@@ -8,18 +8,27 @@
import React, { FC } from 'react';
import PropTypes from 'prop-types';
import { EuiCallOut } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { get } from 'lodash';
-import { ComponentStrings } from '../../../i18n';
+
import { ShowDebugging } from './show_debugging';
+const strings = {
+ getDescription: () =>
+ i18n.translate('xpack.canvas.errorComponent.description', {
+ defaultMessage: 'Expression failed with the message:',
+ }),
+ getTitle: () =>
+ i18n.translate('xpack.canvas.errorComponent.title', {
+ defaultMessage: 'Whoops! Expression failed',
+ }),
+};
export interface Props {
payload: {
error: Error;
};
}
-const { Error: strings } = ComponentStrings;
-
export const Error: FC
= ({ payload }) => {
const message = get(payload, 'error.message');
diff --git a/x-pack/plugins/canvas/public/components/expression/element_not_selected.js b/x-pack/plugins/canvas/public/components/expression/element_not_selected.js
index c7c8c1b063cf1..5f717af6101c1 100644
--- a/x-pack/plugins/canvas/public/components/expression/element_not_selected.js
+++ b/x-pack/plugins/canvas/public/components/expression/element_not_selected.js
@@ -8,9 +8,18 @@
import React from 'react';
import PropTypes from 'prop-types';
import { EuiButton } from '@elastic/eui';
-import { ComponentStrings } from '../../../i18n';
+import { i18n } from '@kbn/i18n';
-const { ExpressionElementNotSelected: strings } = ComponentStrings;
+const strings = {
+ getCloseButtonLabel: () =>
+ i18n.translate('xpack.canvas.expressionElementNotSelected.closeButtonLabel', {
+ defaultMessage: 'Close',
+ }),
+ getSelectDescription: () =>
+ i18n.translate('xpack.canvas.expressionElementNotSelected.selectDescription', {
+ defaultMessage: 'Select an element to show expression input',
+ }),
+};
export const ElementNotSelected = ({ done }) => (
diff --git a/x-pack/plugins/canvas/public/components/expression/expression.tsx b/x-pack/plugins/canvas/public/components/expression/expression.tsx
index 74fdefc322cc9..ff3fed32c0ac0 100644
--- a/x-pack/plugins/canvas/public/components/expression/expression.tsx
+++ b/x-pack/plugins/canvas/public/components/expression/expression.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { FC, MutableRefObject } from 'react';
+import React, { FC, MutableRefObject, useRef } from 'react';
import PropTypes from 'prop-types';
import {
EuiPanel,
@@ -17,17 +17,46 @@ import {
EuiLink,
EuiPortal,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
// @ts-expect-error
import { Shortcuts } from 'react-shortcuts';
-import { ComponentStrings } from '../../../i18n';
+
import { ExpressionInput } from '../expression_input';
import { ToolTipShortcut } from '../tool_tip_shortcut';
import { ExpressionFunction } from '../../../types';
import { FormState } from './';
-const { Expression: strings } = ComponentStrings;
-
-const { useRef } = React;
+const strings = {
+ getCancelButtonLabel: () =>
+ i18n.translate('xpack.canvas.expression.cancelButtonLabel', {
+ defaultMessage: 'Cancel',
+ }),
+ getCloseButtonLabel: () =>
+ i18n.translate('xpack.canvas.expression.closeButtonLabel', {
+ defaultMessage: 'Close',
+ }),
+ getLearnLinkText: () =>
+ i18n.translate('xpack.canvas.expression.learnLinkText', {
+ defaultMessage: 'Learn expression syntax',
+ }),
+ getMaximizeButtonLabel: () =>
+ i18n.translate('xpack.canvas.expression.maximizeButtonLabel', {
+ defaultMessage: 'Maximize editor',
+ }),
+ getMinimizeButtonLabel: () =>
+ i18n.translate('xpack.canvas.expression.minimizeButtonLabel', {
+ defaultMessage: 'Minimize Editor',
+ }),
+ getRunButtonLabel: () =>
+ i18n.translate('xpack.canvas.expression.runButtonLabel', {
+ defaultMessage: 'Run',
+ }),
+ getRunTooltip: () =>
+ i18n.translate('xpack.canvas.expression.runTooltip', {
+ defaultMessage: 'Run the expression',
+ }),
+};
const shortcut = (
ref: MutableRefObject
,
diff --git a/x-pack/plugins/canvas/public/components/expression_input/reference.ts b/x-pack/plugins/canvas/public/components/expression_input/reference.ts
index 95d27360aafc9..94a369e6cb8d8 100644
--- a/x-pack/plugins/canvas/public/components/expression_input/reference.ts
+++ b/x-pack/plugins/canvas/public/components/expression_input/reference.ts
@@ -5,13 +5,64 @@
* 2.0.
*/
-import { ComponentStrings } from '../../../i18n';
+import { i18n } from '@kbn/i18n';
import {
ExpressionFunction,
ExpressionFunctionParameter,
} from '../../../../../../src/plugins/expressions';
-const { ExpressionInput: strings } = ComponentStrings;
+import { BOLD_MD_TOKEN } from '../../../i18n/constants';
+
+const strings = {
+ getArgReferenceAliasesDetail: (aliases: string) =>
+ i18n.translate('xpack.canvas.expressionInput.argReferenceAliasesDetail', {
+ defaultMessage: '{BOLD_MD_TOKEN}Aliases{BOLD_MD_TOKEN}: {aliases}',
+ values: {
+ BOLD_MD_TOKEN,
+ aliases,
+ },
+ }),
+ getArgReferenceDefaultDetail: (defaultVal: string) =>
+ i18n.translate('xpack.canvas.expressionInput.argReferenceDefaultDetail', {
+ defaultMessage: '{BOLD_MD_TOKEN}Default{BOLD_MD_TOKEN}: {defaultVal}',
+ values: {
+ BOLD_MD_TOKEN,
+ defaultVal,
+ },
+ }),
+ getArgReferenceRequiredDetail: (required: string) =>
+ i18n.translate('xpack.canvas.expressionInput.argReferenceRequiredDetail', {
+ defaultMessage: '{BOLD_MD_TOKEN}Required{BOLD_MD_TOKEN}: {required}',
+ values: {
+ BOLD_MD_TOKEN,
+ required,
+ },
+ }),
+ getArgReferenceTypesDetail: (types: string) =>
+ i18n.translate('xpack.canvas.expressionInput.argReferenceTypesDetail', {
+ defaultMessage: '{BOLD_MD_TOKEN}Types{BOLD_MD_TOKEN}: {types}',
+ values: {
+ BOLD_MD_TOKEN,
+ types,
+ },
+ }),
+ getFunctionReferenceAcceptsDetail: (acceptTypes: string) =>
+ i18n.translate('xpack.canvas.expressionInput.functionReferenceAccepts', {
+ defaultMessage: '{BOLD_MD_TOKEN}Accepts{BOLD_MD_TOKEN}: {acceptTypes}',
+ values: {
+ BOLD_MD_TOKEN,
+ acceptTypes,
+ },
+ }),
+ getFunctionReferenceReturnsDetail: (returnType: string) =>
+ i18n.translate('xpack.canvas.expressionInput.functionReferenceReturns', {
+ defaultMessage: '{BOLD_MD_TOKEN}Returns{BOLD_MD_TOKEN}: {returnType}',
+ values: {
+ BOLD_MD_TOKEN,
+ returnType,
+ },
+ }),
+};
/**
* Given an expression function, this function returns a markdown string
diff --git a/x-pack/plugins/canvas/public/components/function_form/function_form_context_error.tsx b/x-pack/plugins/canvas/public/components/function_form/function_form_context_error.tsx
index a022f98d14e1a..2ee709edbf91c 100644
--- a/x-pack/plugins/canvas/public/components/function_form/function_form_context_error.tsx
+++ b/x-pack/plugins/canvas/public/components/function_form/function_form_context_error.tsx
@@ -7,16 +7,23 @@
import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
-import { ComponentStrings } from '../../../i18n/components';
+import { i18n } from '@kbn/i18n';
+const strings = {
+ getContextErrorMessage: (errorMessage: string) =>
+ i18n.translate('xpack.canvas.functionForm.contextError', {
+ defaultMessage: 'ERROR: {errorMessage}',
+ values: {
+ errorMessage,
+ },
+ }),
+};
interface Props {
context: {
error: string;
};
}
-const { FunctionFormContextError: strings } = ComponentStrings;
-
export const FunctionFormContextError: FunctionComponent = ({ context }) => (
{strings.getContextErrorMessage(context.error)}
diff --git a/x-pack/plugins/canvas/public/components/function_form/function_unknown.tsx b/x-pack/plugins/canvas/public/components/function_form/function_unknown.tsx
index b3054e280bbe5..cd7e2f27912a1 100644
--- a/x-pack/plugins/canvas/public/components/function_form/function_unknown.tsx
+++ b/x-pack/plugins/canvas/public/components/function_form/function_unknown.tsx
@@ -7,13 +7,22 @@
import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
-import { ComponentStrings } from '../../../i18n';
+import { i18n } from '@kbn/i18n';
+
+const strings = {
+ getUnknownArgumentTypeErrorMessage: (expressionType: string) =>
+ i18n.translate('xpack.canvas.functionForm.functionUnknown.unknownArgumentTypeError', {
+ defaultMessage: 'Unknown expression type "{expressionType}"',
+ values: {
+ expressionType,
+ },
+ }),
+};
interface Props {
/** the type of the argument */
argType: string;
}
-const { FunctionFormFunctionUnknown: strings } = ComponentStrings;
export const FunctionUnknown: FunctionComponent
= ({ argType }) => (
diff --git a/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx b/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx
index b10103e1824e5..2877ccf41056d 100644
--- a/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx
+++ b/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx
@@ -7,11 +7,13 @@
import React, { FC, useState, lazy, Suspense } from 'react';
import { EuiButtonEmpty, EuiPortal, EuiSpacer } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { ExpressionFunction } from 'src/plugins/expressions';
-import { ComponentStrings } from '../../../i18n';
+
import { KeyboardShortcutsDoc } from '../keyboard_shortcuts_doc';
let FunctionReferenceGenerator: null | React.LazyExoticComponent
= null;
+
if (process.env.NODE_ENV === 'development') {
FunctionReferenceGenerator = lazy(() =>
import('../function_reference_generator').then((module) => ({
@@ -20,7 +22,12 @@ if (process.env.NODE_ENV === 'development') {
);
}
-const { HelpMenu: strings } = ComponentStrings;
+const strings = {
+ getKeyboardShortcutsLinkLabel: () =>
+ i18n.translate('xpack.canvas.helpMenu.keyboardShortcutsLinkLabel', {
+ defaultMessage: 'Keyboard shortcuts',
+ }),
+};
interface Props {
functionRegistry: Record;
diff --git a/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx b/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx
index 0c98ea70b5b9d..a71976006d51c 100644
--- a/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx
+++ b/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx
@@ -17,14 +17,30 @@ import {
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { keymap } from '../../lib/keymap';
import { ShortcutMap, ShortcutNameSpace } from '../../../types/shortcuts';
import { getClientPlatform } from '../../lib/get_client_platform';
import { getId } from '../../lib/get_id';
import { getPrettyShortcut } from '../../lib/get_pretty_shortcut';
-import { ComponentStrings } from '../../../i18n/components';
-const { KeyboardShortcutsDoc: strings } = ComponentStrings;
+const strings = {
+ getFlyoutCloseButtonAriaLabel: () =>
+ i18n.translate('xpack.canvas.keyboardShortcutsDoc.flyout.closeButtonAriaLabel', {
+ defaultMessage: 'Closes keyboard shortcuts reference',
+ }),
+ getShortcutSeparator: () =>
+ i18n.translate('xpack.canvas.keyboardShortcutsDoc.shortcutListSeparator', {
+ defaultMessage: 'or',
+ description:
+ 'Separates which keyboard shortcuts can be used for a single action. Example: "{shortcut1} or {shortcut2} or {shortcut3}"',
+ }),
+ getTitle: () =>
+ i18n.translate('xpack.canvas.keyboardShortcutsDoc.flyoutHeaderTitle', {
+ defaultMessage: 'Keyboard shortcuts',
+ }),
+};
interface DescriptionListItem {
title: string;
diff --git a/x-pack/plugins/canvas/public/components/page_config/index.js b/x-pack/plugins/canvas/public/components/page_config/index.js
index 59f0ac99fd73b..898ac60e68e38 100644
--- a/x-pack/plugins/canvas/public/components/page_config/index.js
+++ b/x-pack/plugins/canvas/public/components/page_config/index.js
@@ -7,13 +7,22 @@
import { connect } from 'react-redux';
import { get } from 'lodash';
+import { i18n } from '@kbn/i18n';
+
import { transitionsRegistry } from '../../lib/transitions_registry';
import { getSelectedPageIndex, getPages } from '../../state/selectors/workpad';
import { stylePage, setPageTransition } from '../../state/actions/pages';
-import { ComponentStrings } from '../../../i18n';
import { PageConfig as Component } from './page_config';
-const { PageConfig: strings } = ComponentStrings;
+const strings = {
+ getNoTransitionDropDownOptionLabel: () =>
+ i18n.translate('xpack.canvas.pageConfig.transitions.noneDropDownOptionLabel', {
+ defaultMessage: 'None',
+ description:
+ 'This is the option the user should choose if they do not want any page transition (i.e. fade in, fade out, etc) to ' +
+ 'be applied to the current page.',
+ }),
+};
const mapStateToProps = (state) => {
const pageIndex = getSelectedPageIndex(state);
diff --git a/x-pack/plugins/canvas/public/components/page_config/page_config.js b/x-pack/plugins/canvas/public/components/page_config/page_config.js
index bc7d92de2273c..8b0c2fedf3af3 100644
--- a/x-pack/plugins/canvas/public/components/page_config/page_config.js
+++ b/x-pack/plugins/canvas/public/components/page_config/page_config.js
@@ -16,10 +16,35 @@ import {
EuiToolTip,
EuiIcon,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { WorkpadColorPicker } from '../workpad_color_picker';
-import { ComponentStrings } from '../../../i18n';
-const { PageConfig: strings } = ComponentStrings;
+const strings = {
+ getBackgroundColorDescription: () =>
+ i18n.translate('xpack.canvas.pageConfig.backgroundColorDescription', {
+ defaultMessage: 'Accepts HEX, RGB or HTML color names',
+ }),
+ getBackgroundColorLabel: () =>
+ i18n.translate('xpack.canvas.pageConfig.backgroundColorLabel', {
+ defaultMessage: 'Background',
+ }),
+ getTitle: () =>
+ i18n.translate('xpack.canvas.pageConfig.title', {
+ defaultMessage: 'Page settings',
+ }),
+ getTransitionLabel: () =>
+ i18n.translate('xpack.canvas.pageConfig.transitionLabel', {
+ defaultMessage: 'Transition',
+ description:
+ 'This refers to the transition effect, such as fade in or rotate, applied to a page in presentation mode.',
+ }),
+ getTransitionPreviewLabel: () =>
+ i18n.translate('xpack.canvas.pageConfig.transitionPreviewLabel', {
+ defaultMessage: 'Preview',
+ description: 'This is the label for a preview of the transition effect selected.',
+ }),
+};
export const PageConfig = ({
pageIndex,
diff --git a/x-pack/plugins/canvas/public/components/page_manager/page_manager.component.tsx b/x-pack/plugins/canvas/public/components/page_manager/page_manager.component.tsx
index 06968d2e4be0a..9d1939db43fd5 100644
--- a/x-pack/plugins/canvas/public/components/page_manager/page_manager.component.tsx
+++ b/x-pack/plugins/canvas/public/components/page_manager/page_manager.component.tsx
@@ -8,7 +8,9 @@
import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types';
import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { DragDropContext, Droppable, Draggable, DragDropContextProps } from 'react-beautiful-dnd';
+
// @ts-expect-error untyped dependency
import Style from 'style-it';
import { ConfirmModal } from '../confirm_modal';
@@ -16,11 +18,26 @@ import { RoutingLink } from '../routing';
import { WorkpadRoutingContext } from '../../routes/workpad';
import { PagePreview } from '../page_preview';
-import { ComponentStrings } from '../../../i18n';
import { CanvasPage } from '../../../types';
-const { PageManager: strings } = ComponentStrings;
-
+const strings = {
+ getAddPageTooltip: () =>
+ i18n.translate('xpack.canvas.pageManager.addPageTooltip', {
+ defaultMessage: 'Add a new page to this workpad',
+ }),
+ getConfirmRemoveTitle: () =>
+ i18n.translate('xpack.canvas.pageManager.confirmRemoveTitle', {
+ defaultMessage: 'Remove Page',
+ }),
+ getConfirmRemoveDescription: () =>
+ i18n.translate('xpack.canvas.pageManager.confirmRemoveDescription', {
+ defaultMessage: 'Are you sure you want to remove this page?',
+ }),
+ getConfirmRemoveButtonLabel: () =>
+ i18n.translate('xpack.canvas.pageManager.removeButtonLabel', {
+ defaultMessage: 'Remove',
+ }),
+};
export interface Props {
isWriteable: boolean;
onAddPage: () => void;
diff --git a/x-pack/plugins/canvas/public/components/page_preview/page_controls.tsx b/x-pack/plugins/canvas/public/components/page_preview/page_controls.tsx
index b29ef1e7fd087..5246fcf822a72 100644
--- a/x-pack/plugins/canvas/public/components/page_preview/page_controls.tsx
+++ b/x-pack/plugins/canvas/public/components/page_preview/page_controls.tsx
@@ -8,10 +8,26 @@
import React, { FC, ReactEventHandler } from 'react';
import PropTypes from 'prop-types';
import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
-import { ComponentStrings } from '../../../i18n';
-
-const { PagePreviewPageControls: strings } = ComponentStrings;
+const strings = {
+ getClonePageAriaLabel: () =>
+ i18n.translate('xpack.canvas.pagePreviewPageControls.clonePageAriaLabel', {
+ defaultMessage: 'Clone page',
+ }),
+ getClonePageTooltip: () =>
+ i18n.translate('xpack.canvas.pagePreviewPageControls.clonePageTooltip', {
+ defaultMessage: 'Clone',
+ }),
+ getDeletePageAriaLabel: () =>
+ i18n.translate('xpack.canvas.pagePreviewPageControls.deletePageAriaLabel', {
+ defaultMessage: 'Delete page',
+ }),
+ getDeletePageTooltip: () =>
+ i18n.translate('xpack.canvas.pagePreviewPageControls.deletePageTooltip', {
+ defaultMessage: 'Delete',
+ }),
+};
interface Props {
pageId: string;
diff --git a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.tsx b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.tsx
index 7ad7bcd8c49c2..dcc77b75f25c3 100644
--- a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.tsx
+++ b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.tsx
@@ -8,10 +8,20 @@
import React, { FC } from 'react';
import PropTypes from 'prop-types';
import { EuiColorPalettePicker, EuiColorPalettePickerPaletteProps } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { palettes, ColorPalette } from '../../../common/lib/palettes';
-import { ComponentStrings } from '../../../i18n';
-const { PalettePicker: strings } = ComponentStrings;
+const strings = {
+ getEmptyPaletteLabel: () =>
+ i18n.translate('xpack.canvas.palettePicker.emptyPaletteLabel', {
+ defaultMessage: 'None',
+ }),
+ getNoPaletteFoundErrorTitle: () =>
+ i18n.translate('xpack.canvas.palettePicker.noPaletteFoundErrorTitle', {
+ defaultMessage: 'Color palette not found',
+ }),
+};
interface RequiredProps {
id?: string;
diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/element_controls.tsx b/x-pack/plugins/canvas/public/components/saved_elements_modal/element_controls.tsx
index 220ea193c902e..ad0a0053f55af 100644
--- a/x-pack/plugins/canvas/public/components/saved_elements_modal/element_controls.tsx
+++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/element_controls.tsx
@@ -8,9 +8,26 @@
import React, { FunctionComponent, MouseEvent } from 'react';
import PropTypes from 'prop-types';
import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
-import { ComponentStrings } from '../../../i18n/components';
+import { i18n } from '@kbn/i18n';
-const { ElementControls: strings } = ComponentStrings;
+const strings = {
+ getDeleteAriaLabel: () =>
+ i18n.translate('xpack.canvas.elementControls.deleteAriaLabel', {
+ defaultMessage: 'Delete element',
+ }),
+ getDeleteTooltip: () =>
+ i18n.translate('xpack.canvas.elementControls.deleteToolTip', {
+ defaultMessage: 'Delete',
+ }),
+ getEditAriaLabel: () =>
+ i18n.translate('xpack.canvas.elementControls.editAriaLabel', {
+ defaultMessage: 'Edit element',
+ }),
+ getEditTooltip: () =>
+ i18n.translate('xpack.canvas.elementControls.editToolTip', {
+ defaultMessage: 'Edit',
+ }),
+};
interface Props {
/**
diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.component.tsx b/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.component.tsx
index bc0039245f432..ee14e89dc4b7d 100644
--- a/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.component.tsx
+++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.component.tsx
@@ -25,14 +25,59 @@ import {
EuiSpacer,
EuiButton,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { sortBy } from 'lodash';
-import { ComponentStrings } from '../../../i18n';
import { CustomElement } from '../../../types';
import { ConfirmModal } from '../confirm_modal/confirm_modal';
import { CustomElementModal } from '../custom_element_modal';
import { ElementGrid } from './element_grid';
-const { SavedElementsModal: strings } = ComponentStrings;
+const strings = {
+ getAddNewElementDescription: () =>
+ i18n.translate('xpack.canvas.savedElementsModal.addNewElementDescription', {
+ defaultMessage: 'Group and save workpad elements to create new elements',
+ }),
+ getAddNewElementTitle: () =>
+ i18n.translate('xpack.canvas.savedElementsModal.addNewElementTitle', {
+ defaultMessage: 'Add new elements',
+ }),
+ getCancelButtonLabel: () =>
+ i18n.translate('xpack.canvas.savedElementsModal.cancelButtonLabel', {
+ defaultMessage: 'Cancel',
+ }),
+ getDeleteButtonLabel: () =>
+ i18n.translate('xpack.canvas.savedElementsModal.deleteButtonLabel', {
+ defaultMessage: 'Delete',
+ }),
+ getDeleteElementDescription: () =>
+ i18n.translate('xpack.canvas.savedElementsModal.deleteElementDescription', {
+ defaultMessage: 'Are you sure you want to delete this element?',
+ }),
+ getDeleteElementTitle: (elementName: string) =>
+ i18n.translate('xpack.canvas.savedElementsModal.deleteElementTitle', {
+ defaultMessage: `Delete element '{elementName}'?`,
+ values: {
+ elementName,
+ },
+ }),
+ getEditElementTitle: () =>
+ i18n.translate('xpack.canvas.savedElementsModal.editElementTitle', {
+ defaultMessage: 'Edit element',
+ }),
+ getFindElementPlaceholder: () =>
+ i18n.translate('xpack.canvas.savedElementsModal.findElementPlaceholder', {
+ defaultMessage: 'Find element',
+ }),
+ getModalTitle: () =>
+ i18n.translate('xpack.canvas.savedElementsModal.modalTitle', {
+ defaultMessage: 'My elements',
+ }),
+ getSavedElementsModalCloseButtonLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeader.addElementModalCloseButtonLabel', {
+ defaultMessage: 'Close',
+ }),
+};
export interface Props {
/**
diff --git a/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.component.tsx b/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.component.tsx
index cc0ad5a728b17..e8f2c7a559f58 100644
--- a/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.component.tsx
+++ b/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.component.tsx
@@ -8,12 +8,28 @@
import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
import { EuiTabbedContent } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
// @ts-expect-error unconverted component
import { Datasource } from '../../datasource';
// @ts-expect-error unconverted component
import { FunctionFormList } from '../../function_form_list';
import { PositionedElement } from '../../../../types';
-import { ComponentStrings } from '../../../../i18n';
+
+const strings = {
+ getDataTabLabel: () =>
+ i18n.translate('xpack.canvas.elementSettings.dataTabLabel', {
+ defaultMessage: 'Data',
+ description:
+ 'This tab contains the settings for the data (i.e. Elasticsearch query) used as ' +
+ 'the source for a Canvas element',
+ }),
+ getDisplayTabLabel: () =>
+ i18n.translate('xpack.canvas.elementSettings.displayTabLabel', {
+ defaultMessage: 'Display',
+ description: 'This tab contains the settings for how data is displayed in a Canvas element',
+ }),
+};
interface Props {
/**
@@ -22,8 +38,6 @@ interface Props {
element: PositionedElement;
}
-const { ElementSettings: strings } = ComponentStrings;
-
export const ElementSettings: FunctionComponent = ({ element }) => {
const tabs = [
{
diff --git a/x-pack/plugins/canvas/public/components/sidebar/group_settings.tsx b/x-pack/plugins/canvas/public/components/sidebar/group_settings.tsx
index e13cf338a2bdc..9d95a6978ff50 100644
--- a/x-pack/plugins/canvas/public/components/sidebar/group_settings.tsx
+++ b/x-pack/plugins/canvas/public/components/sidebar/group_settings.tsx
@@ -7,9 +7,21 @@
import React, { FunctionComponent } from 'react';
import { EuiText } from '@elastic/eui';
-import { ComponentStrings } from '../../../i18n/components';
+import { i18n } from '@kbn/i18n';
-const { GroupSettings: strings } = ComponentStrings;
+const strings = {
+ getSaveGroupDescription: () =>
+ i18n.translate('xpack.canvas.groupSettings.saveGroupDescription', {
+ defaultMessage: 'Save this group as a new element to re-use it throughout your workpad.',
+ }),
+ getUngroupDescription: () =>
+ i18n.translate('xpack.canvas.groupSettings.ungroupDescription', {
+ defaultMessage: 'Ungroup ({uKey}) to edit individual element settings.',
+ values: {
+ uKey: 'U',
+ },
+ }),
+};
export const GroupSettings: FunctionComponent = () => (
diff --git a/x-pack/plugins/canvas/public/components/sidebar/multi_element_settings.tsx b/x-pack/plugins/canvas/public/components/sidebar/multi_element_settings.tsx
index f3bd11f603243..0d73e6397adcc 100644
--- a/x-pack/plugins/canvas/public/components/sidebar/multi_element_settings.tsx
+++ b/x-pack/plugins/canvas/public/components/sidebar/multi_element_settings.tsx
@@ -7,9 +7,23 @@
import React, { FunctionComponent } from 'react';
import { EuiText } from '@elastic/eui';
-import { ComponentStrings } from '../../../i18n/components';
+import { i18n } from '@kbn/i18n';
-const { MultiElementSettings: strings } = ComponentStrings;
+const strings = {
+ getMultipleElementsActionsDescription: () =>
+ i18n.translate('xpack.canvas.groupSettings.multipleElementsActionsDescription', {
+ defaultMessage:
+ 'Deselect these elements to edit their individual settings, press ({gKey}) to group them, or save this selection as a new ' +
+ 'element to re-use it throughout your workpad.',
+ values: {
+ gKey: 'G',
+ },
+ }),
+ getMultipleElementsDescription: () =>
+ i18n.translate('xpack.canvas.groupSettings.multipleElementsDescription', {
+ defaultMessage: 'Multiple elements are currently selected.',
+ }),
+};
export const MultiElementSettings: FunctionComponent = () => (
diff --git a/x-pack/plugins/canvas/public/components/sidebar/sidebar_content.js b/x-pack/plugins/canvas/public/components/sidebar/sidebar_content.js
index a284fc3278436..7292a98fa91ae 100644
--- a/x-pack/plugins/canvas/public/components/sidebar/sidebar_content.js
+++ b/x-pack/plugins/canvas/public/components/sidebar/sidebar_content.js
@@ -9,15 +9,39 @@ import React, { Fragment } from 'react';
import { connect } from 'react-redux';
import { compose, branch, renderComponent } from 'recompose';
import { EuiSpacer } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { getSelectedToplevelNodes, getSelectedElementId } from '../../state/selectors/workpad';
import { SidebarHeader } from '../sidebar_header';
-import { ComponentStrings } from '../../../i18n';
import { MultiElementSettings } from './multi_element_settings';
import { GroupSettings } from './group_settings';
import { GlobalConfig } from './global_config';
import { ElementSettings } from './element_settings';
-const { SidebarContent: strings } = ComponentStrings;
+const strings = {
+ getGroupedElementSidebarTitle: () =>
+ i18n.translate('xpack.canvas.sidebarContent.groupedElementSidebarTitle', {
+ defaultMessage: 'Grouped element',
+ description:
+ 'The title displayed when a grouped element is selected. "elements" refer to the different visualizations, images, ' +
+ 'text, etc that can be added in a Canvas workpad. These elements can be grouped into a larger "grouped element" ' +
+ 'that contains multiple individual elements.',
+ }),
+ getMultiElementSidebarTitle: () =>
+ i18n.translate('xpack.canvas.sidebarContent.multiElementSidebarTitle', {
+ defaultMessage: 'Multiple elements',
+ description:
+ 'The title displayed when multiple elements are selected. "elements" refer to the different visualizations, images, ' +
+ 'text, etc that can be added in a Canvas workpad.',
+ }),
+ getSingleElementSidebarTitle: () =>
+ i18n.translate('xpack.canvas.sidebarContent.singleElementSidebarTitle', {
+ defaultMessage: 'Selected element',
+ description:
+ 'The title displayed when a single element are selected. "element" refer to the different visualizations, images, ' +
+ 'text, etc that can be added in a Canvas workpad.',
+ }),
+};
const mapStateToProps = (state) => ({
selectedToplevelNodes: getSelectedToplevelNodes(state),
diff --git a/x-pack/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx b/x-pack/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx
index d4f8c7642830d..4ba3a7f90f64b 100644
--- a/x-pack/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx
+++ b/x-pack/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx
@@ -8,11 +8,30 @@
import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { ToolTipShortcut } from '../tool_tip_shortcut/';
-import { ComponentStrings } from '../../../i18n/components';
import { ShortcutStrings } from '../../../i18n/shortcuts';
-const { SidebarHeader: strings } = ComponentStrings;
+const strings = {
+ getBringForwardAriaLabel: () =>
+ i18n.translate('xpack.canvas.sidebarHeader.bringForwardArialLabel', {
+ defaultMessage: 'Move element up one layer',
+ }),
+ getBringToFrontAriaLabel: () =>
+ i18n.translate('xpack.canvas.sidebarHeader.bringToFrontArialLabel', {
+ defaultMessage: 'Move element to top layer',
+ }),
+ getSendBackwardAriaLabel: () =>
+ i18n.translate('xpack.canvas.sidebarHeader.sendBackwardArialLabel', {
+ defaultMessage: 'Move element down one layer',
+ }),
+ getSendToBackAriaLabel: () =>
+ i18n.translate('xpack.canvas.sidebarHeader.sendToBackArialLabel', {
+ defaultMessage: 'Move element to bottom layer',
+ }),
+};
+
const shortcutHelp = ShortcutStrings.getShortcutHelp();
interface Props {
diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx b/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx
index 51b9cf7d60262..8d4a1506ad8a2 100644
--- a/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx
+++ b/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx
@@ -8,13 +8,51 @@
import React, { FC, useState } from 'react';
import PropTypes from 'prop-types';
import { EuiFlexGroup, EuiFlexItem, EuiSelect, EuiSpacer, EuiButtonGroup } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { FontValue } from 'src/plugins/expressions';
-import { ComponentStrings } from '../../../i18n';
+
import { FontPicker } from '../font_picker';
import { ColorPickerPopover } from '../color_picker_popover';
import { fontSizes } from './font_sizes';
-const { TextStylePicker: strings } = ComponentStrings;
+const strings = {
+ getAlignCenterOption: () =>
+ i18n.translate('xpack.canvas.textStylePicker.alignCenterOption', {
+ defaultMessage: 'Align center',
+ }),
+ getAlignLeftOption: () =>
+ i18n.translate('xpack.canvas.textStylePicker.alignLeftOption', {
+ defaultMessage: 'Align left',
+ }),
+ getAlignRightOption: () =>
+ i18n.translate('xpack.canvas.textStylePicker.alignRightOption', {
+ defaultMessage: 'Align right',
+ }),
+ getAlignmentOptionsControlLegend: () =>
+ i18n.translate('xpack.canvas.textStylePicker.alignmentOptionsControl', {
+ defaultMessage: 'Alignment options',
+ }),
+ getFontColorLabel: () =>
+ i18n.translate('xpack.canvas.textStylePicker.fontColorLabel', {
+ defaultMessage: 'Font Color',
+ }),
+ getStyleBoldOption: () =>
+ i18n.translate('xpack.canvas.textStylePicker.styleBoldOption', {
+ defaultMessage: 'Bold',
+ }),
+ getStyleItalicOption: () =>
+ i18n.translate('xpack.canvas.textStylePicker.styleItalicOption', {
+ defaultMessage: 'Italic',
+ }),
+ getStyleUnderlineOption: () =>
+ i18n.translate('xpack.canvas.textStylePicker.styleUnderlineOption', {
+ defaultMessage: 'Underline',
+ }),
+ getStyleOptionsControlLegend: () =>
+ i18n.translate('xpack.canvas.textStylePicker.styleOptionsControl', {
+ defaultMessage: 'Style options',
+ }),
+};
export interface StyleProps {
family?: FontValue;
diff --git a/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx b/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx
index 9e89ad4c4f27b..13cc4db7c6217 100644
--- a/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx
+++ b/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx
@@ -8,18 +8,39 @@
import React, { FC, useState, useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { PageManager } from '../page_manager';
import { Expression } from '../expression';
import { Tray } from './tray';
import { CanvasElement } from '../../../types';
-import { ComponentStrings } from '../../../i18n';
import { RoutingButtonIcon } from '../routing';
import { WorkpadRoutingContext } from '../../routes/workpad';
-const { Toolbar: strings } = ComponentStrings;
+const strings = {
+ getEditorButtonLabel: () =>
+ i18n.translate('xpack.canvas.toolbar.editorButtonLabel', {
+ defaultMessage: 'Expression editor',
+ }),
+ getNextPageAriaLabel: () =>
+ i18n.translate('xpack.canvas.toolbar.nextPageAriaLabel', {
+ defaultMessage: 'Next Page',
+ }),
+ getPageButtonLabel: (pageNum: number, totalPages: number) =>
+ i18n.translate('xpack.canvas.toolbar.pageButtonLabel', {
+ defaultMessage: 'Page {pageNum}{rest}',
+ values: {
+ pageNum,
+ rest: totalPages > 1 ? ` of ${totalPages}` : '',
+ },
+ }),
+ getPreviousPageAriaLabel: () =>
+ i18n.translate('xpack.canvas.toolbar.previousPageAriaLabel', {
+ defaultMessage: 'Previous Page',
+ }),
+};
type TrayType = 'pageManager' | 'expression';
diff --git a/x-pack/plugins/canvas/public/components/toolbar/tray/tray.tsx b/x-pack/plugins/canvas/public/components/toolbar/tray/tray.tsx
index 0230eb86e121a..bc6eb455bb9b6 100644
--- a/x-pack/plugins/canvas/public/components/toolbar/tray/tray.tsx
+++ b/x-pack/plugins/canvas/public/components/toolbar/tray/tray.tsx
@@ -8,9 +8,14 @@
import React, { ReactNode, MouseEventHandler } from 'react';
import PropTypes from 'prop-types';
import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
-import { ComponentStrings } from '../../../../i18n';
-const { ToolbarTray: strings } = ComponentStrings;
+const strings = {
+ getCloseTrayAriaLabel: () =>
+ i18n.translate('xpack.canvas.toolbarTray.closeTrayAriaLabel', {
+ defaultMessage: 'Close tray',
+ }),
+};
interface Props {
children: ReactNode;
diff --git a/x-pack/plugins/canvas/public/components/var_config/delete_var.tsx b/x-pack/plugins/canvas/public/components/var_config/delete_var.tsx
index 69b3306d85ea5..f6ba2d7e28825 100644
--- a/x-pack/plugins/canvas/public/components/var_config/delete_var.tsx
+++ b/x-pack/plugins/canvas/public/components/var_config/delete_var.tsx
@@ -15,10 +15,29 @@ import {
EuiSpacer,
EuiText,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { CanvasVariable } from '../../../types';
-import { ComponentStrings } from '../../../i18n';
-const { VarConfigDeleteVar: strings } = ComponentStrings;
+const strings = {
+ getCancelButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfigDeleteVar.cancelButtonLabel', {
+ defaultMessage: 'Cancel',
+ }),
+ getDeleteButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfigDeleteVar.deleteButtonLabel', {
+ defaultMessage: 'Delete variable',
+ }),
+ getTitle: () =>
+ i18n.translate('xpack.canvas.varConfigDeleteVar.titleLabel', {
+ defaultMessage: 'Delete variable?',
+ }),
+ getWarningDescription: () =>
+ i18n.translate('xpack.canvas.varConfigDeleteVar.warningDescription', {
+ defaultMessage:
+ 'Deleting this variable may adversely affect the workpad. Are you sure you wish to continue?',
+ }),
+};
import './var_panel.scss';
diff --git a/x-pack/plugins/canvas/public/components/var_config/edit_var.tsx b/x-pack/plugins/canvas/public/components/var_config/edit_var.tsx
index 64ec8af291448..35f9e67745aec 100644
--- a/x-pack/plugins/canvas/public/components/var_config/edit_var.tsx
+++ b/x-pack/plugins/canvas/public/components/var_config/edit_var.tsx
@@ -20,12 +20,61 @@ import {
EuiSpacer,
EuiCallOut,
} from '@elastic/eui';
-import { CanvasVariable } from '../../../types';
+import { i18n } from '@kbn/i18n';
+import { CanvasVariable } from '../../../types';
import { VarValueField } from './var_value_field';
-import { ComponentStrings } from '../../../i18n';
-const { VarConfigEditVar: strings } = ComponentStrings;
+const strings = {
+ getAddTitle: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.addTitleLabel', {
+ defaultMessage: 'Add variable',
+ }),
+ getCancelButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.cancelButtonLabel', {
+ defaultMessage: 'Cancel',
+ }),
+ getDuplicateNameError: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.duplicateNameError', {
+ defaultMessage: 'Variable name already in use',
+ }),
+ getEditTitle: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.editTitleLabel', {
+ defaultMessage: 'Edit variable',
+ }),
+ getEditWarning: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.editWarning', {
+ defaultMessage: 'Editing a variable in use may adversely affect your workpad',
+ }),
+ getNameFieldLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.nameFieldLabel', {
+ defaultMessage: 'Name',
+ }),
+ getSaveButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.saveButtonLabel', {
+ defaultMessage: 'Save changes',
+ }),
+ getTypeBooleanLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.typeBooleanLabel', {
+ defaultMessage: 'Boolean',
+ }),
+ getTypeFieldLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.typeFieldLabel', {
+ defaultMessage: 'Type',
+ }),
+ getTypeNumberLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.typeNumberLabel', {
+ defaultMessage: 'Number',
+ }),
+ getTypeStringLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.typeStringLabel', {
+ defaultMessage: 'String',
+ }),
+ getValueFieldLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.valueFieldLabel', {
+ defaultMessage: 'Value',
+ }),
+};
import './edit_var.scss';
import './var_panel.scss';
diff --git a/x-pack/plugins/canvas/public/components/var_config/index.tsx b/x-pack/plugins/canvas/public/components/var_config/index.tsx
index 3f072e2f95140..db2a84e93a5dc 100644
--- a/x-pack/plugins/canvas/public/components/var_config/index.tsx
+++ b/x-pack/plugins/canvas/public/components/var_config/index.tsx
@@ -7,12 +7,22 @@
import React, { FC } from 'react';
import copy from 'copy-to-clipboard';
+import { i18n } from '@kbn/i18n';
+
import { VarConfig as ChildComponent } from './var_config';
import { useNotifyService } from '../../services';
-import { ComponentStrings } from '../../../i18n';
import { CanvasVariable } from '../../../types';
-const { VarConfig: strings } = ComponentStrings;
+const strings = {
+ getCopyNotificationDescription: () =>
+ i18n.translate('xpack.canvas.varConfig.copyNotificationDescription', {
+ defaultMessage: 'Variable syntax copied to clipboard',
+ }),
+ getDeleteNotificationDescription: () =>
+ i18n.translate('xpack.canvas.varConfig.deleteNotificationDescription', {
+ defaultMessage: 'Variable successfully deleted',
+ }),
+};
interface Props {
variables: CanvasVariable[];
diff --git a/x-pack/plugins/canvas/public/components/var_config/var_config.tsx b/x-pack/plugins/canvas/public/components/var_config/var_config.tsx
index 0fe506715d07d..dc8898e2132e7 100644
--- a/x-pack/plugins/canvas/public/components/var_config/var_config.tsx
+++ b/x-pack/plugins/canvas/public/components/var_config/var_config.tsx
@@ -18,17 +18,15 @@ import {
EuiSpacer,
EuiButton,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { CanvasVariable } from '../../../types';
-import { ComponentStrings } from '../../../i18n';
import { EditVar } from './edit_var';
import { DeleteVar } from './delete_var';
import './var_config.scss';
-const { VarConfig: strings } = ComponentStrings;
-
enum PanelMode {
List,
Edit,
@@ -49,6 +47,58 @@ interface Props {
onEditVar: (oldVar: CanvasVariable, newVar: CanvasVariable) => void;
}
+const strings = {
+ getAddButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.addButtonLabel', {
+ defaultMessage: 'Add a variable',
+ }),
+ getAddTooltipLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.addTooltipLabel', {
+ defaultMessage: 'Add a variable',
+ }),
+ getCopyActionButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.copyActionButtonLabel', {
+ defaultMessage: 'Copy snippet',
+ }),
+ getCopyActionTooltipLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.copyActionTooltipLabel', {
+ defaultMessage: 'Copy variable syntax to clipboard',
+ }),
+ getDeleteActionButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.deleteActionButtonLabel', {
+ defaultMessage: 'Delete variable',
+ }),
+ getEditActionButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.editActionButtonLabel', {
+ defaultMessage: 'Edit variable',
+ }),
+ getEmptyDescription: () =>
+ i18n.translate('xpack.canvas.varConfig.emptyDescription', {
+ defaultMessage:
+ 'This workpad has no variables currently. You may add variables to store and edit common values. These variables can then be used in elements or within the expression editor.',
+ }),
+ getTableNameLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.tableNameLabel', {
+ defaultMessage: 'Name',
+ }),
+ getTableTypeLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.tableTypeLabel', {
+ defaultMessage: 'Type',
+ }),
+ getTableValueLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.tableValueLabel', {
+ defaultMessage: 'Value',
+ }),
+ getTitle: () =>
+ i18n.translate('xpack.canvas.varConfig.titleLabel', {
+ defaultMessage: 'Variables',
+ }),
+ getTitleTooltip: () =>
+ i18n.translate('xpack.canvas.varConfig.titleTooltip', {
+ defaultMessage: 'Add variables to store and edit common values',
+ }),
+};
+
export const VarConfig: FC
= ({
variables,
onCopyVar,
diff --git a/x-pack/plugins/canvas/public/components/var_config/var_value_field.tsx b/x-pack/plugins/canvas/public/components/var_config/var_value_field.tsx
index c89164dc6efd4..1232ba3977d70 100644
--- a/x-pack/plugins/canvas/public/components/var_config/var_value_field.tsx
+++ b/x-pack/plugins/canvas/public/components/var_config/var_value_field.tsx
@@ -8,11 +8,24 @@
import React, { FC } from 'react';
import { EuiFieldText, EuiFieldNumber, EuiButtonGroup } from '@elastic/eui';
import { htmlIdGenerator } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { CanvasVariable } from '../../../types';
-import { ComponentStrings } from '../../../i18n';
-const { VarConfigVarValueField: strings } = ComponentStrings;
+const strings = {
+ getBooleanOptionsLegend: () =>
+ i18n.translate('xpack.canvas.varConfigVarValueField.booleanOptionsLegend', {
+ defaultMessage: 'Boolean value',
+ }),
+ getFalseOption: () =>
+ i18n.translate('xpack.canvas.varConfigVarValueField.falseOption', {
+ defaultMessage: 'False',
+ }),
+ getTrueOption: () =>
+ i18n.translate('xpack.canvas.varConfigVarValueField.trueOption', {
+ defaultMessage: 'True',
+ }),
+};
interface Props {
type: CanvasVariable['type'];
diff --git a/x-pack/plugins/canvas/public/components/workpad_color_picker/workpad_color_picker.component.tsx b/x-pack/plugins/canvas/public/components/workpad_color_picker/workpad_color_picker.component.tsx
index cc6271e376c07..0561ac005519b 100644
--- a/x-pack/plugins/canvas/public/components/workpad_color_picker/workpad_color_picker.component.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_color_picker/workpad_color_picker.component.tsx
@@ -6,10 +6,15 @@
*/
import React from 'react';
+import { i18n } from '@kbn/i18n';
import { ColorPickerPopover, Props } from '../color_picker_popover';
-import { ComponentStrings } from '../../../i18n';
-const { WorkpadConfig: strings } = ComponentStrings;
+const strings = {
+ getBackgroundColorLabel: () =>
+ i18n.translate('xpack.canvas.workpadConfig.backgroundColorLabel', {
+ defaultMessage: 'Background color',
+ }),
+};
export const WorkpadColorPicker = (props: Props) => {
return (
diff --git a/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.component.tsx b/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.component.tsx
index 2776280d17b32..18e3f2dac9777 100644
--- a/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.component.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.component.tsx
@@ -22,14 +22,70 @@ import {
EuiAccordion,
EuiButton,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { VarConfig } from '../var_config';
-
import { DEFAULT_WORKPAD_CSS } from '../../../common/lib/constants';
import { CanvasVariable } from '../../../types';
-import { ComponentStrings } from '../../../i18n';
-const { WorkpadConfig: strings } = ComponentStrings;
+const strings = {
+ getApplyStylesheetButtonLabel: () =>
+ i18n.translate('xpack.canvas.workpadConfig.applyStylesheetButtonLabel', {
+ defaultMessage: `Apply stylesheet`,
+ description: '"stylesheet" refers to the collection of CSS style rules entered by the user.',
+ }),
+ getFlipDimensionAriaLabel: () =>
+ i18n.translate('xpack.canvas.workpadConfig.swapDimensionsAriaLabel', {
+ defaultMessage: `Swap the page's width and height`,
+ }),
+ getFlipDimensionTooltip: () =>
+ i18n.translate('xpack.canvas.workpadConfig.swapDimensionsTooltip', {
+ defaultMessage: 'Swap the width and height',
+ }),
+ getGlobalCSSLabel: () =>
+ i18n.translate('xpack.canvas.workpadConfig.globalCSSLabel', {
+ defaultMessage: `Global CSS overrides`,
+ }),
+ getGlobalCSSTooltip: () =>
+ i18n.translate('xpack.canvas.workpadConfig.globalCSSTooltip', {
+ defaultMessage: `Apply styles to all pages in this workpad`,
+ }),
+ getNameLabel: () =>
+ i18n.translate('xpack.canvas.workpadConfig.nameLabel', {
+ defaultMessage: 'Name',
+ }),
+ getPageHeightLabel: () =>
+ i18n.translate('xpack.canvas.workpadConfig.heightLabel', {
+ defaultMessage: 'Height',
+ }),
+ getPageSizeBadgeAriaLabel: (sizeName: string) =>
+ i18n.translate('xpack.canvas.workpadConfig.pageSizeBadgeAriaLabel', {
+ defaultMessage: `Preset page size: {sizeName}`,
+ values: {
+ sizeName,
+ },
+ }),
+ getPageSizeBadgeOnClickAriaLabel: (sizeName: string) =>
+ i18n.translate('xpack.canvas.workpadConfig.pageSizeBadgeOnClickAriaLabel', {
+ defaultMessage: `Set page size to {sizeName}`,
+ values: {
+ sizeName,
+ },
+ }),
+ getPageWidthLabel: () =>
+ i18n.translate('xpack.canvas.workpadConfig.widthLabel', {
+ defaultMessage: 'Width',
+ }),
+ getTitle: () =>
+ i18n.translate('xpack.canvas.workpadConfig.title', {
+ defaultMessage: 'Workpad settings',
+ }),
+ getUSLetterButtonLabel: () =>
+ i18n.translate('xpack.canvas.workpadConfig.USLetterButtonLabel', {
+ defaultMessage: 'US Letter',
+ description: 'This is referring to the dimensions of U.S. standard letter paper.',
+ }),
+};
export interface Props {
size: {
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/edit_menu.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/edit_menu.component.tsx
index cb66eceac97c3..c78bdb2a78821 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/edit_menu.component.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/edit_menu.component.tsx
@@ -8,7 +8,8 @@
import React, { Fragment, FunctionComponent, useState } from 'react';
import PropTypes from 'prop-types';
import { EuiButtonEmpty, EuiContextMenu, EuiIcon } from '@elastic/eui';
-import { ComponentStrings } from '../../../../i18n/components';
+import { i18n } from '@kbn/i18n';
+
import { ShortcutStrings } from '../../../../i18n/shortcuts';
import { flattenPanelTree } from '../../../lib/flatten_panel_tree';
import { Popover, ClosePopoverFn } from '../../popover';
@@ -16,8 +17,95 @@ import { CustomElementModal } from '../../custom_element_modal';
import { CONTEXT_MENU_TOP_BORDER_CLASSNAME } from '../../../../common/lib/constants';
import { PositionedElement } from '../../../../types';
-const { WorkpadHeaderEditMenu: strings } = ComponentStrings;
const shortcutHelp = ShortcutStrings.getShortcutHelp();
+const strings = {
+ getAlignmentMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.alignmentMenuItemLabel', {
+ defaultMessage: 'Alignment',
+ description:
+ 'This refers to the vertical (i.e. left, center, right) and horizontal (i.e. top, middle, bottom) ' +
+ 'alignment options of the selected elements',
+ }),
+ getBottomAlignMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.bottomAlignMenuItemLabel', {
+ defaultMessage: 'Bottom',
+ }),
+ getCenterAlignMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.centerAlignMenuItemLabel', {
+ defaultMessage: 'Center',
+ description: 'This refers to alignment centered horizontally.',
+ }),
+ getCreateElementModalTitle: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.createElementModalTitle', {
+ defaultMessage: 'Create new element',
+ }),
+ getDistributionMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.distributionMenutItemLabel', {
+ defaultMessage: 'Distribution',
+ description:
+ 'This refers to the options to evenly spacing the selected elements horizontall or vertically.',
+ }),
+ getEditMenuButtonLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.editMenuButtonLabel', {
+ defaultMessage: 'Edit',
+ }),
+ getEditMenuLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.editMenuLabel', {
+ defaultMessage: 'Edit options',
+ }),
+ getGroupMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.groupMenuItemLabel', {
+ defaultMessage: 'Group',
+ description: 'This refers to grouping multiple selected elements.',
+ }),
+ getHorizontalDistributionMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.horizontalDistributionMenutItemLabel', {
+ defaultMessage: 'Horizontal',
+ }),
+ getLeftAlignMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.leftAlignMenuItemLabel', {
+ defaultMessage: 'Left',
+ }),
+ getMiddleAlignMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.middleAlignMenuItemLabel', {
+ defaultMessage: 'Middle',
+ description: 'This refers to alignment centered vertically.',
+ }),
+ getOrderMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.orderMenuItemLabel', {
+ defaultMessage: 'Order',
+ description: 'Refers to the order of the elements displayed on the page from front to back',
+ }),
+ getRedoMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.redoMenuItemLabel', {
+ defaultMessage: 'Redo',
+ }),
+ getRightAlignMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.rightAlignMenuItemLabel', {
+ defaultMessage: 'Right',
+ }),
+ getSaveElementMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.savedElementMenuItemLabel', {
+ defaultMessage: 'Save as new element',
+ }),
+ getTopAlignMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.topAlignMenuItemLabel', {
+ defaultMessage: 'Top',
+ }),
+ getUndoMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.undoMenuItemLabel', {
+ defaultMessage: 'Undo',
+ }),
+ getUngroupMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.ungroupMenuItemLabel', {
+ defaultMessage: 'Ungroup',
+ description: 'This refers to ungrouping a grouped element',
+ }),
+ getVerticalDistributionMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.verticalDistributionMenutItemLabel', {
+ defaultMessage: 'Vertical',
+ }),
+};
export interface Props {
/**
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx
index 19414f7c8d964..e1d69163e0761 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx
@@ -14,8 +14,9 @@ import {
EuiIcon,
EuiContextMenuPanelItemDescriptor,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { CONTEXT_MENU_TOP_BORDER_CLASSNAME } from '../../../../common/lib';
-import { ComponentStrings } from '../../../../i18n/components';
import { ElementSpec } from '../../../../types';
import { flattenPanelTree } from '../../../lib/flatten_panel_tree';
import { getId } from '../../../lib/get_id';
@@ -31,7 +32,56 @@ interface ElementTypeMeta {
[key: string]: { name: string; icon: string };
}
-export const { WorkpadHeaderElementMenu: strings } = ComponentStrings;
+const strings = {
+ getAssetsMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderElementMenu.manageAssetsMenuItemLabel', {
+ defaultMessage: 'Manage assets',
+ }),
+ getChartMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderElementMenu.chartMenuItemLabel', {
+ defaultMessage: 'Chart',
+ }),
+ getElementMenuButtonLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderElementMenu.elementMenuButtonLabel', {
+ defaultMessage: 'Add element',
+ }),
+ getElementMenuLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderElementMenu.elementMenuLabel', {
+ defaultMessage: 'Add an element',
+ }),
+ getEmbedObjectMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderElementMenu.embedObjectMenuItemLabel', {
+ defaultMessage: 'Add from Kibana',
+ }),
+ getFilterMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderElementMenu.filterMenuItemLabel', {
+ defaultMessage: 'Filter',
+ }),
+ getImageMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderElementMenu.imageMenuItemLabel', {
+ defaultMessage: 'Image',
+ }),
+ getMyElementsMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderElementMenu.myElementsMenuItemLabel', {
+ defaultMessage: 'My elements',
+ }),
+ getOtherMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderElementMenu.otherMenuItemLabel', {
+ defaultMessage: 'Other',
+ }),
+ getProgressMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderElementMenu.progressMenuItemLabel', {
+ defaultMessage: 'Progress',
+ }),
+ getShapeMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderElementMenu.shapeMenuItemLabel', {
+ defaultMessage: 'Shape',
+ }),
+ getTextMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderElementMenu.textMenuItemLabel', {
+ defaultMessage: 'Text',
+ }),
+};
// label and icon for the context menu item for each element type
const elementTypeMeta: ElementTypeMeta = {
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/labs_control/labs_control.tsx b/x-pack/plugins/canvas/public/components/workpad_header/labs_control/labs_control.tsx
index eea59e6aa49f3..fde21c7c85c37 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/labs_control/labs_control.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/labs_control/labs_control.tsx
@@ -7,15 +7,21 @@
import React, { useState } from 'react';
import { EuiButtonEmpty, EuiNotificationBadge } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import {
LazyLabsFlyout,
withSuspense,
} from '../../../../../../../src/plugins/presentation_util/public';
-import { ComponentStrings } from '../../../../i18n';
import { useLabsService } from '../../../services';
-const { LabsControl: strings } = ComponentStrings;
+
+const strings = {
+ getLabsButtonLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderLabsControlSettings.labsButtonLabel', {
+ defaultMessage: 'Labs',
+ }),
+};
const Flyout = withSuspense(LazyLabsFlyout, null);
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/refresh_control/refresh_control.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/refresh_control/refresh_control.component.tsx
index dd9ddc2707ba6..7b1df158087b4 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/refresh_control/refresh_control.component.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/refresh_control/refresh_control.component.tsx
@@ -8,10 +8,20 @@
import React, { MouseEventHandler } from 'react';
import PropTypes from 'prop-types';
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { ToolTipShortcut } from '../../tool_tip_shortcut';
-import { ComponentStrings } from '../../../../i18n';
-const { WorkpadHeaderRefreshControlSettings: strings } = ComponentStrings;
+const strings = {
+ getRefreshAriaLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderRefreshControlSettings.refreshAriaLabel', {
+ defaultMessage: 'Refresh Elements',
+ }),
+ getRefreshTooltip: () =>
+ i18n.translate('xpack.canvas.workpadHeaderRefreshControlSettings.refreshTooltip', {
+ defaultMessage: 'Refresh data',
+ }),
+};
export interface Props {
doRefresh: MouseEventHandler;
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/flyout.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/flyout.component.tsx
index 7c90a6fb045b7..5da009e050a27 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/flyout.component.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/flyout.component.tsx
@@ -21,16 +21,46 @@ import {
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { ComponentStrings } from '../../../../../i18n/components';
import { ZIP, CANVAS, HTML } from '../../../../../i18n/constants';
import { OnCloseFn } from '../share_menu.component';
import { WorkpadStep } from './workpad_step';
import { RuntimeStep } from './runtime_step';
import { SnippetsStep } from './snippets_step';
-const { ShareWebsiteFlyout: strings } = ComponentStrings;
+const strings = {
+ getRuntimeStepTitle: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.downloadRuntimeTitle', {
+ defaultMessage: 'Download runtime',
+ }),
+ getSnippentsStepTitle: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.addSnippetsTitle', {
+ defaultMessage: 'Add snippets to website',
+ }),
+ getStepsDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.description', {
+ defaultMessage:
+ 'Follow these steps to share a static version of this workpad on an external website. It will be a visual snapshot of the current workpad, and will not have access to live data.',
+ }),
+ getTitle: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.flyoutTitle', {
+ defaultMessage: 'Share on a website',
+ }),
+ getUnsupportedRendererWarning: () =>
+ i18n.translate('xpack.canvas.workpadHeaderShareMenu.unsupportedRendererWarning', {
+ defaultMessage:
+ 'This workpad contains render functions that are not supported by the {CANVAS} Shareable Workpad Runtime. These elements will not be rendered:',
+ values: {
+ CANVAS,
+ },
+ }),
+ getWorkpadStepTitle: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.downloadWorkpadTitle', {
+ defaultMessage: 'Download workpad',
+ }),
+};
export type OnDownloadFn = (type: 'share' | 'shareRuntime' | 'shareZip') => void;
export type OnCopyFn = () => void;
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/flyout.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/flyout.ts
index 05d0070a5ea69..65c9d6598578d 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/flyout.ts
+++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/flyout.ts
@@ -7,6 +7,8 @@
import { connect } from 'react-redux';
import { compose, withProps } from 'recompose';
+import { i18n } from '@kbn/i18n';
+
import {
getWorkpad,
getRenderedWorkpad,
@@ -24,14 +26,35 @@ import { arrayBufferFetch } from '../../../../../common/lib/fetch';
import { API_ROUTE_SHAREABLE_ZIP } from '../../../../../common/lib/constants';
import { renderFunctionNames } from '../../../../../shareable_runtime/supported_renderers';
-import { ComponentStrings } from '../../../../../i18n/components';
import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public/';
import { OnCloseFn } from '../share_menu.component';
+import { ZIP } from '../../../../../i18n/constants';
import { WithKibanaProps } from '../../../../index';
export { OnDownloadFn, OnCopyFn } from './flyout.component';
-const { WorkpadHeaderShareMenu: strings } = ComponentStrings;
+const strings = {
+ getCopyShareConfigMessage: () =>
+ i18n.translate('xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage', {
+ defaultMessage: 'Copied share markup to clipboard',
+ }),
+ getShareableZipErrorTitle: (workpadName: string) =>
+ i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWebsiteErrorTitle', {
+ defaultMessage:
+ "Failed to create {ZIP} file for '{workpadName}'. The workpad may be too large. You'll need to download the files separately.",
+ values: {
+ ZIP,
+ workpadName,
+ },
+ }),
+ getUnknownExportErrorMessage: (type: string) =>
+ i18n.translate('xpack.canvas.workpadHeaderShareMenu.unknownExportErrorMessage', {
+ defaultMessage: 'Unknown export type: {type}',
+ values: {
+ type,
+ },
+ }),
+};
const getUnsupportedRenderers = (state: State) => {
const renderers: string[] = [];
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/runtime_step.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/runtime_step.tsx
index c686c403a9a45..8b2fe1a1c0394 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/runtime_step.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/runtime_step.tsx
@@ -7,12 +7,26 @@
import React, { FC } from 'react';
import { EuiText, EuiSpacer, EuiButton } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
-import { ComponentStrings } from '../../../../../i18n/components';
+import { CANVAS } from '../../../../../i18n/constants';
import { OnDownloadFn } from './flyout';
-const { ShareWebsiteRuntimeStep: strings } = ComponentStrings;
+const strings = {
+ getDownloadLabel: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.runtimeStep.downloadLabel', {
+ defaultMessage: 'Download runtime',
+ }),
+ getStepDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.runtimeStep.description', {
+ defaultMessage:
+ 'In order to render a Shareable Workpad, you also need to include the {CANVAS} Shareable Workpad Runtime. You can skip this step if the runtime is already included on your website.',
+ values: {
+ CANVAS,
+ },
+ }),
+};
export const RuntimeStep: FC<{ onDownload: OnDownloadFn }> = ({ onDownload }) => (
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/snippets_step.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/snippets_step.tsx
index bc9f123c623f6..1bac3068e7dbb 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/snippets_step.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/snippets_step.tsx
@@ -16,13 +16,91 @@ import {
EuiDescriptionListDescription,
EuiHorizontalRule,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
-import { ComponentStrings } from '../../../../../i18n/components';
+import { CANVAS, URL, JSON } from '../../../../../i18n/constants';
import { Clipboard } from '../../../clipboard';
import { OnCopyFn } from './flyout';
-const { ShareWebsiteSnippetsStep: strings } = ComponentStrings;
+const strings = {
+ getAutoplayParameterDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.autoplayParameterDescription', {
+ defaultMessage: 'Should the runtime automatically move through the pages of the workpad?',
+ }),
+ getCallRuntimeLabel: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.callRuntimeLabel', {
+ defaultMessage: 'Call Runtime',
+ }),
+ getHeightParameterDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.heightParameterDescription', {
+ defaultMessage: 'The height of the Workpad. Defaults to the Workpad height.',
+ }),
+ getIncludeRuntimeLabel: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.includeRuntimeLabel', {
+ defaultMessage: 'Include Runtime',
+ }),
+ getIntervalParameterDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.intervalParameterDescription', {
+ defaultMessage:
+ 'The interval upon which the pages will advance in time format, (e.g. {twoSeconds}, {oneMinute})',
+ values: {
+ twoSeconds: '2s',
+ oneMinute: '1m',
+ },
+ }),
+ getPageParameterDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.pageParameterDescription', {
+ defaultMessage: 'The page to display. Defaults to the page specified by the Workpad.',
+ }),
+ getParametersDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.parametersDescription', {
+ defaultMessage: 'There are a number of inline parameters to configure the Shareable Workpad.',
+ }),
+ getParametersTitle: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.parametersLabel', {
+ defaultMessage: 'Parameters',
+ }),
+ getPlaceholderLabel: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.placeholderLabel', {
+ defaultMessage: 'Placeholder',
+ }),
+ getRequiredLabel: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.requiredLabel', {
+ defaultMessage: 'required',
+ }),
+ getShareableParameterDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.shareableParameterDescription', {
+ defaultMessage: 'The type of shareable. In this case, a {CANVAS} Workpad.',
+ values: {
+ CANVAS,
+ },
+ }),
+ getSnippetsStepDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.description', {
+ defaultMessage:
+ 'The Workpad is placed within the {HTML} of the site by using an {HTML} placeholder. Parameters for the runtime are included inline. See the full list of parameters below. You can include more than one workpad on the page.',
+ values: {
+ HTML,
+ },
+ }),
+ getToolbarParameterDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.toolbarParameterDescription', {
+ defaultMessage: 'Should the toolbar be hidden?',
+ }),
+ getUrlParameterDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.urlParameterDescription', {
+ defaultMessage: 'The {URL} of the Shareable Workpad {JSON} file.',
+ values: {
+ URL,
+ JSON,
+ },
+ }),
+ getWidthParameterDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.widthParameterDescription', {
+ defaultMessage: 'The width of the Workpad. Defaults to the Workpad width.',
+ }),
+};
const HTML = `
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/workpad_step.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/workpad_step.tsx
index c5a6a4478c765..3ab358d0fe324 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/workpad_step.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/workpad_step.tsx
@@ -7,12 +7,26 @@
import React, { FC } from 'react';
import { EuiText, EuiSpacer, EuiButton } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
-import { ComponentStrings } from '../../../../../i18n/components';
+import { JSON } from '../../../../../i18n/constants';
import { OnDownloadFn } from './flyout';
-const { ShareWebsiteWorkpadStep: strings } = ComponentStrings;
+const strings = {
+ getDownloadLabel: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.workpadStep.downloadLabel', {
+ defaultMessage: 'Download workpad',
+ }),
+ getStepDescription: () =>
+ i18n.translate('xpack.canvas.shareWebsiteFlyout.workpadStep.description', {
+ defaultMessage:
+ 'The workpad will be exported as a single {JSON} file for sharing in another site.',
+ values: {
+ JSON,
+ },
+ }),
+};
export const WorkpadStep: FC<{ onDownload: OnDownloadFn }> = ({ onDownload }) => (
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx
index d4cb4d0736bb1..5ccc09bf3586b 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx
@@ -5,18 +5,47 @@
* 2.0.
*/
+import React, { FunctionComponent, useState } from 'react';
+import PropTypes from 'prop-types';
import { EuiButtonEmpty, EuiContextMenu, EuiIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { IBasePath } from 'kibana/public';
-import PropTypes from 'prop-types';
-import React, { FunctionComponent, useState } from 'react';
+
import { ReportingStart } from '../../../../../reporting/public';
-import { ComponentStrings } from '../../../../i18n/components';
+import { PDF, JSON } from '../../../../i18n/constants';
import { flattenPanelTree } from '../../../lib/flatten_panel_tree';
import { ClosePopoverFn, Popover } from '../../popover';
import { ShareWebsiteFlyout } from './flyout';
import { CanvasWorkpadSharingData, getPdfJobParams } from './utils';
-const { WorkpadHeaderShareMenu: strings } = ComponentStrings;
+const strings = {
+ getShareDownloadJSONTitle: () =>
+ i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareDownloadJSONTitle', {
+ defaultMessage: 'Download as {JSON}',
+ values: {
+ JSON,
+ },
+ }),
+ getShareDownloadPDFTitle: () =>
+ i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareDownloadPDFTitle', {
+ defaultMessage: '{PDF} reports',
+ values: {
+ PDF,
+ },
+ }),
+ getShareMenuButtonLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareMenuButtonLabel', {
+ defaultMessage: 'Share',
+ }),
+ getShareWebsiteTitle: () =>
+ i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWebsiteTitle', {
+ defaultMessage: 'Share on a website',
+ }),
+ getShareWorkpadMessage: () =>
+ i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWorkpadMessage', {
+ defaultMessage: 'Share this workpad',
+ }),
+};
type CopyTypes = 'pdf' | 'reportingConfig';
type ExportTypes = 'pdf' | 'json';
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.ts
index fc4906817cf6f..ef13655b66aca 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.ts
+++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.ts
@@ -7,14 +7,23 @@
import { connect } from 'react-redux';
import { compose, withProps } from 'recompose';
-import { ComponentStrings } from '../../../../i18n';
+import { i18n } from '@kbn/i18n';
+
import { CanvasWorkpad, State } from '../../../../types';
import { downloadWorkpad } from '../../../lib/download_workpad';
import { withServices, WithServicesProps } from '../../../services';
import { getPages, getWorkpad } from '../../../state/selectors/workpad';
import { Props as ComponentProps, ShareMenu as Component } from './share_menu.component';
-const { WorkpadHeaderShareMenu: strings } = ComponentStrings;
+const strings = {
+ getUnknownExportErrorMessage: (type: string) =>
+ i18n.translate('xpack.canvas.workpadHeaderShareMenu.unknownExportErrorMessage', {
+ defaultMessage: 'Unknown export type: {type}',
+ values: {
+ type,
+ },
+ }),
+};
const mapStateToProps = (state: State) => ({
workpad: getWorkpad(state),
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/auto_refresh_controls.tsx b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/auto_refresh_controls.tsx
index 1508f8683b8c1..6815ef351e0b8 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/auto_refresh_controls.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/auto_refresh_controls.tsx
@@ -22,14 +22,34 @@ import {
EuiToolTip,
htmlIdGenerator,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { timeDuration } from '../../../lib/time_duration';
+import { UnitStrings } from '../../../../i18n';
import { CustomInterval } from './custom_interval';
-import { ComponentStrings, UnitStrings } from '../../../../i18n';
-const { WorkpadHeaderAutoRefreshControls: strings } = ComponentStrings;
const { time: timeStrings } = UnitStrings;
const { getSecondsText, getMinutesText, getHoursText } = timeStrings;
+const strings = {
+ getDisableTooltip: () =>
+ i18n.translate('xpack.canvas.workpadHeaderAutoRefreshControls.disableTooltip', {
+ defaultMessage: 'Disable auto-refresh',
+ }),
+ getIntervalFormLabelText: () =>
+ i18n.translate('xpack.canvas.workpadHeaderAutoRefreshControls.intervalFormLabel', {
+ defaultMessage: 'Change auto-refresh interval',
+ }),
+ getRefreshListDurationManualText: () =>
+ i18n.translate('xpack.canvas.workpadHeaderAutoRefreshControls.refreshListDurationManualText', {
+ defaultMessage: 'Manually',
+ }),
+ getRefreshListTitle: () =>
+ i18n.translate('xpack.canvas.workpadHeaderAutoRefreshControls.refreshListTitle', {
+ defaultMessage: 'Refresh elements',
+ }),
+};
+
interface Props {
refreshInterval: number;
setRefresh: (interval: number) => void;
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx
index d4d28d19131f0..284749340e440 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx
@@ -8,12 +8,31 @@
import React, { useState, ChangeEvent } from 'react';
import PropTypes from 'prop-types';
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiButton, EuiFieldText } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { ButtonSize } from '@elastic/eui/src/components/button/button';
import { FlexGroupGutterSize } from '@elastic/eui/src/components/flex/flex_group';
import { getTimeInterval } from '../../../lib/time_interval';
-import { ComponentStrings } from '../../../../i18n';
-const { WorkpadHeaderCustomInterval: strings } = ComponentStrings;
+const strings = {
+ getButtonLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderCustomInterval.confirmButtonLabel', {
+ defaultMessage: 'Set',
+ }),
+ getFormDescription: () =>
+ i18n.translate('xpack.canvas.workpadHeaderCustomInterval.formDescription', {
+ defaultMessage:
+ 'Use shorthand notation, like {secondsExample}, {minutesExample}, or {hoursExample}',
+ values: {
+ secondsExample: '30s',
+ minutesExample: '10m',
+ hoursExample: '1h',
+ },
+ }),
+ getFormLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderCustomInterval.formLabel', {
+ defaultMessage: 'Set a custom interval',
+ }),
+};
interface Props {
gutterSize: FlexGroupGutterSize;
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/kiosk_controls.tsx b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/kiosk_controls.tsx
index 55373d7a3515c..b8ed80c870f28 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/kiosk_controls.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/kiosk_controls.tsx
@@ -22,14 +22,34 @@ import {
EuiFlexGroup,
htmlIdGenerator,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import { timeDuration } from '../../../lib/time_duration';
+import { UnitStrings } from '../../../../i18n';
import { CustomInterval } from './custom_interval';
-import { ComponentStrings, UnitStrings } from '../../../../i18n';
-const { WorkpadHeaderKioskControls: strings } = ComponentStrings;
const { time: timeStrings } = UnitStrings;
const { getSecondsText, getMinutesText } = timeStrings;
+const strings = {
+ getCycleFormLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderKioskControl.cycleFormLabel', {
+ defaultMessage: 'Change cycling interval',
+ }),
+ getTitle: () =>
+ i18n.translate('xpack.canvas.workpadHeaderKioskControl.controlTitle', {
+ defaultMessage: 'Cycle fullscreen pages',
+ }),
+ getAutoplayListDurationManualText: () =>
+ i18n.translate('xpack.canvas.workpadHeaderKioskControl.autoplayListDurationManual', {
+ defaultMessage: 'Manually',
+ }),
+ getDisableTooltip: () =>
+ i18n.translate('xpack.canvas.workpadHeaderKioskControl.disableTooltip', {
+ defaultMessage: 'Disable auto-play',
+ }),
+};
+
interface Props {
autoplayInterval: number;
onSetInterval: (interval: number) => void;
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/view_menu.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/view_menu.component.tsx
index 8fb24c1f3c62e..168ddc690c4d4 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/view_menu.component.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/view_menu.component.tsx
@@ -13,18 +13,80 @@ import {
EuiIcon,
EuiContextMenuPanelItemDescriptor,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
import {
MAX_ZOOM_LEVEL,
MIN_ZOOM_LEVEL,
CONTEXT_MENU_TOP_BORDER_CLASSNAME,
} from '../../../../common/lib/constants';
-import { ComponentStrings } from '../../../../i18n/components';
+
import { flattenPanelTree } from '../../../lib/flatten_panel_tree';
import { Popover, ClosePopoverFn } from '../../popover';
import { AutoRefreshControls } from './auto_refresh_controls';
import { KioskControls } from './kiosk_controls';
-const { WorkpadHeaderViewMenu: strings } = ComponentStrings;
+const strings = {
+ getAutoplaySettingsMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.autoplaySettingsMenuItemLabel', {
+ defaultMessage: 'Autoplay settings',
+ }),
+ getFullscreenMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.fullscreenMenuLabel', {
+ defaultMessage: 'Enter fullscreen mode',
+ }),
+ getHideEditModeLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.hideEditModeLabel', {
+ defaultMessage: 'Hide editing controls',
+ }),
+ getRefreshMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.refreshMenuItemLabel', {
+ defaultMessage: 'Refresh data',
+ }),
+ getRefreshSettingsMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.refreshSettingsMenuItemLabel', {
+ defaultMessage: 'Auto refresh settings',
+ }),
+ getShowEditModeLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.showEditModeLabel', {
+ defaultMessage: 'Show editing controls',
+ }),
+ getViewMenuButtonLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.viewMenuButtonLabel', {
+ defaultMessage: 'View',
+ }),
+ getViewMenuLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.viewMenuLabel', {
+ defaultMessage: 'View options',
+ }),
+ getZoomFitToWindowText: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomFitToWindowText', {
+ defaultMessage: 'Fit to window',
+ }),
+ getZoomInText: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomInText', {
+ defaultMessage: 'Zoom in',
+ }),
+ getZoomMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomMenuItemLabel', {
+ defaultMessage: 'Zoom',
+ }),
+ getZoomOutText: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomOutText', {
+ defaultMessage: 'Zoom out',
+ }),
+ getZoomPercentage: (scale: number) =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomResetText', {
+ defaultMessage: '{scalePercentage}%',
+ values: {
+ scalePercentage: scale * 100,
+ },
+ }),
+ getZoomResetText: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomPrecentageValue', {
+ defaultMessage: 'Reset',
+ }),
+};
const QUICK_ZOOM_LEVELS = [0.5, 1, 2];
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.component.tsx
index 415d3ddf46709..5320a65a90408 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.component.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.component.tsx
@@ -10,7 +10,8 @@ import PropTypes from 'prop-types';
// @ts-expect-error no @types definition
import { Shortcuts } from 'react-shortcuts';
import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
-import { ComponentStrings } from '../../../i18n';
+import { i18n } from '@kbn/i18n';
+
import { ToolTipShortcut } from '../tool_tip_shortcut/';
import { RefreshControl } from './refresh_control';
// @ts-expect-error untyped local
@@ -22,7 +23,28 @@ import { ViewMenu } from './view_menu';
import { LabsControl } from './labs_control';
import { CommitFn } from '../../../types';
-const { WorkpadHeader: strings } = ComponentStrings;
+const strings = {
+ getFullScreenButtonAriaLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeader.fullscreenButtonAriaLabel', {
+ defaultMessage: 'View fullscreen',
+ }),
+ getFullScreenTooltip: () =>
+ i18n.translate('xpack.canvas.workpadHeader.fullscreenTooltip', {
+ defaultMessage: 'Enter fullscreen mode',
+ }),
+ getHideEditControlTooltip: () =>
+ i18n.translate('xpack.canvas.workpadHeader.hideEditControlTooltip', {
+ defaultMessage: 'Hide editing controls',
+ }),
+ getNoWritePermissionTooltipText: () =>
+ i18n.translate('xpack.canvas.workpadHeader.noWritePermissionTooltip', {
+ defaultMessage: "You don't have permission to edit this workpad",
+ }),
+ getShowEditControlTooltip: () =>
+ i18n.translate('xpack.canvas.workpadHeader.showEditControlTooltip', {
+ defaultMessage: 'Show editing controls',
+ }),
+};
export interface Props {
isWriteable: boolean;
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 17c31b8cd115e..e73bb19627b8f 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -5976,9 +5976,6 @@
"xpack.banners.settings.textColor.description": "バナーテキストの色を設定します。{subscriptionLink}",
"xpack.banners.settings.textColor.title": "バナーテキスト色",
"xpack.banners.settings.textContent.title": "バナーテキスト",
- "xpack.canvas.app.loadErrorMessage": "メッセージ:{error}",
- "xpack.canvas.app.loadErrorTitle": "Canvas の読み込みに失敗",
- "xpack.canvas.app.loadingMessage": "Canvas を読み込み中",
"xpack.canvas.appDescription": "データを完璧に美しく表現します。",
"xpack.canvas.argAddPopover.addAriaLabel": "引数を追加",
"xpack.canvas.argFormAdvancedFailure.applyButtonLabel": "適用",
@@ -5996,8 +5993,6 @@
"xpack.canvas.asset.deleteAssetTooltip": "削除",
"xpack.canvas.asset.downloadAssetTooltip": "ダウンロード",
"xpack.canvas.asset.thumbnailAltText": "アセットのサムネイル",
- "xpack.canvas.assetManager.manageButtonLabel": "アセットの管理",
- "xpack.canvas.assetModal.copyAssetMessage": "「{id}」をクリップボードにコピーしました",
"xpack.canvas.assetModal.emptyAssetsDescription": "アセットをインポートして開始します",
"xpack.canvas.assetModal.filePickerPromptText": "画像を選択するかドラッグ & ドロップしてください",
"xpack.canvas.assetModal.loadingText": "画像をアップロード中",
@@ -6020,7 +6015,6 @@
"xpack.canvas.customElementModal.nameInputLabel": "名前",
"xpack.canvas.customElementModal.remainingCharactersDescription": "残り {numberOfRemainingCharacter} 文字",
"xpack.canvas.customElementModal.saveButtonLabel": "保存",
- "xpack.canvas.datasourceDatasourceComponent.changeButtonLabel": "要素データソースの変更",
"xpack.canvas.datasourceDatasourceComponent.expressionArgDescription": "データソースの引数は式で制御されます。式エディターを使用して、データソースを修正します。",
"xpack.canvas.datasourceDatasourceComponent.previewButtonLabel": "データをプレビュー",
"xpack.canvas.datasourceDatasourceComponent.saveButtonLabel": "保存",
@@ -6449,8 +6443,6 @@
"xpack.canvas.groupSettings.saveGroupDescription": "ワークパッド全体で再利用できるように、このグループを新規エレメントとして保存します。",
"xpack.canvas.groupSettings.ungroupDescription": "個々のエレメントの設定を編集できるように、 ({uKey}) のグループを解除します。",
"xpack.canvas.helpMenu.appName": "Canvas",
- "xpack.canvas.helpMenu.description": "{CANVAS} に関する情報",
- "xpack.canvas.helpMenu.documentationLinkLabel": "{CANVAS} ドキュメント",
"xpack.canvas.helpMenu.keyboardShortcutsLinkLabel": "キーボードショートカット",
"xpack.canvas.home.myWorkpadsTabLabel": "マイワークパッド",
"xpack.canvas.home.workpadTemplatesTabLabel": "テンプレート",
@@ -6517,7 +6509,6 @@
"xpack.canvas.lib.palettes.yellowBlueLabel": "黄、青",
"xpack.canvas.lib.palettes.yellowGreenLabel": "黄、緑",
"xpack.canvas.lib.palettes.yellowRedLabel": "黄、赤",
- "xpack.canvas.link.errorMessage": "リンクエラー:{message}",
"xpack.canvas.pageConfig.backgroundColorDescription": "HEX、RGB、また HTML 色名が使用できます",
"xpack.canvas.pageConfig.backgroundColorLabel": "背景",
"xpack.canvas.pageConfig.title": "ページ設定",
@@ -6527,7 +6518,6 @@
"xpack.canvas.pageManager.addPageTooltip": "新しいページをこのワークパッドに追加",
"xpack.canvas.pageManager.confirmRemoveDescription": "このページを削除してよろしいですか?",
"xpack.canvas.pageManager.confirmRemoveTitle": "ページを削除",
- "xpack.canvas.pageManager.pageNumberAriaLabel": "ページ番号 {pageNumber} を読み込む",
"xpack.canvas.pageManager.removeButtonLabel": "削除",
"xpack.canvas.pagePreviewPageControls.clonePageAriaLabel": "ページのクローンを作成",
"xpack.canvas.pagePreviewPageControls.clonePageTooltip": "クローンを作成",
@@ -6579,10 +6569,8 @@
"xpack.canvas.savedElementsModal.deleteElementDescription": "このエレメントを削除してよろしいですか?",
"xpack.canvas.savedElementsModal.deleteElementTitle": "要素'{elementName}'を削除しますか?",
"xpack.canvas.savedElementsModal.editElementTitle": "エレメントを編集",
- "xpack.canvas.savedElementsModal.elementsTitle": "エレメント",
"xpack.canvas.savedElementsModal.findElementPlaceholder": "エレメントを検索",
"xpack.canvas.savedElementsModal.modalTitle": "マイエレメント",
- "xpack.canvas.savedElementsModal.myElementsTitle": "マイエレメント",
"xpack.canvas.shareWebsiteFlyout.description": "外部 Web サイトでこのワークパッドの不動バージョンを共有するには、これらの手順に従ってください。現在のワークパッドのビジュアルスナップショットになり、ライブデータにはアクセスできません。",
"xpack.canvas.shareWebsiteFlyout.flyoutCalloutDescription": "共有するには、このワークパッド、{CANVAS} シェアラブルワークパッドランタイム、サンプル {HTML} ファイルを含む {link} を使用します。",
"xpack.canvas.shareWebsiteFlyout.flyoutTitle": "Webサイトで共有",
@@ -6637,13 +6625,10 @@
"xpack.canvas.textStylePicker.styleItalicOption": "斜体",
"xpack.canvas.textStylePicker.styleOptionsControl": "スタイルオプション",
"xpack.canvas.textStylePicker.styleUnderlineOption": "下線",
- "xpack.canvas.timePicker.applyButtonLabel": "適用",
"xpack.canvas.toolbar.editorButtonLabel": "表現エディター",
- "xpack.canvas.toolbar.errorMessage": "ツールバーエラー:{message}",
"xpack.canvas.toolbar.nextPageAriaLabel": "次のページ",
"xpack.canvas.toolbar.pageButtonLabel": "{pageNum}{rest} ページ",
"xpack.canvas.toolbar.previousPageAriaLabel": "前のページ",
- "xpack.canvas.toolbar.workpadManagerCloseButtonLabel": "閉じる",
"xpack.canvas.toolbarTray.closeTrayAriaLabel": "トレイのクローンを作成",
"xpack.canvas.transitions.fade.displayName": "フェード",
"xpack.canvas.transitions.fade.help": "ページからページへフェードします",
@@ -6905,20 +6890,8 @@
"xpack.canvas.units.quickRange.today": "今日",
"xpack.canvas.units.quickRange.yesterday": "昨日",
"xpack.canvas.useCloneWorkpad.clonedWorkpadName": "{workpadName} のコピー",
- "xpack.canvas.varConfig.addButtonLabel": "変数の追加",
- "xpack.canvas.varConfig.addTooltipLabel": "変数の追加",
- "xpack.canvas.varConfig.copyActionButtonLabel": "スニペットをコピー",
- "xpack.canvas.varConfig.copyActionTooltipLabel": "変数構文をクリップボードにコピー",
"xpack.canvas.varConfig.copyNotificationDescription": "変数構文がクリップボードにコピーされました",
- "xpack.canvas.varConfig.deleteActionButtonLabel": "変数の削除",
"xpack.canvas.varConfig.deleteNotificationDescription": "変数の削除が正常に完了しました",
- "xpack.canvas.varConfig.editActionButtonLabel": "変数の編集",
- "xpack.canvas.varConfig.emptyDescription": "このワークパッドには現在変数がありません。変数を追加して、共通の値を格納したり、編集したりすることができます。これらの変数は、要素または式エディターで使用できます。",
- "xpack.canvas.varConfig.tableNameLabel": "名前",
- "xpack.canvas.varConfig.tableTypeLabel": "型",
- "xpack.canvas.varConfig.tableValueLabel": "値",
- "xpack.canvas.varConfig.titleLabel": "変数",
- "xpack.canvas.varConfig.titleTooltip": "変数を追加して、共通の値を格納したり、編集したりします",
"xpack.canvas.varConfigDeleteVar.cancelButtonLabel": "キャンセル",
"xpack.canvas.varConfigDeleteVar.deleteButtonLabel": "変数の削除",
"xpack.canvas.varConfigDeleteVar.titleLabel": "変数を削除しますか?",
@@ -6952,7 +6925,6 @@
"xpack.canvas.workpadConfig.USLetterButtonLabel": "US レター",
"xpack.canvas.workpadConfig.widthLabel": "幅",
"xpack.canvas.workpadCreate.createButtonLabel": "ワークパッドを作成",
- "xpack.canvas.workpadHeader.addElementButtonLabel": "エレメントを追加",
"xpack.canvas.workpadHeader.addElementModalCloseButtonLabel": "閉じる",
"xpack.canvas.workpadHeader.fullscreenButtonAriaLabel": "全画面表示",
"xpack.canvas.workpadHeader.fullscreenTooltip": "全画面モードを開始します",
@@ -6999,10 +6971,8 @@
"xpack.canvas.workpadHeaderElementMenu.textMenuItemLabel": "テキスト",
"xpack.canvas.workpadHeaderKioskControl.controlTitle": "全画面ページのサイクル",
"xpack.canvas.workpadHeaderKioskControl.cycleFormLabel": "サイクル間隔を変更",
- "xpack.canvas.workpadHeaderKioskControl.cycleToggleSwitch": "スライドを自動的にサイクル",
"xpack.canvas.workpadHeaderRefreshControlSettings.refreshAriaLabel": "エレメントを更新",
"xpack.canvas.workpadHeaderRefreshControlSettings.refreshTooltip": "データを更新",
- "xpack.canvas.workpadHeaderShareMenu.copyPDFMessage": "{PDF}生成{URL}がクリップボードにコピーされました。",
"xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage": "共有マークアップがクリップボードにコピーされました",
"xpack.canvas.workpadHeaderShareMenu.shareDownloadJSONTitle": "{JSON} をダウンロード",
"xpack.canvas.workpadHeaderShareMenu.shareDownloadPDFTitle": "{PDF}レポート",
@@ -7012,8 +6982,6 @@
"xpack.canvas.workpadHeaderShareMenu.shareWorkpadMessage": "このワークパッドを共有",
"xpack.canvas.workpadHeaderShareMenu.unknownExportErrorMessage": "不明なエクスポートタイプ:{type}",
"xpack.canvas.workpadHeaderShareMenu.unsupportedRendererWarning": "このワークパッドには、{CANVAS}シェアラブルワークパッドランタイムがサポートしていないレンダリング関数が含まれています。これらのエレメントはレンダリングされません:",
- "xpack.canvas.workpadHeaderViewMenu.autoplayOffMenuItemLabel": "自動再生をオフにする",
- "xpack.canvas.workpadHeaderViewMenu.autoplayOnMenuItemLabel": "自動再生をオンにする",
"xpack.canvas.workpadHeaderViewMenu.autoplaySettingsMenuItemLabel": "自動再生設定",
"xpack.canvas.workpadHeaderViewMenu.fullscreenMenuLabel": "全画面モードを開始します",
"xpack.canvas.workpadHeaderViewMenu.hideEditModeLabel": "編集コントロールを非表示にします",
@@ -7022,13 +6990,10 @@
"xpack.canvas.workpadHeaderViewMenu.showEditModeLabel": "編集コントロールを表示します",
"xpack.canvas.workpadHeaderViewMenu.viewMenuButtonLabel": "表示",
"xpack.canvas.workpadHeaderViewMenu.viewMenuLabel": "表示オプション",
- "xpack.canvas.workpadHeaderViewMenu.zoomControlsAriaLabel": "ズームコントロール",
- "xpack.canvas.workpadHeaderViewMenu.zoomControlsTooltip": "ズームコントロール",
"xpack.canvas.workpadHeaderViewMenu.zoomFitToWindowText": "ウィンドウに合わせる",
"xpack.canvas.workpadHeaderViewMenu.zoomInText": "ズームイン",
"xpack.canvas.workpadHeaderViewMenu.zoomMenuItemLabel": "ズーム",
"xpack.canvas.workpadHeaderViewMenu.zoomOutText": "ズームアウト",
- "xpack.canvas.workpadHeaderViewMenu.zoomPanelTitle": "ズーム",
"xpack.canvas.workpadHeaderViewMenu.zoomPrecentageValue": "リセット",
"xpack.canvas.workpadHeaderViewMenu.zoomResetText": "{scalePercentage}%",
"xpack.canvas.workpadImport.filePickerPlaceholder": "ワークパッド {JSON} ファイルをインポート",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 055ccbdde6ae8..51f6378f481e2 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -6015,9 +6015,6 @@
"xpack.banners.settings.textColor.description": "设置横幅广告文本的颜色。{subscriptionLink}",
"xpack.banners.settings.textColor.title": "横幅广告文本颜色",
"xpack.banners.settings.textContent.title": "横幅广告文本",
- "xpack.canvas.app.loadErrorMessage": "消息:{error}",
- "xpack.canvas.app.loadErrorTitle": "Canvas 加载失败",
- "xpack.canvas.app.loadingMessage": "Canvas 正在加载",
"xpack.canvas.appDescription": "以最佳像素展示您的数据。",
"xpack.canvas.argAddPopover.addAriaLabel": "添加参数",
"xpack.canvas.argFormAdvancedFailure.applyButtonLabel": "应用",
@@ -6035,8 +6032,6 @@
"xpack.canvas.asset.deleteAssetTooltip": "删除",
"xpack.canvas.asset.downloadAssetTooltip": "下载",
"xpack.canvas.asset.thumbnailAltText": "资产缩略图",
- "xpack.canvas.assetManager.manageButtonLabel": "管理资产",
- "xpack.canvas.assetModal.copyAssetMessage": "已将“{id}”复制到剪贴板",
"xpack.canvas.assetModal.emptyAssetsDescription": "导入您的资产以开始",
"xpack.canvas.assetModal.filePickerPromptText": "选择或拖放图像",
"xpack.canvas.assetModal.loadingText": "正在上传图像",
@@ -6059,7 +6054,6 @@
"xpack.canvas.customElementModal.nameInputLabel": "名称",
"xpack.canvas.customElementModal.remainingCharactersDescription": "还剩 {numberOfRemainingCharacter} 个字符",
"xpack.canvas.customElementModal.saveButtonLabel": "保存",
- "xpack.canvas.datasourceDatasourceComponent.changeButtonLabel": "更改元素数据源",
"xpack.canvas.datasourceDatasourceComponent.expressionArgDescription": "数据源包含由表达式控制的参数。使用表达式编辑器可修改数据源。",
"xpack.canvas.datasourceDatasourceComponent.previewButtonLabel": "预览数据",
"xpack.canvas.datasourceDatasourceComponent.saveButtonLabel": "保存",
@@ -6489,8 +6483,6 @@
"xpack.canvas.groupSettings.saveGroupDescription": "将此组另存为新元素,以在整个 Workpad 重复使用。",
"xpack.canvas.groupSettings.ungroupDescription": "取消分组 ({uKey}) 以编辑各个元素设置。",
"xpack.canvas.helpMenu.appName": "Canvas",
- "xpack.canvas.helpMenu.description": "有关 {CANVAS} 特定信息",
- "xpack.canvas.helpMenu.documentationLinkLabel": "{CANVAS} 文档",
"xpack.canvas.helpMenu.keyboardShortcutsLinkLabel": "快捷键",
"xpack.canvas.home.myWorkpadsTabLabel": "我的 Workpad",
"xpack.canvas.home.workpadTemplatesTabLabel": "模板",
@@ -6557,7 +6549,6 @@
"xpack.canvas.lib.palettes.yellowBlueLabel": "黄、蓝",
"xpack.canvas.lib.palettes.yellowGreenLabel": "黄、绿",
"xpack.canvas.lib.palettes.yellowRedLabel": "黄、红",
- "xpack.canvas.link.errorMessage": "链接错误:{message}",
"xpack.canvas.pageConfig.backgroundColorDescription": "接受 HEX、RGB 或 HTML 颜色名称",
"xpack.canvas.pageConfig.backgroundColorLabel": "背景",
"xpack.canvas.pageConfig.title": "页面设置",
@@ -6567,7 +6558,6 @@
"xpack.canvas.pageManager.addPageTooltip": "将新页面添加到此 Workpad",
"xpack.canvas.pageManager.confirmRemoveDescription": "确定要移除此页面?",
"xpack.canvas.pageManager.confirmRemoveTitle": "移除页面",
- "xpack.canvas.pageManager.pageNumberAriaLabel": "加载页码 {pageNumber}",
"xpack.canvas.pageManager.removeButtonLabel": "移除",
"xpack.canvas.pagePreviewPageControls.clonePageAriaLabel": "克隆页面",
"xpack.canvas.pagePreviewPageControls.clonePageTooltip": "克隆",
@@ -6619,10 +6609,8 @@
"xpack.canvas.savedElementsModal.deleteElementDescription": "确定要删除此元素?",
"xpack.canvas.savedElementsModal.deleteElementTitle": "删除元素“{elementName}”?",
"xpack.canvas.savedElementsModal.editElementTitle": "编辑元素",
- "xpack.canvas.savedElementsModal.elementsTitle": "元素",
"xpack.canvas.savedElementsModal.findElementPlaceholder": "查找元素",
"xpack.canvas.savedElementsModal.modalTitle": "我的元素",
- "xpack.canvas.savedElementsModal.myElementsTitle": "我的元素",
"xpack.canvas.shareWebsiteFlyout.description": "按照以下步骤在外部网站上共享此 Workpad 的静态版本。其将是当前 Workpad 的可视化快照,对实时数据没有访问权限。",
"xpack.canvas.shareWebsiteFlyout.flyoutCalloutDescription": "要尝试共享,可以{link},其包含此 Workpad、{CANVAS} Shareable Workpad Runtime 及示例 {HTML} 文件。",
"xpack.canvas.shareWebsiteFlyout.flyoutTitle": "在网站上共享",
@@ -6677,13 +6665,10 @@
"xpack.canvas.textStylePicker.styleItalicOption": "斜体",
"xpack.canvas.textStylePicker.styleOptionsControl": "样式选项",
"xpack.canvas.textStylePicker.styleUnderlineOption": "下划线",
- "xpack.canvas.timePicker.applyButtonLabel": "应用",
"xpack.canvas.toolbar.editorButtonLabel": "表达式编辑器",
- "xpack.canvas.toolbar.errorMessage": "工具栏错误:{message}",
"xpack.canvas.toolbar.nextPageAriaLabel": "下一页",
"xpack.canvas.toolbar.pageButtonLabel": "第 {pageNum}{rest} 页",
"xpack.canvas.toolbar.previousPageAriaLabel": "上一页",
- "xpack.canvas.toolbar.workpadManagerCloseButtonLabel": "关闭",
"xpack.canvas.toolbarTray.closeTrayAriaLabel": "关闭托盘",
"xpack.canvas.transitions.fade.displayName": "淡化",
"xpack.canvas.transitions.fade.help": "从一页淡入到下一页",
@@ -6949,20 +6934,8 @@
"xpack.canvas.units.time.minutes": "{minutes, plural, other {# 分钟}}",
"xpack.canvas.units.time.seconds": "{seconds, plural, other {# 秒}}",
"xpack.canvas.useCloneWorkpad.clonedWorkpadName": "{workpadName} 副本",
- "xpack.canvas.varConfig.addButtonLabel": "添加变量",
- "xpack.canvas.varConfig.addTooltipLabel": "添加变量",
- "xpack.canvas.varConfig.copyActionButtonLabel": "复制代码片段",
- "xpack.canvas.varConfig.copyActionTooltipLabel": "将变量语法复制到剪贴板",
"xpack.canvas.varConfig.copyNotificationDescription": "变量语法已复制到剪贴板",
- "xpack.canvas.varConfig.deleteActionButtonLabel": "删除变量",
"xpack.canvas.varConfig.deleteNotificationDescription": "变量已成功删除",
- "xpack.canvas.varConfig.editActionButtonLabel": "编辑变量",
- "xpack.canvas.varConfig.emptyDescription": "此 Workpad 当前没有变量。您可以添加变量以存储和编辑公用值。这样,便可以在元素中或表达式编辑器中使用这些变量。",
- "xpack.canvas.varConfig.tableNameLabel": "名称",
- "xpack.canvas.varConfig.tableTypeLabel": "类型",
- "xpack.canvas.varConfig.tableValueLabel": "值",
- "xpack.canvas.varConfig.titleLabel": "变量",
- "xpack.canvas.varConfig.titleTooltip": "添加变量以存储和编辑公用值",
"xpack.canvas.varConfigDeleteVar.cancelButtonLabel": "取消",
"xpack.canvas.varConfigDeleteVar.deleteButtonLabel": "删除变量",
"xpack.canvas.varConfigDeleteVar.titleLabel": "删除变量?",
@@ -6996,7 +6969,6 @@
"xpack.canvas.workpadConfig.USLetterButtonLabel": "美国信函",
"xpack.canvas.workpadConfig.widthLabel": "宽",
"xpack.canvas.workpadCreate.createButtonLabel": "创建 Workpad",
- "xpack.canvas.workpadHeader.addElementButtonLabel": "添加元素",
"xpack.canvas.workpadHeader.addElementModalCloseButtonLabel": "关闭",
"xpack.canvas.workpadHeader.cycleIntervalDaysText": "每 {days} {days, plural, other {天}}",
"xpack.canvas.workpadHeader.cycleIntervalHoursText": "每 {hours} {hours, plural, other {小时}}",
@@ -7047,10 +7019,8 @@
"xpack.canvas.workpadHeaderElementMenu.textMenuItemLabel": "文本",
"xpack.canvas.workpadHeaderKioskControl.controlTitle": "循环播放全屏页面",
"xpack.canvas.workpadHeaderKioskControl.cycleFormLabel": "更改循环播放时间间隔",
- "xpack.canvas.workpadHeaderKioskControl.cycleToggleSwitch": "自动循环播放幻灯片",
"xpack.canvas.workpadHeaderRefreshControlSettings.refreshAriaLabel": "刷新元素",
"xpack.canvas.workpadHeaderRefreshControlSettings.refreshTooltip": "刷新数据",
- "xpack.canvas.workpadHeaderShareMenu.copyPDFMessage": "{PDF} 生成 {URL} 已复制到您的剪贴板。",
"xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage": "已将共享标记复制到剪贴板",
"xpack.canvas.workpadHeaderShareMenu.shareDownloadJSONTitle": "下载为 {JSON}",
"xpack.canvas.workpadHeaderShareMenu.shareDownloadPDFTitle": "{PDF} 报告",
@@ -7060,8 +7030,6 @@
"xpack.canvas.workpadHeaderShareMenu.shareWorkpadMessage": "共享此 Workpad",
"xpack.canvas.workpadHeaderShareMenu.unknownExportErrorMessage": "未知导出类型:{type}",
"xpack.canvas.workpadHeaderShareMenu.unsupportedRendererWarning": "此 Workpad 包含 {CANVAS} Shareable Workpad Runtime 不支持的呈现函数。将不会呈现以下元素:",
- "xpack.canvas.workpadHeaderViewMenu.autoplayOffMenuItemLabel": "关闭自动播放",
- "xpack.canvas.workpadHeaderViewMenu.autoplayOnMenuItemLabel": "打开自动播放",
"xpack.canvas.workpadHeaderViewMenu.autoplaySettingsMenuItemLabel": "自动播放设置",
"xpack.canvas.workpadHeaderViewMenu.fullscreenMenuLabel": "进入全屏模式",
"xpack.canvas.workpadHeaderViewMenu.hideEditModeLabel": "隐藏编辑控件",
@@ -7070,13 +7038,10 @@
"xpack.canvas.workpadHeaderViewMenu.showEditModeLabel": "显示编辑控制",
"xpack.canvas.workpadHeaderViewMenu.viewMenuButtonLabel": "查看",
"xpack.canvas.workpadHeaderViewMenu.viewMenuLabel": "查看选项",
- "xpack.canvas.workpadHeaderViewMenu.zoomControlsAriaLabel": "缩放控制",
- "xpack.canvas.workpadHeaderViewMenu.zoomControlsTooltip": "缩放控制",
"xpack.canvas.workpadHeaderViewMenu.zoomFitToWindowText": "适应窗口大小",
"xpack.canvas.workpadHeaderViewMenu.zoomInText": "放大",
"xpack.canvas.workpadHeaderViewMenu.zoomMenuItemLabel": "缩放",
"xpack.canvas.workpadHeaderViewMenu.zoomOutText": "缩小",
- "xpack.canvas.workpadHeaderViewMenu.zoomPanelTitle": "缩放",
"xpack.canvas.workpadHeaderViewMenu.zoomPrecentageValue": "重置",
"xpack.canvas.workpadHeaderViewMenu.zoomResetText": "{scalePercentage}%",
"xpack.canvas.workpadImport.filePickerPlaceholder": "导入 Workpad {JSON} 文件",
From d5a760cfd9811d85d5c53eab44fc812a4b733263 Mon Sep 17 00:00:00 2001
From: Tyler Smalley
Date: Wed, 23 Jun 2021 18:12:56 -0700
Subject: [PATCH 06/86] [test] Migrates visualize esArchive to kbnArchiver
(#102850)
Signed-off-by: Tyler Smalley
---
test/functional/apps/context/index.js | 10 +-
.../apps/discover/_source_filters.ts | 9 +-
.../fixtures/es_archiver/visualize/data.json | 388 --------------
.../es_archiver/visualize/mappings.json | 487 ------------------
.../fixtures/kbn_archiver/visualize.json | 70 +--
.../test_suites/run_pipeline/index.ts | 8 +-
.../custom_visualizations/index.js | 8 +-
.../tests/vega/vega_map_visualization.ts | 7 +-
8 files changed, 69 insertions(+), 918 deletions(-)
delete mode 100644 test/functional/fixtures/es_archiver/visualize/data.json
delete mode 100644 test/functional/fixtures/es_archiver/visualize/mappings.json
diff --git a/test/functional/apps/context/index.js b/test/functional/apps/context/index.js
index 7612dae338d9f..031171a58718b 100644
--- a/test/functional/apps/context/index.js
+++ b/test/functional/apps/context/index.js
@@ -15,16 +15,18 @@ export default function ({ getService, getPageObjects, loadTestFile }) {
describe('context app', function () {
this.tags('ciGroup1');
- before(async function () {
+ before(async () => {
await browser.setWindowSize(1200, 800);
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
- await esArchiver.load('test/functional/fixtures/es_archiver/visualize');
+ await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/visualize.json');
await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' });
await PageObjects.common.navigateToApp('discover');
});
- after(function unloadMakelogs() {
- return esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
+ after(async () => {
+ await kibanaServer.importExport.unload(
+ 'test/functional/fixtures/kbn_archiver/visualize.json'
+ );
});
loadTestFile(require.resolve('./_context_navigation'));
diff --git a/test/functional/apps/discover/_source_filters.ts b/test/functional/apps/discover/_source_filters.ts
index f3793dc3e0288..6c6979b39702c 100644
--- a/test/functional/apps/discover/_source_filters.ts
+++ b/test/functional/apps/discover/_source_filters.ts
@@ -23,8 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
defaultIndex: 'logstash-*',
});
- log.debug('load kibana index with default index pattern');
- await esArchiver.load('test/functional/fixtures/es_archiver/visualize_source-filters');
+ await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/visualize.json');
// and load a set of makelogs data
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
@@ -43,6 +42,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.common.sleep(1000);
});
+ after(async () => {
+ await kibanaServer.importExport.unload(
+ 'test/functional/fixtures/kbn_archiver/visualize.json'
+ );
+ });
+
it('should not get the field referer', async function () {
const fieldNames = await PageObjects.discover.getAllFieldNames();
expect(fieldNames).to.not.contain('referer');
diff --git a/test/functional/fixtures/es_archiver/visualize/data.json b/test/functional/fixtures/es_archiver/visualize/data.json
deleted file mode 100644
index d48aa3e98d18a..0000000000000
--- a/test/functional/fixtures/es_archiver/visualize/data.json
+++ /dev/null
@@ -1,388 +0,0 @@
-{
- "type": "doc",
- "value": {
- "id": "index-pattern:logstash-*",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "index-pattern": {
- "fieldAttrs": "{\"utc_time\":{\"customLabel\":\"UTC time\"}}",
- "fieldFormatMap": "{\"bytes\":{\"id\":\"bytes\"}}",
- "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]",
- "timeFieldName": "@timestamp",
- "title": "logstash-*"
- },
- "migrationVersion": {
- "index-pattern": "7.11.0"
- },
- "references": [
- ],
- "type": "index-pattern"
- },
- "type": "_doc"
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "index-pattern:logstash*",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "index-pattern": {
- "fieldFormatMap": "{\"bytes\":{\"id\":\"bytes\"}}",
- "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]",
- "title": "logstash*"
- },
- "migrationVersion": {
- "index-pattern": "7.11.0"
- },
- "references": [
- ],
- "type": "index-pattern"
- },
- "type": "_doc"
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "index-pattern:long-window-logstash-*",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "index-pattern": {
- "fieldFormatMap": "{\"bytes\":{\"id\":\"bytes\"}}",
- "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]",
- "timeFieldName": "@timestamp",
- "title": "long-window-logstash-*"
- },
- "migrationVersion": {
- "index-pattern": "7.11.0"
- },
- "references": [
- ],
- "type": "index-pattern"
- },
- "type": "_doc"
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "visualization:Shared-Item-Visualization-AreaChart",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "migrationVersion": {
- "visualization": "7.14.0"
- },
- "references": [
- {
- "id": "logstash-*",
- "name": "kibanaSavedObjectMeta.searchSourceJSON.index",
- "type": "index-pattern"
- }
- ],
- "type": "visualization",
- "visualization": {
- "description": "AreaChart",
- "kibanaSavedObjectMeta": {
- "searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"
- },
- "title": "Shared-Item Visualization AreaChart",
- "uiStateJSON": "{}",
- "version": 1,
- "visState": "{\"title\":\"New Visualization\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}"
- }
- },
- "type": "_doc"
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "visualization:Visualization-AreaChart",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "migrationVersion": {
- "visualization": "7.14.0"
- },
- "references": [
- {
- "id": "logstash-*",
- "name": "kibanaSavedObjectMeta.searchSourceJSON.index",
- "type": "index-pattern"
- }
- ],
- "type": "visualization",
- "visualization": {
- "description": "AreaChart",
- "kibanaSavedObjectMeta": {
- "searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"
- },
- "title": "Visualization AreaChart",
- "uiStateJSON": "{}",
- "version": 1,
- "visState": "{\"title\":\"Visualization AreaChart\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":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,\"lineWidth\":2,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"labels\":{},\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"isVislibVis\":true,\"detailedTooltip\":true,\"fittingFunction\":\"zero\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"timeRange\":{\"from\":\"now-15m\",\"to\":\"now\"},\"useNormalizedEsInterval\":true,\"scaleMetricValues\":false,\"interval\":\"auto\",\"drop_partials\":false,\"min_doc_count\":1,\"extended_bounds\":{}}}]}"
- }
- },
- "type": "_doc"
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "visualization:68305470-87bc-11e9-a991-3b492a7c3e09",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "migrationVersion": {
- "visualization": "7.14.0"
- },
- "references": [
- {
- "id": "logstash-*",
- "name": "control_0_index_pattern",
- "type": "index-pattern"
- },
- {
- "id": "logstash-*",
- "name": "control_1_index_pattern",
- "type": "index-pattern"
- }
- ],
- "type": "visualization",
- "updated_at": "2019-06-05T18:04:48.310Z",
- "visualization": {
- "description": "",
- "kibanaSavedObjectMeta": {
- "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"
- },
- "title": "chained input control",
- "uiStateJSON": "{}",
- "version": 1,
- "visState": "{\"title\":\"chained input control\",\"type\":\"input_control_vis\",\"params\":{\"controls\":[{\"id\":\"1559757816862\",\"fieldName\":\"geo.src\",\"parent\":\"\",\"label\":\"\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"},\"indexPatternRefName\":\"control_0_index_pattern\"},{\"id\":\"1559757836347\",\"fieldName\":\"clientip\",\"parent\":\"1559757816862\",\"label\":\"\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"},\"indexPatternRefName\":\"control_1_index_pattern\"}],\"updateFiltersOnChange\":false,\"useTimeFilter\":false,\"pinFilters\":false},\"aggs\":[]}"
- }
- },
- "type": "_doc"
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "visualization:64983230-87bf-11e9-a991-3b492a7c3e09",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "migrationVersion": {
- "visualization": "7.14.0"
- },
- "references": [
- {
- "id": "logstash-*",
- "name": "control_0_index_pattern",
- "type": "index-pattern"
- }
- ],
- "type": "visualization",
- "updated_at": "2019-06-05T18:26:10.771Z",
- "visualization": {
- "description": "",
- "kibanaSavedObjectMeta": {
- "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"
- },
- "title": "dynamic options input control",
- "uiStateJSON": "{}",
- "version": 1,
- "visState": "{\"title\":\"dynamic options input control\",\"type\":\"input_control_vis\",\"params\":{\"controls\":[{\"id\":\"1559759127876\",\"fieldName\":\"geo.src\",\"parent\":\"\",\"label\":\"\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"},\"indexPatternRefName\":\"control_0_index_pattern\"}],\"updateFiltersOnChange\":false,\"useTimeFilter\":false,\"pinFilters\":false},\"aggs\":[]}"
- }
- },
- "type": "_doc"
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "visualization:5d2de430-87c0-11e9-a991-3b492a7c3e09",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "migrationVersion": {
- "visualization": "7.14.0"
- },
- "references": [
- {
- "id": "logstash-*",
- "name": "control_0_index_pattern",
- "type": "index-pattern"
- },
- {
- "id": "logstash-*",
- "name": "control_1_index_pattern",
- "type": "index-pattern"
- }
- ],
- "type": "visualization",
- "updated_at": "2019-06-05T18:33:07.827Z",
- "visualization": {
- "description": "",
- "kibanaSavedObjectMeta": {
- "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"
- },
- "title": "chained input control with dynamic options",
- "uiStateJSON": "{}",
- "version": 1,
- "visState": "{\"title\":\"chained input control with dynamic options\",\"type\":\"input_control_vis\",\"params\":{\"controls\":[{\"id\":\"1559759550755\",\"fieldName\":\"machine.os.raw\",\"parent\":\"\",\"label\":\"\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"},\"indexPatternRefName\":\"control_0_index_pattern\"},{\"id\":\"1559759557302\",\"fieldName\":\"geo.src\",\"parent\":\"1559759550755\",\"label\":\"\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"},\"indexPatternRefName\":\"control_1_index_pattern\"}],\"updateFiltersOnChange\":false,\"useTimeFilter\":false,\"pinFilters\":false},\"aggs\":[]}"
- }
- },
- "type": "_doc"
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "index-pattern:test_index*",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "index-pattern": {
- "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"message.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"message\"}}},{\"name\":\"user\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"user.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"user\"}}}]",
- "title": "test_index*"
- },
- "migrationVersion": {
- "index-pattern": "7.11.0"
- },
- "references": [
- ],
- "type": "index-pattern"
- },
- "type": "_doc"
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "visualization:AreaChart-no-date-field",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "migrationVersion": {
- "visualization": "7.14.0"
- },
- "references": [
- {
- "id": "test_index*",
- "name": "kibanaSavedObjectMeta.searchSourceJSON.index",
- "type": "index-pattern"
- }
- ],
- "type": "visualization",
- "visualization": {
- "description": "AreaChart",
- "kibanaSavedObjectMeta": {
- "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"
- },
- "title": "AreaChart [no date field]",
- "uiStateJSON": "{}",
- "version": 1,
- "visState": "{\"title\":\"AreaChart [no date field]\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"addTooltip\":true,\"addLegend\":true,\"times\":[],\"addTimeMarker\":false,\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"isVislibVis\":true,\"detailedTooltip\":true,\"fittingFunction\":\"zero\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}}]}"
- }
- },
- "type": "_doc"
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "index-pattern:log*",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "index-pattern": {
- "fieldFormatMap": "{\"bytes\":{\"id\":\"bytes\"}}",
- "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]",
- "title": "log*"
- },
- "migrationVersion": {
- "index-pattern": "7.11.0"
- },
- "references": [
- ],
- "type": "index-pattern"
- },
- "type": "_doc"
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "visualization:AreaChart-no-time-filter",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "migrationVersion": {
- "visualization": "7.14.0"
- },
- "references": [
- {
- "id": "log*",
- "name": "kibanaSavedObjectMeta.searchSourceJSON.index",
- "type": "index-pattern"
- }
- ],
- "type": "visualization",
- "visualization": {
- "description": "AreaChart",
- "kibanaSavedObjectMeta": {
- "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"
- },
- "title": "AreaChart [no time filter]",
- "uiStateJSON": "{}",
- "version": 1,
- "visState": "{\"title\":\"AreaChart [no time filter]\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"addTooltip\":true,\"addLegend\":true,\"times\":[],\"addTimeMarker\":false,\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"isVislibVis\":true,\"detailedTooltip\":true,\"fittingFunction\":\"zero\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}}]}"
- }
- },
- "type": "_doc"
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "visualization:VegaMap",
- "index": ".kibana",
- "source": {
- "coreMigrationVersion": "7.14.0",
- "migrationVersion": {
- "visualization": "7.14.0"
- },
- "references": [
- ],
- "type": "visualization",
- "visualization": {
- "description": "VegaMap",
- "kibanaSavedObjectMeta": {
- "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"
- },
- "title": "VegaMap",
- "uiStateJSON": "{}",
- "version": 1,
- "visState": "{\"aggs\":[],\"params\":{\"spec\":\"{\\n $schema: https://vega.github.io/schema/vega/v5.json\\n config: {\\n kibana: {type: \\\"map\\\", latitude: 25, longitude: -70, zoom: 3}\\n }\\n data: [\\n {\\n name: table\\n url: {\\n index: kibana_sample_data_flights\\n %context%: true\\n // Uncomment to enable time filtering\\n // %timefield%: timestamp\\n body: {\\n size: 0\\n aggs: {\\n origins: {\\n terms: {field: \\\"OriginAirportID\\\", size: 10000}\\n aggs: {\\n originLocation: {\\n top_hits: {\\n size: 1\\n _source: {\\n includes: [\\\"OriginLocation\\\", \\\"Origin\\\"]\\n }\\n }\\n }\\n distinations: {\\n terms: {field: \\\"DestAirportID\\\", size: 10000}\\n aggs: {\\n destLocation: {\\n top_hits: {\\n size: 1\\n _source: {\\n includes: [\\\"DestLocation\\\"]\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n format: {property: \\\"aggregations.origins.buckets\\\"}\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n originLocation.hits.hits[0]._source.OriginLocation.lon\\n originLocation.hits.hits[0]._source.OriginLocation.lat\\n ]\\n }\\n ]\\n }\\n {\\n name: selectedDatum\\n on: [\\n {trigger: \\\"!selected\\\", remove: true}\\n {trigger: \\\"selected\\\", insert: \\\"selected\\\"}\\n ]\\n }\\n ]\\n signals: [\\n {\\n name: selected\\n value: null\\n on: [\\n {events: \\\"@airport:mouseover\\\", update: \\\"datum\\\"}\\n {events: \\\"@airport:mouseout\\\", update: \\\"null\\\"}\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: airportSize\\n type: linear\\n domain: {data: \\\"table\\\", field: \\\"doc_count\\\"}\\n range: [\\n {signal: \\\"zoom*zoom*0.2+1\\\"}\\n {signal: \\\"zoom*zoom*10+1\\\"}\\n ]\\n }\\n ]\\n marks: [\\n {\\n type: group\\n from: {\\n facet: {\\n name: facetedDatum\\n data: selectedDatum\\n field: distinations.buckets\\n }\\n }\\n data: [\\n {\\n name: facetDatumElems\\n source: facetedDatum\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n destLocation.hits.hits[0]._source.DestLocation.lon\\n destLocation.hits.hits[0]._source.DestLocation.lat\\n ]\\n }\\n {type: \\\"formula\\\", expr: \\\"{x:parent.x, y:parent.y}\\\", as: \\\"source\\\"}\\n {type: \\\"formula\\\", expr: \\\"{x:datum.x, y:datum.y}\\\", as: \\\"target\\\"}\\n {type: \\\"linkpath\\\", shape: \\\"diagonal\\\"}\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: lineThickness\\n type: log\\n clamp: true\\n range: [1, 8]\\n }\\n {\\n name: lineOpacity\\n type: log\\n clamp: true\\n range: [0.2, 0.8]\\n }\\n ]\\n marks: [\\n {\\n from: {data: \\\"facetDatumElems\\\"}\\n type: path\\n interactive: false\\n encode: {\\n update: {\\n path: {field: \\\"path\\\"}\\n stroke: {value: \\\"black\\\"}\\n strokeWidth: {scale: \\\"lineThickness\\\", field: \\\"doc_count\\\"}\\n strokeOpacity: {scale: \\\"lineOpacity\\\", field: \\\"doc_count\\\"}\\n }\\n }\\n }\\n ]\\n }\\n {\\n name: airport\\n type: symbol\\n from: {data: \\\"table\\\"}\\n encode: {\\n update: {\\n size: {scale: \\\"airportSize\\\", field: \\\"doc_count\\\"}\\n xc: {signal: \\\"datum.x\\\"}\\n yc: {signal: \\\"datum.y\\\"}\\n tooltip: {\\n signal: \\\"{title: datum.originLocation.hits.hits[0]._source.Origin + ' (' + datum.key + ')', connnections: length(datum.distinations.buckets), flights: datum.doc_count}\\\"\\n }\\n }\\n }\\n }\\n ]\\n}\"},\"title\":\"[Flights] Airport Connections (Hover Over Airport)\",\"type\":\"vega\"}"
- }
- },
- "type": "_doc"
- }
-}
\ No newline at end of file
diff --git a/test/functional/fixtures/es_archiver/visualize/mappings.json b/test/functional/fixtures/es_archiver/visualize/mappings.json
deleted file mode 100644
index d032352d9a688..0000000000000
--- a/test/functional/fixtures/es_archiver/visualize/mappings.json
+++ /dev/null
@@ -1,487 +0,0 @@
-{
- "type": "index",
- "value": {
- "aliases": {
- ".kibana_$KIBANA_PACKAGE_VERSION": {},
- ".kibana": {}
- },
- "index": ".kibana_$KIBANA_PACKAGE_VERSION_001",
- "mappings": {
- "_meta": {
- "migrationMappingPropertyHashes": {
- "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd",
- "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724",
- "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724",
- "config": "c63748b75f39d0c54de12d12c1ccbc20",
- "core-usage-stats": "3d1b76c39bfb2cc8296b024d73854724",
- "coreMigrationVersion": "2f4316de49999235636386fe51dc06c1",
- "dashboard": "40554caf09725935e2c02e02563a2d07",
- "index-pattern": "45915a1ad866812242df474eb0479052",
- "kql-telemetry": "d12a98a6f19a2d273696597547e064ee",
- "legacy-url-alias": "6155300fd11a00e23d5cbaa39f0fce0a",
- "migrationVersion": "4a1746014a75ade3a714e1db5763276f",
- "namespace": "2f4316de49999235636386fe51dc06c1",
- "namespaces": "2f4316de49999235636386fe51dc06c1",
- "originId": "2f4316de49999235636386fe51dc06c1",
- "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9",
- "references": "7997cf5a56cc02bdc9c93361bde732b0",
- "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4",
- "search": "db2c00e39b36f40930a3b9fc71c823e1",
- "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724",
- "telemetry": "36a616f7026dfa617d6655df850fe16d",
- "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf",
- "type": "2f4316de49999235636386fe51dc06c1",
- "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3",
- "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3",
- "updated_at": "00da57df13e94e9d98437d13ace4bfe0",
- "url": "c7f66a0df8b1b52f17c28c4adb111105",
- "usage-counters": "8cc260bdceffec4ffc3ad165c97dc1b4",
- "visualization": "f819cf6636b75c9e76ba733a0c6ef355"
- }
- },
- "dynamic": "strict",
- "properties": {
- "application_usage_daily": {
- "dynamic": "false",
- "properties": {
- "timestamp": {
- "type": "date"
- }
- }
- },
- "application_usage_totals": {
- "dynamic": "false",
- "type": "object"
- },
- "application_usage_transactional": {
- "dynamic": "false",
- "type": "object"
- },
- "config": {
- "dynamic": "false",
- "properties": {
- "buildNum": {
- "type": "keyword"
- }
- }
- },
- "core-usage-stats": {
- "dynamic": "false",
- "type": "object"
- },
- "coreMigrationVersion": {
- "type": "keyword"
- },
- "dashboard": {
- "properties": {
- "description": {
- "type": "text"
- },
- "hits": {
- "doc_values": false,
- "index": false,
- "type": "integer"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "index": false,
- "type": "text"
- }
- }
- },
- "optionsJSON": {
- "index": false,
- "type": "text"
- },
- "panelsJSON": {
- "index": false,
- "type": "text"
- },
- "refreshInterval": {
- "properties": {
- "display": {
- "doc_values": false,
- "index": false,
- "type": "keyword"
- },
- "pause": {
- "doc_values": false,
- "index": false,
- "type": "boolean"
- },
- "section": {
- "doc_values": false,
- "index": false,
- "type": "integer"
- },
- "value": {
- "doc_values": false,
- "index": false,
- "type": "integer"
- }
- }
- },
- "timeFrom": {
- "doc_values": false,
- "index": false,
- "type": "keyword"
- },
- "timeRestore": {
- "doc_values": false,
- "index": false,
- "type": "boolean"
- },
- "timeTo": {
- "doc_values": false,
- "index": false,
- "type": "keyword"
- },
- "title": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- }
- }
- },
- "index-pattern": {
- "dynamic": "false",
- "properties": {
- "title": {
- "type": "text"
- },
- "type": {
- "type": "keyword"
- }
- }
- },
- "kql-telemetry": {
- "properties": {
- "optInCount": {
- "type": "long"
- },
- "optOutCount": {
- "type": "long"
- }
- }
- },
- "legacy-url-alias": {
- "dynamic": "false",
- "properties": {
- "disabled": {
- "type": "boolean"
- },
- "sourceId": {
- "type": "keyword"
- },
- "targetType": {
- "type": "keyword"
- }
- }
- },
- "migrationVersion": {
- "dynamic": "true",
- "properties": {
- "index-pattern": {
- "fields": {
- "keyword": {
- "ignore_above": 256,
- "type": "keyword"
- }
- },
- "type": "text"
- },
- "visualization": {
- "fields": {
- "keyword": {
- "ignore_above": 256,
- "type": "keyword"
- }
- },
- "type": "text"
- }
- }
- },
- "namespace": {
- "type": "keyword"
- },
- "namespaces": {
- "type": "keyword"
- },
- "originId": {
- "type": "keyword"
- },
- "query": {
- "properties": {
- "description": {
- "type": "text"
- },
- "filters": {
- "enabled": false,
- "type": "object"
- },
- "query": {
- "properties": {
- "language": {
- "type": "keyword"
- },
- "query": {
- "index": false,
- "type": "keyword"
- }
- }
- },
- "timefilter": {
- "enabled": false,
- "type": "object"
- },
- "title": {
- "type": "text"
- }
- }
- },
- "references": {
- "properties": {
- "id": {
- "type": "keyword"
- },
- "name": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "sample-data-telemetry": {
- "properties": {
- "installCount": {
- "type": "long"
- },
- "unInstallCount": {
- "type": "long"
- }
- }
- },
- "search": {
- "properties": {
- "columns": {
- "doc_values": false,
- "index": false,
- "type": "keyword"
- },
- "description": {
- "type": "text"
- },
- "grid": {
- "enabled": false,
- "type": "object"
- },
- "hideChart": {
- "doc_values": false,
- "index": false,
- "type": "boolean"
- },
- "hits": {
- "doc_values": false,
- "index": false,
- "type": "integer"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "index": false,
- "type": "text"
- }
- }
- },
- "sort": {
- "doc_values": false,
- "index": false,
- "type": "keyword"
- },
- "title": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- }
- }
- },
- "search-telemetry": {
- "dynamic": "false",
- "type": "object"
- },
- "server": {
- "dynamic": "false",
- "type": "object"
- },
- "telemetry": {
- "properties": {
- "allowChangingOptInStatus": {
- "type": "boolean"
- },
- "enabled": {
- "type": "boolean"
- },
- "lastReported": {
- "type": "date"
- },
- "lastVersionChecked": {
- "type": "keyword"
- },
- "reportFailureCount": {
- "type": "integer"
- },
- "reportFailureVersion": {
- "type": "keyword"
- },
- "sendUsageFrom": {
- "type": "keyword"
- },
- "userHasSeenNotice": {
- "type": "boolean"
- }
- }
- },
- "timelion-sheet": {
- "properties": {
- "description": {
- "type": "text"
- },
- "hits": {
- "type": "integer"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "type": "text"
- }
- }
- },
- "timelion_chart_height": {
- "type": "integer"
- },
- "timelion_columns": {
- "type": "integer"
- },
- "timelion_interval": {
- "type": "keyword"
- },
- "timelion_other_interval": {
- "type": "keyword"
- },
- "timelion_rows": {
- "type": "integer"
- },
- "timelion_sheet": {
- "type": "text"
- },
- "title": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- }
- }
- },
- "type": {
- "type": "keyword"
- },
- "ui-counter": {
- "properties": {
- "count": {
- "type": "integer"
- }
- }
- },
- "ui-metric": {
- "properties": {
- "count": {
- "type": "integer"
- }
- }
- },
- "updated_at": {
- "type": "date"
- },
- "url": {
- "properties": {
- "accessCount": {
- "type": "long"
- },
- "accessDate": {
- "type": "date"
- },
- "createDate": {
- "type": "date"
- },
- "url": {
- "fields": {
- "keyword": {
- "ignore_above": 2048,
- "type": "keyword"
- }
- },
- "type": "text"
- }
- }
- },
- "usage-counters": {
- "dynamic": "false",
- "properties": {
- "domainId": {
- "type": "keyword"
- }
- }
- },
- "visualization": {
- "properties": {
- "description": {
- "type": "text"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "index": false,
- "type": "text"
- }
- }
- },
- "savedSearchRefName": {
- "doc_values": false,
- "index": false,
- "type": "keyword"
- },
- "title": {
- "type": "text"
- },
- "uiStateJSON": {
- "index": false,
- "type": "text"
- },
- "version": {
- "type": "integer"
- },
- "visState": {
- "index": false,
- "type": "text"
- }
- }
- }
- }
- },
- "settings": {
- "index": {
- "auto_expand_replicas": "0-1",
- "number_of_replicas": "0",
- "number_of_shards": "1",
- "priority": "10",
- "refresh_interval": "1s",
- "routing_partition_size": "1"
- }
- }
- }
-}
\ No newline at end of file
diff --git a/test/functional/fixtures/kbn_archiver/visualize.json b/test/functional/fixtures/kbn_archiver/visualize.json
index 758841e8d81ef..660da856964b4 100644
--- a/test/functional/fixtures/kbn_archiver/visualize.json
+++ b/test/functional/fixtures/kbn_archiver/visualize.json
@@ -6,14 +6,14 @@
"timeFieldName": "@timestamp",
"title": "logstash-*"
},
- "coreMigrationVersion": "8.0.0",
+ "coreMigrationVersion": "7.14.0",
"id": "logstash-*",
"migrationVersion": {
"index-pattern": "7.11.0"
},
"references": [],
"type": "index-pattern",
- "version": "WzI2LDJd"
+ "version": "WzEzLDFd"
}
{
@@ -27,10 +27,10 @@
"version": 1,
"visState": "{\"title\":\"chained input control with dynamic options\",\"type\":\"input_control_vis\",\"params\":{\"controls\":[{\"id\":\"1559759550755\",\"fieldName\":\"machine.os.raw\",\"parent\":\"\",\"label\":\"\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"},\"indexPatternRefName\":\"control_0_index_pattern\"},{\"id\":\"1559759557302\",\"fieldName\":\"geo.src\",\"parent\":\"1559759550755\",\"label\":\"\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"},\"indexPatternRefName\":\"control_1_index_pattern\"}],\"updateFiltersOnChange\":false,\"useTimeFilter\":false,\"pinFilters\":false},\"aggs\":[]}"
},
- "coreMigrationVersion": "8.0.0",
+ "coreMigrationVersion": "7.14.0",
"id": "5d2de430-87c0-11e9-a991-3b492a7c3e09",
"migrationVersion": {
- "visualization": "7.13.0"
+ "visualization": "7.14.0"
},
"references": [
{
@@ -46,7 +46,7 @@
],
"type": "visualization",
"updated_at": "2019-06-05T18:33:07.827Z",
- "version": "WzMzLDJd"
+ "version": "WzIwLDFd"
}
{
@@ -60,10 +60,10 @@
"version": 1,
"visState": "{\"title\":\"dynamic options input control\",\"type\":\"input_control_vis\",\"params\":{\"controls\":[{\"id\":\"1559759127876\",\"fieldName\":\"geo.src\",\"parent\":\"\",\"label\":\"\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"},\"indexPatternRefName\":\"control_0_index_pattern\"}],\"updateFiltersOnChange\":false,\"useTimeFilter\":false,\"pinFilters\":false},\"aggs\":[]}"
},
- "coreMigrationVersion": "8.0.0",
+ "coreMigrationVersion": "7.14.0",
"id": "64983230-87bf-11e9-a991-3b492a7c3e09",
"migrationVersion": {
- "visualization": "7.13.0"
+ "visualization": "7.14.0"
},
"references": [
{
@@ -74,7 +74,7 @@
],
"type": "visualization",
"updated_at": "2019-06-05T18:26:10.771Z",
- "version": "WzMyLDJd"
+ "version": "WzE5LDFd"
}
{
@@ -88,10 +88,10 @@
"version": 1,
"visState": "{\"title\":\"chained input control\",\"type\":\"input_control_vis\",\"params\":{\"controls\":[{\"id\":\"1559757816862\",\"fieldName\":\"geo.src\",\"parent\":\"\",\"label\":\"\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"},\"indexPatternRefName\":\"control_0_index_pattern\"},{\"id\":\"1559757836347\",\"fieldName\":\"clientip\",\"parent\":\"1559757816862\",\"label\":\"\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"},\"indexPatternRefName\":\"control_1_index_pattern\"}],\"updateFiltersOnChange\":false,\"useTimeFilter\":false,\"pinFilters\":false},\"aggs\":[]}"
},
- "coreMigrationVersion": "8.0.0",
+ "coreMigrationVersion": "7.14.0",
"id": "68305470-87bc-11e9-a991-3b492a7c3e09",
"migrationVersion": {
- "visualization": "7.13.0"
+ "visualization": "7.14.0"
},
"references": [
{
@@ -107,7 +107,7 @@
],
"type": "visualization",
"updated_at": "2019-06-05T18:04:48.310Z",
- "version": "WzMxLDJd"
+ "version": "WzE4LDFd"
}
{
@@ -115,10 +115,14 @@
"fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"message.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"message\"}}},{\"name\":\"user\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"user.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"user\"}}}]",
"title": "test_index*"
},
+ "coreMigrationVersion": "7.14.0",
"id": "test_index*",
+ "migrationVersion": {
+ "index-pattern": "7.11.0"
+ },
"references": [],
"type": "index-pattern",
- "version": "WzI1LDJd"
+ "version": "WzIxLDFd"
}
{
@@ -132,10 +136,10 @@
"version": 1,
"visState": "{\"title\":\"AreaChart [no date field]\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"addTooltip\":true,\"addLegend\":true,\"times\":[],\"addTimeMarker\":false,\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"isVislibVis\":true,\"detailedTooltip\":true,\"fittingFunction\":\"zero\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}}]}"
},
- "coreMigrationVersion": "8.0.0",
+ "coreMigrationVersion": "7.14.0",
"id": "AreaChart-no-date-field",
"migrationVersion": {
- "visualization": "7.13.0"
+ "visualization": "7.14.0"
},
"references": [
{
@@ -145,7 +149,7 @@
}
],
"type": "visualization",
- "version": "WzM0LDJd"
+ "version": "WzIyLDFd"
}
{
@@ -154,14 +158,14 @@
"fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]",
"title": "log*"
},
- "coreMigrationVersion": "8.0.0",
+ "coreMigrationVersion": "7.14.0",
"id": "log*",
"migrationVersion": {
"index-pattern": "7.11.0"
},
"references": [],
"type": "index-pattern",
- "version": "WzM1LDJd"
+ "version": "WzIzLDFd"
}
{
@@ -175,10 +179,10 @@
"version": 1,
"visState": "{\"title\":\"AreaChart [no time filter]\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"addTooltip\":true,\"addLegend\":true,\"times\":[],\"addTimeMarker\":false,\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"isVislibVis\":true,\"detailedTooltip\":true,\"fittingFunction\":\"zero\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}}]}"
},
- "coreMigrationVersion": "8.0.0",
+ "coreMigrationVersion": "7.14.0",
"id": "AreaChart-no-time-filter",
"migrationVersion": {
- "visualization": "7.13.0"
+ "visualization": "7.14.0"
},
"references": [
{
@@ -188,7 +192,7 @@
}
],
"type": "visualization",
- "version": "WzM2LDJd"
+ "version": "WzI0LDFd"
}
{
@@ -202,10 +206,10 @@
"version": 1,
"visState": "{\"title\":\"New Visualization\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}"
},
- "coreMigrationVersion": "8.0.0",
+ "coreMigrationVersion": "7.14.0",
"id": "Shared-Item-Visualization-AreaChart",
"migrationVersion": {
- "visualization": "7.13.0"
+ "visualization": "7.14.0"
},
"references": [
{
@@ -215,7 +219,7 @@
}
],
"type": "visualization",
- "version": "WzI5LDJd"
+ "version": "WzE2LDFd"
}
{
@@ -229,14 +233,14 @@
"version": 1,
"visState": "{\"aggs\":[],\"params\":{\"spec\":\"{\\n $schema: https://vega.github.io/schema/vega/v5.json\\n config: {\\n kibana: {type: \\\"map\\\", latitude: 25, longitude: -70, zoom: 3}\\n }\\n data: [\\n {\\n name: table\\n url: {\\n index: kibana_sample_data_flights\\n %context%: true\\n // Uncomment to enable time filtering\\n // %timefield%: timestamp\\n body: {\\n size: 0\\n aggs: {\\n origins: {\\n terms: {field: \\\"OriginAirportID\\\", size: 10000}\\n aggs: {\\n originLocation: {\\n top_hits: {\\n size: 1\\n _source: {\\n includes: [\\\"OriginLocation\\\", \\\"Origin\\\"]\\n }\\n }\\n }\\n distinations: {\\n terms: {field: \\\"DestAirportID\\\", size: 10000}\\n aggs: {\\n destLocation: {\\n top_hits: {\\n size: 1\\n _source: {\\n includes: [\\\"DestLocation\\\"]\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n format: {property: \\\"aggregations.origins.buckets\\\"}\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n originLocation.hits.hits[0]._source.OriginLocation.lon\\n originLocation.hits.hits[0]._source.OriginLocation.lat\\n ]\\n }\\n ]\\n }\\n {\\n name: selectedDatum\\n on: [\\n {trigger: \\\"!selected\\\", remove: true}\\n {trigger: \\\"selected\\\", insert: \\\"selected\\\"}\\n ]\\n }\\n ]\\n signals: [\\n {\\n name: selected\\n value: null\\n on: [\\n {events: \\\"@airport:mouseover\\\", update: \\\"datum\\\"}\\n {events: \\\"@airport:mouseout\\\", update: \\\"null\\\"}\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: airportSize\\n type: linear\\n domain: {data: \\\"table\\\", field: \\\"doc_count\\\"}\\n range: [\\n {signal: \\\"zoom*zoom*0.2+1\\\"}\\n {signal: \\\"zoom*zoom*10+1\\\"}\\n ]\\n }\\n ]\\n marks: [\\n {\\n type: group\\n from: {\\n facet: {\\n name: facetedDatum\\n data: selectedDatum\\n field: distinations.buckets\\n }\\n }\\n data: [\\n {\\n name: facetDatumElems\\n source: facetedDatum\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n destLocation.hits.hits[0]._source.DestLocation.lon\\n destLocation.hits.hits[0]._source.DestLocation.lat\\n ]\\n }\\n {type: \\\"formula\\\", expr: \\\"{x:parent.x, y:parent.y}\\\", as: \\\"source\\\"}\\n {type: \\\"formula\\\", expr: \\\"{x:datum.x, y:datum.y}\\\", as: \\\"target\\\"}\\n {type: \\\"linkpath\\\", shape: \\\"diagonal\\\"}\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: lineThickness\\n type: log\\n clamp: true\\n range: [1, 8]\\n }\\n {\\n name: lineOpacity\\n type: log\\n clamp: true\\n range: [0.2, 0.8]\\n }\\n ]\\n marks: [\\n {\\n from: {data: \\\"facetDatumElems\\\"}\\n type: path\\n interactive: false\\n encode: {\\n update: {\\n path: {field: \\\"path\\\"}\\n stroke: {value: \\\"black\\\"}\\n strokeWidth: {scale: \\\"lineThickness\\\", field: \\\"doc_count\\\"}\\n strokeOpacity: {scale: \\\"lineOpacity\\\", field: \\\"doc_count\\\"}\\n }\\n }\\n }\\n ]\\n }\\n {\\n name: airport\\n type: symbol\\n from: {data: \\\"table\\\"}\\n encode: {\\n update: {\\n size: {scale: \\\"airportSize\\\", field: \\\"doc_count\\\"}\\n xc: {signal: \\\"datum.x\\\"}\\n yc: {signal: \\\"datum.y\\\"}\\n tooltip: {\\n signal: \\\"{title: datum.originLocation.hits.hits[0]._source.Origin + ' (' + datum.key + ')', connnections: length(datum.distinations.buckets), flights: datum.doc_count}\\\"\\n }\\n }\\n }\\n }\\n ]\\n}\"},\"title\":\"[Flights] Airport Connections (Hover Over Airport)\",\"type\":\"vega\"}"
},
- "coreMigrationVersion": "8.0.0",
+ "coreMigrationVersion": "7.14.0",
"id": "VegaMap",
"migrationVersion": {
- "visualization": "7.13.0"
+ "visualization": "7.14.0"
},
"references": [],
"type": "visualization",
- "version": "WzM3LDJd"
+ "version": "WzI1LDFd"
}
{
@@ -250,10 +254,10 @@
"version": 1,
"visState": "{\"title\":\"Visualization AreaChart\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":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,\"lineWidth\":2,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"labels\":{},\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"isVislibVis\":true,\"detailedTooltip\":true,\"fittingFunction\":\"zero\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"timeRange\":{\"from\":\"now-15m\",\"to\":\"now\"},\"useNormalizedEsInterval\":true,\"scaleMetricValues\":false,\"interval\":\"auto\",\"drop_partials\":false,\"min_doc_count\":1,\"extended_bounds\":{}}}]}"
},
- "coreMigrationVersion": "8.0.0",
+ "coreMigrationVersion": "7.14.0",
"id": "Visualization-AreaChart",
"migrationVersion": {
- "visualization": "7.13.0"
+ "visualization": "7.14.0"
},
"references": [
{
@@ -263,7 +267,7 @@
}
],
"type": "visualization",
- "version": "WzMwLDJd"
+ "version": "WzE3LDFd"
}
{
@@ -272,14 +276,14 @@
"fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]",
"title": "logstash*"
},
- "coreMigrationVersion": "8.0.0",
+ "coreMigrationVersion": "7.14.0",
"id": "logstash*",
"migrationVersion": {
"index-pattern": "7.11.0"
},
"references": [],
"type": "index-pattern",
- "version": "WzI3LDJd"
+ "version": "WzE0LDFd"
}
{
@@ -289,12 +293,12 @@
"timeFieldName": "@timestamp",
"title": "long-window-logstash-*"
},
- "coreMigrationVersion": "8.0.0",
+ "coreMigrationVersion": "7.14.0",
"id": "long-window-logstash-*",
"migrationVersion": {
"index-pattern": "7.11.0"
},
"references": [],
"type": "index-pattern",
- "version": "WzI4LDJd"
+ "version": "WzE1LDFd"
}
\ No newline at end of file
diff --git a/test/interpreter_functional/test_suites/run_pipeline/index.ts b/test/interpreter_functional/test_suites/run_pipeline/index.ts
index 9cf7e0deba2fa..f8c37bab02b86 100644
--- a/test/interpreter_functional/test_suites/run_pipeline/index.ts
+++ b/test/interpreter_functional/test_suites/run_pipeline/index.ts
@@ -21,7 +21,7 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid
before(async () => {
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
- await esArchiver.load('test/functional/fixtures/es_archiver/visualize_embedding');
+ await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/visualize.json');
await kibanaServer.uiSettings.replace({
'dateFormat:tz': 'Australia/North',
defaultIndex: 'logstash-*',
@@ -32,6 +32,12 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid
await testSubjects.find('pluginContent');
});
+ after(async () => {
+ await kibanaServer.importExport.unload(
+ 'test/functional/fixtures/kbn_archiver/visualize.json'
+ );
+ });
+
loadTestFile(require.resolve('./basic'));
loadTestFile(require.resolve('./tag_cloud'));
loadTestFile(require.resolve('./metric'));
diff --git a/test/plugin_functional/test_suites/custom_visualizations/index.js b/test/plugin_functional/test_suites/custom_visualizations/index.js
index 0998b97da67ff..22b0f21fb983a 100644
--- a/test/plugin_functional/test_suites/custom_visualizations/index.js
+++ b/test/plugin_functional/test_suites/custom_visualizations/index.js
@@ -14,7 +14,7 @@ export default function ({ getService, loadTestFile }) {
describe('custom visualizations', function () {
before(async () => {
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
- await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/visualize');
+ await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/visualize.json');
await kibanaServer.uiSettings.replace({
'dateFormat:tz': 'Australia/North',
defaultIndex: 'logstash-*',
@@ -22,6 +22,12 @@ export default function ({ getService, loadTestFile }) {
await browser.setWindowSize(1300, 900);
});
+ after(async () => {
+ await kibanaServer.importExport.unload(
+ 'test/functional/fixtures/kbn_archiver/visualize.json'
+ );
+ });
+
loadTestFile(require.resolve('./self_changing_vis'));
});
}
diff --git a/test/visual_regression/tests/vega/vega_map_visualization.ts b/test/visual_regression/tests/vega/vega_map_visualization.ts
index 96b08467e4a8f..d891e7f2bab6b 100644
--- a/test/visual_regression/tests/vega/vega_map_visualization.ts
+++ b/test/visual_regression/tests/vega/vega_map_visualization.ts
@@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
+ const kibanaServer = getService('kibanaServer');
const PageObjects = getPageObjects(['common', 'visualize', 'visChart', 'visEditor', 'vegaChart']);
const visualTesting = getService('visualTesting');
@@ -18,12 +19,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await esArchiver.loadIfNeeded(
'test/functional/fixtures/es_archiver/kibana_sample_data_flights'
);
- await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/visualize');
+ await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/visualize.json');
});
after(async () => {
await esArchiver.unload('test/functional/fixtures/es_archiver/kibana_sample_data_flights');
- await esArchiver.unload('test/functional/fixtures/es_archiver/visualize');
+ await kibanaServer.importExport.unload(
+ 'test/functional/fixtures/kbn_archiver/visualize.json'
+ );
});
it('should show map with vega layer', async function () {
From 59423927cb561c88e52cd1940525f8629992f349 Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Thu, 24 Jun 2021 04:49:04 +0100
Subject: [PATCH 07/86] chore(NA): moving @kbn/plugin-helpers into bazel
(#103181)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../monorepo-packages.asciidoc | 1 +
package.json | 2 +-
packages/BUILD.bazel | 1 +
packages/kbn-plugin-helpers/BUILD.bazel | 97 +++++++++++++++++++
packages/kbn-plugin-helpers/package.json | 4 -
packages/kbn-plugin-helpers/tsconfig.json | 2 +-
x-pack/package.json | 1 -
yarn.lock | 2 +-
8 files changed, 102 insertions(+), 8 deletions(-)
create mode 100644 packages/kbn-plugin-helpers/BUILD.bazel
diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc
index 5d7ba22841aa1..ae5c0e9db1ef0 100644
--- a/docs/developer/getting-started/monorepo-packages.asciidoc
+++ b/docs/developer/getting-started/monorepo-packages.asciidoc
@@ -87,6 +87,7 @@ yarn kbn watch-bazel
- @kbn/mapbox-gl
- @kbn/monaco
- @kbn/optimizer
+- @kbn/plugin-helpers
- @kbn/rule-data-utils
- @kbn/securitysolution-es-utils
- @kbn/securitysolution-hook-utils
diff --git a/package.json b/package.json
index f99eb86a43cec..e4ffc0433baab 100644
--- a/package.json
+++ b/package.json
@@ -467,7 +467,7 @@
"@kbn/expect": "link:bazel-bin/packages/kbn-expect",
"@kbn/optimizer": "link:bazel-bin/packages/kbn-optimizer",
"@kbn/plugin-generator": "link:bazel-bin/packages/kbn-plugin-generator",
- "@kbn/plugin-helpers": "link:packages/kbn-plugin-helpers",
+ "@kbn/plugin-helpers": "link:bazel-bin/packages/kbn-plugin-helpers",
"@kbn/pm": "link:packages/kbn-pm",
"@kbn/storybook": "link:bazel-bin/packages/kbn-storybook",
"@kbn/telemetry-tools": "link:bazel-bin/packages/kbn-telemetry-tools",
diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel
index d9e2f0e1f9985..42df6e0d9bcf9 100644
--- a/packages/BUILD.bazel
+++ b/packages/BUILD.bazel
@@ -31,6 +31,7 @@ filegroup(
"//packages/kbn-monaco:build",
"//packages/kbn-optimizer:build",
"//packages/kbn-plugin-generator:build",
+ "//packages/kbn-plugin-helpers:build",
"//packages/kbn-rule-data-utils:build",
"//packages/kbn-securitysolution-list-constants:build",
"//packages/kbn-securitysolution-io-ts-types:build",
diff --git a/packages/kbn-plugin-helpers/BUILD.bazel b/packages/kbn-plugin-helpers/BUILD.bazel
new file mode 100644
index 0000000000000..1a1f3453f768a
--- /dev/null
+++ b/packages/kbn-plugin-helpers/BUILD.bazel
@@ -0,0 +1,97 @@
+
+load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
+load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm")
+
+PKG_BASE_NAME = "kbn-plugin-helpers"
+PKG_REQUIRE_NAME = "@kbn/plugin-helpers"
+
+SOURCE_FILES = glob(
+ [
+ "src/**/*.ts",
+ ],
+ exclude = [
+ "**/*.test.*",
+ ],
+)
+
+SRCS = SOURCE_FILES
+
+filegroup(
+ name = "srcs",
+ srcs = SRCS,
+)
+
+NPM_MODULE_EXTRA_FILES = [
+ "package.json",
+ "README.md"
+]
+
+SRC_DEPS = [
+ "//packages/kbn-dev-utils",
+ "//packages/kbn-optimizer",
+ "//packages/kbn-utils",
+ "@npm//del",
+ "@npm//execa",
+ "@npm//extract-zip",
+ "@npm//globby",
+ "@npm//gulp-zip",
+ "@npm//inquirer",
+ "@npm//load-json-file",
+ "@npm//vinyl-fs",
+]
+
+TYPES_DEPS = [
+ "@npm//@types/extract-zip",
+ "@npm//@types/gulp-zip",
+ "@npm//@types/inquirer",
+ "@npm//@types/jest",
+ "@npm//@types/node",
+ "@npm//@types/vinyl-fs",
+]
+
+DEPS = SRC_DEPS + TYPES_DEPS
+
+ts_config(
+ name = "tsconfig",
+ src = "tsconfig.json",
+ deps = [
+ "//:tsconfig.base.json",
+ ],
+)
+
+ts_project(
+ name = "tsc",
+ args = ['--pretty'],
+ srcs = SRCS,
+ deps = DEPS,
+ declaration = True,
+ declaration_map = True,
+ incremental = True,
+ out_dir = "target",
+ source_map = True,
+ root_dir = "src",
+ tsconfig = ":tsconfig",
+)
+
+js_library(
+ name = PKG_BASE_NAME,
+ srcs = NPM_MODULE_EXTRA_FILES,
+ deps = DEPS + [":tsc"],
+ package_name = PKG_REQUIRE_NAME,
+ visibility = ["//visibility:public"],
+)
+
+pkg_npm(
+ name = "npm_module",
+ deps = [
+ ":%s" % PKG_BASE_NAME,
+ ]
+)
+
+filegroup(
+ name = "build",
+ srcs = [
+ ":npm_module",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/packages/kbn-plugin-helpers/package.json b/packages/kbn-plugin-helpers/package.json
index 36a37075191a3..1f4df52a03304 100644
--- a/packages/kbn-plugin-helpers/package.json
+++ b/packages/kbn-plugin-helpers/package.json
@@ -11,9 +11,5 @@
"types": "target/index.d.ts",
"bin": {
"plugin-helpers": "bin/plugin-helpers.js"
- },
- "scripts": {
- "kbn:bootstrap": "rm -rf target && ../../node_modules/.bin/tsc",
- "kbn:watch": "../../node_modules/.bin/tsc --watch"
}
}
\ No newline at end of file
diff --git a/packages/kbn-plugin-helpers/tsconfig.json b/packages/kbn-plugin-helpers/tsconfig.json
index 87d11843f398a..4348f1e1a7516 100644
--- a/packages/kbn-plugin-helpers/tsconfig.json
+++ b/packages/kbn-plugin-helpers/tsconfig.json
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
- "incremental": false,
+ "incremental": true,
"outDir": "target",
"target": "ES2018",
"declaration": true,
diff --git a/x-pack/package.json b/x-pack/package.json
index 1397a3da81072..1af3d569e41ab 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -26,7 +26,6 @@
"yarn": "^1.21.1"
},
"devDependencies": {
- "@kbn/plugin-helpers": "link:../packages/kbn-plugin-helpers",
"@kbn/test": "link:../packages/kbn-test"
}
}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 64e01ac0475d5..fb24b33a2c48a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2701,7 +2701,7 @@
version "0.0.0"
uid ""
-"@kbn/plugin-helpers@link:packages/kbn-plugin-helpers":
+"@kbn/plugin-helpers@link:bazel-bin/packages/kbn-plugin-helpers":
version "0.0.0"
uid ""
From 17adfacb1b2f4e9519f5b06af88a55fd2d862e8e Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Thu, 24 Jun 2021 04:50:49 +0100
Subject: [PATCH 08/86] chore(NA): moving @kbn/cli-dev-mode into bazel
(#103067)
* chore(NA): moving @kbn/cli-dev-mode into bazel
* chore(NA): merge and solve conflicts with master
---
.../monorepo-packages.asciidoc | 1 +
package.json | 2 +-
packages/BUILD.bazel | 1 +
packages/kbn-cli-dev-mode/BUILD.bazel | 103 ++++++++++++++++++
packages/kbn-cli-dev-mode/package.json | 5 -
packages/kbn-cli-dev-mode/tsconfig.json | 3 +-
yarn.lock | 2 +-
7 files changed, 109 insertions(+), 8 deletions(-)
create mode 100644 packages/kbn-cli-dev-mode/BUILD.bazel
diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc
index ae5c0e9db1ef0..217645b903818 100644
--- a/docs/developer/getting-started/monorepo-packages.asciidoc
+++ b/docs/developer/getting-started/monorepo-packages.asciidoc
@@ -70,6 +70,7 @@ yarn kbn watch-bazel
- @kbn/apm-utils
- @kbn/babel-code-parser
- @kbn/babel-preset
+- @kbn/cli-dev-mode
- @kbn/config
- @kbn/config-schema
- @kbn/crypto
diff --git a/package.json b/package.json
index e4ffc0433baab..0d1d5ae020219 100644
--- a/package.json
+++ b/package.json
@@ -457,7 +457,7 @@
"@jest/reporters": "^26.6.2",
"@kbn/babel-code-parser": "link:bazel-bin/packages/kbn-babel-code-parser",
"@kbn/babel-preset": "link:bazel-bin/packages/kbn-babel-preset",
- "@kbn/cli-dev-mode": "link:packages/kbn-cli-dev-mode",
+ "@kbn/cli-dev-mode": "link:bazel-bin/packages/kbn-cli-dev-mode",
"@kbn/dev-utils": "link:bazel-bin/packages/kbn-dev-utils",
"@kbn/docs-utils": "link:bazel-bin/packages/kbn-docs-utils",
"@kbn/es": "link:bazel-bin/packages/kbn-es",
diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel
index 42df6e0d9bcf9..1094a2def3e70 100644
--- a/packages/BUILD.bazel
+++ b/packages/BUILD.bazel
@@ -12,6 +12,7 @@ filegroup(
"//packages/kbn-apm-utils:build",
"//packages/kbn-babel-code-parser:build",
"//packages/kbn-babel-preset:build",
+ "//packages/kbn-cli-dev-mode:build",
"//packages/kbn-common-utils:build",
"//packages/kbn-config:build",
"//packages/kbn-config-schema:build",
diff --git a/packages/kbn-cli-dev-mode/BUILD.bazel b/packages/kbn-cli-dev-mode/BUILD.bazel
new file mode 100644
index 0000000000000..ab1b6601f429b
--- /dev/null
+++ b/packages/kbn-cli-dev-mode/BUILD.bazel
@@ -0,0 +1,103 @@
+load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
+load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm")
+
+PKG_BASE_NAME = "kbn-cli-dev-mode"
+PKG_REQUIRE_NAME = "@kbn/cli-dev-mode"
+
+SOURCE_FILES = glob(
+ [
+ "src/**/*.ts",
+ ],
+ exclude = ["**/*.test.*"],
+)
+
+SRCS = SOURCE_FILES
+
+filegroup(
+ name = "srcs",
+ srcs = SRCS,
+)
+
+NPM_MODULE_EXTRA_FILES = [
+ "package.json",
+ "README.md"
+]
+
+SRC_DEPS = [
+ "//packages/kbn-config",
+ "//packages/kbn-config-schema",
+ "//packages/kbn-dev-utils",
+ "//packages/kbn-logging",
+ "//packages/kbn-optimizer",
+ "//packages/kbn-server-http-tools",
+ "//packages/kbn-std",
+ "//packages/kbn-utils",
+ "@npm//@hapi/h2o2",
+ "@npm//@hapi/hapi",
+ "@npm//argsplit",
+ "@npm//chokidar",
+ "@npm//elastic-apm-node",
+ "@npm//execa",
+ "@npm//getopts",
+ "@npm//lodash",
+ "@npm//moment",
+ "@npm//rxjs",
+ "@npm//supertest",
+]
+
+TYPES_DEPS = [
+ "@npm//@types/hapi__h2o2",
+ "@npm//@types/hapi__hapi",
+ "@npm//@types/getopts",
+ "@npm//@types/jest",
+ "@npm//@types/lodash",
+ "@npm//@types/node",
+ "@npm//@types/supertest",
+]
+
+DEPS = SRC_DEPS + TYPES_DEPS
+
+ts_config(
+ name = "tsconfig",
+ src = "tsconfig.json",
+ deps = [
+ "//:tsconfig.base.json",
+ ],
+)
+
+ts_project(
+ name = "tsc",
+ args = ['--pretty'],
+ srcs = SRCS,
+ deps = DEPS,
+ declaration = True,
+ declaration_map = True,
+ incremental = True,
+ out_dir = "target",
+ source_map = True,
+ root_dir = "src",
+ tsconfig = ":tsconfig",
+)
+
+js_library(
+ name = PKG_BASE_NAME,
+ srcs = NPM_MODULE_EXTRA_FILES,
+ deps = DEPS + [":tsc"],
+ package_name = PKG_REQUIRE_NAME,
+ visibility = ["//visibility:public"],
+)
+
+pkg_npm(
+ name = "npm_module",
+ deps = [
+ ":%s" % PKG_BASE_NAME,
+ ]
+)
+
+filegroup(
+ name = "build",
+ srcs = [
+ ":npm_module",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/packages/kbn-cli-dev-mode/package.json b/packages/kbn-cli-dev-mode/package.json
index cf6fcfd88a26d..ac86ee2ef369b 100644
--- a/packages/kbn-cli-dev-mode/package.json
+++ b/packages/kbn-cli-dev-mode/package.json
@@ -5,11 +5,6 @@
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0",
"private": true,
- "scripts": {
- "build": "../../node_modules/.bin/tsc",
- "kbn:bootstrap": "yarn build",
- "kbn:watch": "yarn build --watch"
- },
"kibana": {
"devOnly": true
}
diff --git a/packages/kbn-cli-dev-mode/tsconfig.json b/packages/kbn-cli-dev-mode/tsconfig.json
index 4436d27dbff88..0c71ad8e245d4 100644
--- a/packages/kbn-cli-dev-mode/tsconfig.json
+++ b/packages/kbn-cli-dev-mode/tsconfig.json
@@ -1,10 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
- "incremental": false,
+ "incremental": true,
"outDir": "./target",
"declaration": true,
"declarationMap": true,
+ "rootDir": "./src",
"sourceMap": true,
"sourceRoot": "../../../../packages/kbn-cli-dev-mode/src",
"types": [
diff --git a/yarn.lock b/yarn.lock
index fb24b33a2c48a..48c21914784dd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2617,7 +2617,7 @@
version "0.0.0"
uid ""
-"@kbn/cli-dev-mode@link:packages/kbn-cli-dev-mode":
+"@kbn/cli-dev-mode@link:bazel-bin/packages/kbn-cli-dev-mode":
version "0.0.0"
uid ""
From da7cdb67b79dde5b8cca33242e1c60ea07db3713 Mon Sep 17 00:00:00 2001
From: Jason Stoltzfus
Date: Thu, 24 Jun 2021 01:02:46 -0400
Subject: [PATCH 09/86] [App Search] Fixed Documents view for Editor and
Analyst roles (#103113)
* Updated search-ui version
1.6.0 adds the ability to authenticate without a search key, which
we need for this fix.
* Updated Documents page to not use a search key
This change updates the page to rely on basic authentication rather
than a search key.
As part of that, we needed to create a proxy endpoint for search-ui
to post to, rather than going directly to the ent-search API.
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
package.json | 4 +-
.../search_experience/search_experience.tsx | 9 ++--
.../server/routes/app_search/search.ts | 23 +++++++++
yarn.lock | 49 +++++++++----------
4 files changed, 55 insertions(+), 30 deletions(-)
diff --git a/package.json b/package.json
index 0d1d5ae020219..ecedb64c343ec 100644
--- a/package.json
+++ b/package.json
@@ -109,10 +109,10 @@
"@elastic/maki": "6.3.0",
"@elastic/node-crypto": "1.2.1",
"@elastic/numeral": "^2.5.1",
- "@elastic/react-search-ui": "^1.5.1",
+ "@elastic/react-search-ui": "^1.6.0",
"@elastic/request-crypto": "1.1.4",
"@elastic/safer-lodash-set": "link:bazel-bin/packages/elastic-safer-lodash-set",
- "@elastic/search-ui-app-search-connector": "^1.5.0",
+ "@elastic/search-ui-app-search-connector": "^1.6.0",
"@elastic/ui-ace": "0.2.3",
"@hapi/accept": "^5.0.2",
"@hapi/boom": "^9.1.1",
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx
index 709dfc69905f0..8416974ad7a2e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx
@@ -18,7 +18,7 @@ import { i18n } from '@kbn/i18n';
import './search_experience.scss';
-import { externalUrl } from '../../../../shared/enterprise_search_url';
+import { HttpLogic } from '../../../../shared/http';
import { useLocalStorage } from '../../../../shared/use_local_storage';
import { EngineLogic } from '../../engine';
@@ -52,7 +52,8 @@ const DEFAULT_SORT_OPTIONS: SortOption[] = [
export const SearchExperience: React.FC = () => {
const { engine } = useValues(EngineLogic);
- const endpointBase = externalUrl.enterpriseSearchUrl;
+ const { http } = useValues(HttpLogic);
+ const endpointBase = http.basePath.prepend('/api/app_search/search-ui');
const [showCustomizationModal, setShowCustomizationModal] = useState(false);
const openCustomizationModal = () => setShowCustomizationModal(true);
@@ -72,7 +73,9 @@ export const SearchExperience: React.FC = () => {
cacheResponses: false,
endpointBase,
engineName: engine.name,
- searchKey: engine.apiKey,
+ additionalHeaders: {
+ 'kbn-xsrf': true,
+ },
});
const searchProviderConfig = buildSearchUIConfig(connector, engine.schema || {}, fields);
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search.ts
index 016f71e7e65b8..216bffc683265 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/search.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search.ts
@@ -14,6 +14,8 @@
import { schema } from '@kbn/config-schema';
+import { skipBodyValidation } from '../../lib/route_config_helpers';
+
import { RouteDependencies } from '../../plugin';
export function registerSearchRoutes({
@@ -36,4 +38,25 @@ export function registerSearchRoutes({
path: '/api/as/v1/engines/:engineName/search.json',
})
);
+
+ // For the Search UI routes below, Search UI always uses the full API path, like:
+ // "/api/as/v1/engines/{engineName}/search.json". We only have control over the base path
+ // in Search UI, so we created a common basepath of "/api/app_search/search-ui" here that
+ // Search UI can use.
+ //
+ // Search UI *also* uses the click tracking and query suggestion endpoints, however, since the
+ // App Search plugin doesn't use that portion of Search UI, we only set up a proxy for the search endpoint below.
+ router.post(
+ skipBodyValidation({
+ path: '/api/app_search/search-ui/api/as/v1/engines/{engineName}/search.json',
+ validate: {
+ params: schema.object({
+ engineName: schema.string(),
+ }),
+ },
+ }),
+ enterpriseSearchRequestHandler.createRequest({
+ path: '/api/as/v1/engines/:engineName/search.json',
+ })
+ );
}
diff --git a/yarn.lock b/yarn.lock
index 48c21914784dd..448c97ff82469 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1147,7 +1147,7 @@
dependencies:
regenerator-runtime "^0.12.0"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.5.tgz#665450911c6031af38f81db530f387ec04cd9a98"
integrity sha512-121rumjddw9c3NCQ55KGkyE1h/nzWhU/owjhw0l4mQrkzz4x9SGS1X8gFLraHwX7td3Yo4QTL+qj0NcIzN87BA==
@@ -1352,10 +1352,10 @@
dependencies:
"@elastic/apm-rum-core" "^5.11.0"
-"@elastic/app-search-javascript@^7.3.0":
- version "7.8.0"
- resolved "https://registry.yarnpkg.com/@elastic/app-search-javascript/-/app-search-javascript-7.8.0.tgz#cbc7af6bcdd224518f7f595145d6ec744e0b165d"
- integrity sha512-EsAa/E/dQwBO72nrQ9YrXudP9KVY0sVUOvqPKZ3hBj9Mr3+MtWMyIKcyMf09bzdayk4qE+moetYDe5ahVbiA+Q==
+"@elastic/app-search-javascript@^7.13.1":
+ version "7.13.1"
+ resolved "https://registry.yarnpkg.com/@elastic/app-search-javascript/-/app-search-javascript-7.13.1.tgz#07d84daa27e856ad14f3f840683288eab06577f4"
+ integrity sha512-ShzZtGWykLQ0+wXzfk6lJztv68fRcGa8rsLDxJLH/O/2CGY+PJDnj8Qu5lJPmsAPZlZgaB8u7l26YGVPOoaqSA==
dependencies:
object-hash "^1.3.0"
@@ -1539,22 +1539,22 @@
resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.5.1.tgz#96acf39c3d599950646ef8ccfd24a3f057cf4932"
integrity sha512-Tby6TKjixRFY+atVNeYUdGr9m0iaOq8230KTwn8BbUhkh7LwozfgKq0U98HRX7n63ZL62szl+cDKTYzh5WPCFQ==
-"@elastic/react-search-ui-views@1.5.1":
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/@elastic/react-search-ui-views/-/react-search-ui-views-1.5.1.tgz#766cd6b6049f7aa8ab711a6a3a4a060ee5fdd0ce"
- integrity sha512-x4X2xc/69996IEId3VVBTwPICnx/sschnfQ6YmuU3+myRa+VUPkkAWIK/cBcyBW8TNsLtZHWZrjQYi24+H7YWA==
+"@elastic/react-search-ui-views@1.6.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@elastic/react-search-ui-views/-/react-search-ui-views-1.6.0.tgz#7211d47c29ef0636c853721491b9905ac7ae58da"
+ integrity sha512-VADJ18p8HoSPtxKEWFODzId08j0ahyHmHjXv1vP6O/PvtA+ECvi0gDSh/WgdRF792G0e+4d2Dke8LIhxaEvE+w==
dependencies:
downshift "^3.2.10"
rc-pagination "^1.20.1"
react-select "^2.4.4"
-"@elastic/react-search-ui@^1.5.1":
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/@elastic/react-search-ui/-/react-search-ui-1.5.1.tgz#2c261226d2eda3834b4779fbeea5693958169ff2"
- integrity sha512-SI7uOF+jI+Z2D+2otym+4eLBYnocmxa+NA6VPSBrADZXyn8oUEzA4MBtJtxHLtcj64Tj8Riv0tw3t9q3b8iF+w==
+"@elastic/react-search-ui@^1.6.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@elastic/react-search-ui/-/react-search-ui-1.6.0.tgz#8d547d5e1f0a8eebe94798b29966f51643aa886f"
+ integrity sha512-bwSKuCQTQiBWr6QufQtZZGu6rcVYfoiUnyZbwZMS6ojedd5XY7FtMcE+QnR6/IIo0M2IUrxD74XtVNqkUhoCRg==
dependencies:
- "@elastic/react-search-ui-views" "1.5.1"
- "@elastic/search-ui" "1.5.1"
+ "@elastic/react-search-ui-views" "1.6.0"
+ "@elastic/search-ui" "1.6.0"
"@elastic/request-crypto@1.1.4":
version "1.1.4"
@@ -1569,18 +1569,17 @@
version "0.0.0"
uid ""
-"@elastic/search-ui-app-search-connector@^1.5.0":
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/@elastic/search-ui-app-search-connector/-/search-ui-app-search-connector-1.5.0.tgz#d379132c5015775acfaee5322ec019e9c0559ccc"
- integrity sha512-lHuXBjaMaN1fsm1taQMR/7gfpAg4XOsvZOi8u1AoufUw9kGr6Xc00Gznj1qTyH0Qebi2aSmY0NBN6pdIEGvvGQ==
+"@elastic/search-ui-app-search-connector@^1.6.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@elastic/search-ui-app-search-connector/-/search-ui-app-search-connector-1.6.0.tgz#faf1c4a384285648ef7b5ef9cd0e65de0341d2b0"
+ integrity sha512-6oNvqzo4nuutmCM0zEzYrV6VwG8j0ML43SkaG6UrFzLUd6DeWUVGNN+SLNAlfQDWBUjc2m5EGvgdk/0GOWDZeA==
dependencies:
- "@babel/runtime" "^7.5.4"
- "@elastic/app-search-javascript" "^7.3.0"
+ "@elastic/app-search-javascript" "^7.13.1"
-"@elastic/search-ui@1.5.1":
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/@elastic/search-ui/-/search-ui-1.5.1.tgz#14c66a66f5e937ef5e24d6266620b49d986fb3ed"
- integrity sha512-ssfvX1q76X1UwqYASWtBni4PZ+3SYk1PvHmOjpVf9BYai1OqZLGVaj8Sw+cE1ia56zl5In7viCfciC+CP31ovA==
+"@elastic/search-ui@1.6.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@elastic/search-ui/-/search-ui-1.6.0.tgz#8b2286cacff44735be96605b2929ca9b469c78de"
+ integrity sha512-i7htjET9uE4xngyzS8kX3DkSD5XNcr+3FS0Jjx3xRpKVc/dFst4bJyiSeRrQcq+2oBb4mEJJOCFaIrLZg3mdSA==
dependencies:
date-fns "^1.30.1"
deep-equal "^1.0.1"
From 023b163ae0b5ea6b046a70ee0ec918be78554277 Mon Sep 17 00:00:00 2001
From: Constance
Date: Wed, 23 Jun 2021 22:04:08 -0700
Subject: [PATCH 10/86] [App Search] Refactor empty engine polling to
EngineLogic (#103041)
* Set up isEngineEmpty and isEngineSchemaEmpty selectors
+ update selector tests with mock engines to just an `engine` var (I saw Jason do this down below for `searchKey` and liked it, so I copied it)
+ update Documents & Search UI pages to new selectors
* Update EngineOverview to use isEngineEmpty + remove polling
- Per Casey, polling is primarily only needed to dynamically update from empty state to non-empty state, which we're going to handle at the engine level in the next commit
* Add empty engine polling behavior
- Now that both Engines Overview, Documents, and Search UI views have empty states that are hooked up to EngineLogic, this poll will update all of the above pages automatically when a new document comes in!
* [Misc UI polish] Add (+) icon to Index documents button
- to match other create page header buttons
* [PR feedback] Test improvements
Co-authored-by: Jason Stoltzfus
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Jason Stoltzfus
---
.../components/document_creation_button.tsx | 2 +-
.../components/documents/documents.test.tsx | 1 -
.../components/documents/documents.tsx | 4 +-
.../app_search/components/engine/constants.ts | 20 ++
.../components/engine/engine_logic.test.ts | 239 ++++++++++++++++--
.../components/engine/engine_logic.ts | 62 ++++-
.../components/engine/engine_router.test.tsx | 14 +-
.../components/engine/engine_router.tsx | 11 +-
.../engine_overview/engine_overview.test.tsx | 24 +-
.../engine_overview/engine_overview.tsx | 21 +-
.../engine_overview_logic.test.ts | 69 +----
.../engine_overview/engine_overview_logic.ts | 49 +---
.../engine_overview_metrics.test.tsx | 20 ++
.../engine_overview_metrics.tsx | 14 +-
.../components/search_ui/search_ui.tsx | 4 +-
15 files changed, 379 insertions(+), 175 deletions(-)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/document_creation_button.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/document_creation_button.tsx
index cded18094c5f2..482ee282cf464 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/document_creation_button.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/document_creation_button.tsx
@@ -21,7 +21,7 @@ export const DocumentCreationButton: React.FC = () => {
<>
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.test.tsx
index b5b6dd453c9df..7e1b2acc81d18 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.test.tsx
@@ -22,7 +22,6 @@ import { Documents } from '.';
describe('Documents', () => {
const values = {
isMetaEngine: false,
- engine: { document_count: 1 },
myRole: { canManageEngineDocuments: true },
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.tsx
index 62c7759757bda..75044bfcc8fb7 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.tsx
@@ -21,7 +21,7 @@ import { DOCUMENTS_TITLE } from './constants';
import { SearchExperience } from './search_experience';
export const Documents: React.FC = () => {
- const { isMetaEngine, engine } = useValues(EngineLogic);
+ const { isMetaEngine, isEngineEmpty } = useValues(EngineLogic);
const { myRole } = useValues(AppLogic);
return (
@@ -32,7 +32,7 @@ export const Documents: React.FC = () => {
rightSideItems:
myRole.canManageEngineDocuments && !isMetaEngine ? [ ] : [],
}}
- isEmptyState={!engine.document_count}
+ isEmptyState={isEngineEmpty}
emptyState={ }
>
{isMetaEngine && (
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts
new file mode 100644
index 0000000000000..9102f706fdbed
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const POLLING_DURATION = 5000;
+
+export const POLLING_ERROR_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.pollingErrorMessage',
+ { defaultMessage: 'Could not fetch engine data' }
+);
+
+export const POLLING_ERROR_TEXT = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.pollingErrorDescription',
+ { defaultMessage: 'Please check your connection or manually reload the page.' }
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts
index 2b60193d4f7d3..0189edbbf871f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts
@@ -5,10 +5,15 @@
* 2.0.
*/
-import { LogicMounter, mockHttpValues } from '../../../__mocks__/kea_logic';
+import {
+ LogicMounter,
+ mockHttpValues,
+ mockFlashMessageHelpers,
+} from '../../../__mocks__/kea_logic';
import { nextTick } from '@kbn/test/jest';
+import { SchemaType } from '../../../shared/schema/types';
import { ApiTokenTypes } from '../credentials/constants';
import { EngineTypes } from './types';
@@ -16,8 +21,9 @@ import { EngineTypes } from './types';
import { EngineLogic } from './';
describe('EngineLogic', () => {
- const { mount } = new LogicMounter(EngineLogic);
+ const { mount, unmount } = new LogicMounter(EngineLogic);
const { http } = mockHttpValues;
+ const { flashErrorToast } = mockFlashMessageHelpers;
const mockEngineData = {
name: 'some-engine',
@@ -34,7 +40,7 @@ describe('EngineLogic', () => {
sample: false,
isMeta: false,
invalidBoosts: false,
- schema: {},
+ schema: { test: SchemaType.Text },
apiTokens: [],
apiKey: 'some-key',
};
@@ -43,6 +49,8 @@ describe('EngineLogic', () => {
dataLoading: true,
engine: {},
engineName: '',
+ isEngineEmpty: true,
+ isEngineSchemaEmpty: true,
isMetaEngine: false,
isSampleEngine: false,
hasSchemaErrors: false,
@@ -50,6 +58,14 @@ describe('EngineLogic', () => {
hasUnconfirmedSchemaFields: false,
engineNotFound: false,
searchKey: '',
+ intervalId: null,
+ };
+
+ const DEFAULT_VALUES_WITH_ENGINE = {
+ ...DEFAULT_VALUES,
+ engine: mockEngineData,
+ isEngineEmpty: false,
+ isEngineSchemaEmpty: false,
};
beforeEach(() => {
@@ -69,7 +85,7 @@ describe('EngineLogic', () => {
EngineLogic.actions.setEngineData(mockEngineData);
expect(EngineLogic.values).toEqual({
- ...DEFAULT_VALUES,
+ ...DEFAULT_VALUES_WITH_ENGINE,
engine: mockEngineData,
dataLoading: false,
});
@@ -154,6 +170,34 @@ describe('EngineLogic', () => {
});
});
});
+
+ describe('onPollStart', () => {
+ it('should set intervalId', () => {
+ mount({ intervalId: null });
+ EngineLogic.actions.onPollStart(123);
+
+ expect(EngineLogic.values).toEqual({
+ ...DEFAULT_VALUES,
+ intervalId: 123,
+ });
+ });
+
+ describe('onPollStop', () => {
+ // Note: This does have to be a separate action following stopPolling(), rather
+ // than using stopPolling: () => null as a reducer. If you do that, then the ID
+ // gets cleared before the actual poll interval does & the poll interval never clears :doh:
+
+ it('should reset intervalId', () => {
+ mount({ intervalId: 123 });
+ EngineLogic.actions.onPollStop();
+
+ expect(EngineLogic.values).toEqual({
+ ...DEFAULT_VALUES,
+ intervalId: null,
+ });
+ });
+ });
+ });
});
describe('listeners', () => {
@@ -170,28 +214,156 @@ describe('EngineLogic', () => {
expect(EngineLogic.actions.setEngineData).toHaveBeenCalledWith(mockEngineData);
});
- it('handles errors', async () => {
+ it('handles 4xx errors', async () => {
mount();
jest.spyOn(EngineLogic.actions, 'setEngineNotFound');
- http.get.mockReturnValue(Promise.reject('An error occured'));
+ http.get.mockReturnValue(Promise.reject({ response: { status: 404 } }));
EngineLogic.actions.initializeEngine();
await nextTick();
expect(EngineLogic.actions.setEngineNotFound).toHaveBeenCalledWith(true);
});
+
+ it('handles 5xx errors', async () => {
+ mount();
+ http.get.mockReturnValue(Promise.reject('An error occured'));
+
+ EngineLogic.actions.initializeEngine();
+ await nextTick();
+
+ expect(flashErrorToast).toHaveBeenCalledWith('Could not fetch engine data', {
+ text: expect.stringContaining('Please check your connection'),
+ toastLifeTimeMs: 3750,
+ });
+ });
+ });
+
+ describe('pollEmptyEngine', () => {
+ beforeEach(() => jest.useFakeTimers());
+ afterEach(() => jest.clearAllTimers());
+ afterAll(() => jest.useRealTimers());
+
+ it('starts a poll', () => {
+ mount();
+ jest.spyOn(global, 'setInterval');
+ jest.spyOn(EngineLogic.actions, 'onPollStart');
+
+ EngineLogic.actions.pollEmptyEngine();
+
+ expect(global.setInterval).toHaveBeenCalled();
+ expect(EngineLogic.actions.onPollStart).toHaveBeenCalled();
+ });
+
+ it('polls for engine data if the current engine is empty', () => {
+ mount({ engine: {} });
+ jest.spyOn(EngineLogic.actions, 'initializeEngine');
+
+ EngineLogic.actions.pollEmptyEngine();
+
+ jest.advanceTimersByTime(5000);
+ expect(EngineLogic.actions.initializeEngine).toHaveBeenCalledTimes(1);
+ jest.advanceTimersByTime(5000);
+ expect(EngineLogic.actions.initializeEngine).toHaveBeenCalledTimes(2);
+ });
+
+ it('cancels the poll if the current engine changed from empty to non-empty', () => {
+ mount({ engine: mockEngineData });
+ jest.spyOn(EngineLogic.actions, 'stopPolling');
+ jest.spyOn(EngineLogic.actions, 'initializeEngine');
+
+ EngineLogic.actions.pollEmptyEngine();
+
+ jest.advanceTimersByTime(5000);
+ expect(EngineLogic.actions.stopPolling).toHaveBeenCalled();
+ expect(EngineLogic.actions.initializeEngine).not.toHaveBeenCalled();
+ });
+
+ it('does not create new polls if one already exists', () => {
+ jest.spyOn(global, 'setInterval');
+ mount({ intervalId: 123 });
+
+ EngineLogic.actions.pollEmptyEngine();
+
+ expect(global.setInterval).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('stopPolling', () => {
+ it('clears the poll interval and unsets the intervalId', () => {
+ jest.spyOn(global, 'clearInterval');
+ mount({ intervalId: 123 });
+
+ EngineLogic.actions.stopPolling();
+
+ expect(global.clearInterval).toHaveBeenCalledWith(123);
+ expect(EngineLogic.values.intervalId).toEqual(null);
+ });
+
+ it('does not clearInterval if a poll has not been started', () => {
+ jest.spyOn(global, 'clearInterval');
+ mount({ intervalId: null });
+
+ EngineLogic.actions.stopPolling();
+
+ expect(global.clearInterval).not.toHaveBeenCalled();
+ });
});
});
describe('selectors', () => {
+ describe('isEngineEmpty', () => {
+ it('returns true if the engine contains no documents', () => {
+ const engine = { ...mockEngineData, document_count: 0 };
+ mount({ engine });
+
+ expect(EngineLogic.values).toEqual({
+ ...DEFAULT_VALUES_WITH_ENGINE,
+ engine,
+ isEngineEmpty: true,
+ });
+ });
+
+ it('returns true if the engine is not yet initialized', () => {
+ mount({ engine: {} });
+
+ expect(EngineLogic.values).toEqual({
+ ...DEFAULT_VALUES,
+ isEngineEmpty: true,
+ });
+ });
+ });
+
+ describe('isEngineSchemaEmpty', () => {
+ it('returns true if the engine schema contains no fields', () => {
+ const engine = { ...mockEngineData, schema: {} };
+ mount({ engine });
+
+ expect(EngineLogic.values).toEqual({
+ ...DEFAULT_VALUES_WITH_ENGINE,
+ engine,
+ isEngineSchemaEmpty: true,
+ });
+ });
+
+ it('returns true if the engine is not yet initialized', () => {
+ mount({ engine: {} });
+
+ expect(EngineLogic.values).toEqual({
+ ...DEFAULT_VALUES,
+ isEngineSchemaEmpty: true,
+ });
+ });
+ });
+
describe('isSampleEngine', () => {
it('should be set based on engine.sample', () => {
- const mockSampleEngine = { ...mockEngineData, sample: true };
- mount({ engine: mockSampleEngine });
+ const engine = { ...mockEngineData, sample: true };
+ mount({ engine });
expect(EngineLogic.values).toEqual({
- ...DEFAULT_VALUES,
- engine: mockSampleEngine,
+ ...DEFAULT_VALUES_WITH_ENGINE,
+ engine,
isSampleEngine: true,
});
});
@@ -199,12 +371,12 @@ describe('EngineLogic', () => {
describe('isMetaEngine', () => {
it('should be set based on engine.type', () => {
- const mockMetaEngine = { ...mockEngineData, type: EngineTypes.meta };
- mount({ engine: mockMetaEngine });
+ const engine = { ...mockEngineData, type: EngineTypes.meta };
+ mount({ engine });
expect(EngineLogic.values).toEqual({
- ...DEFAULT_VALUES,
- engine: mockMetaEngine,
+ ...DEFAULT_VALUES_WITH_ENGINE,
+ engine,
isMetaEngine: true,
});
});
@@ -212,17 +384,17 @@ describe('EngineLogic', () => {
describe('hasSchemaErrors', () => {
it('should be set based on engine.activeReindexJob.numDocumentsWithErrors', () => {
- const mockSchemaEngine = {
+ const engine = {
...mockEngineData,
activeReindexJob: {
numDocumentsWithErrors: 10,
},
};
- mount({ engine: mockSchemaEngine });
+ mount({ engine });
expect(EngineLogic.values).toEqual({
- ...DEFAULT_VALUES,
- engine: mockSchemaEngine,
+ ...DEFAULT_VALUES_WITH_ENGINE,
+ engine,
hasSchemaErrors: true,
});
});
@@ -230,7 +402,7 @@ describe('EngineLogic', () => {
describe('hasSchemaConflicts', () => {
it('should be set based on engine.schemaConflicts', () => {
- const mockSchemaEngine = {
+ const engine = {
...mockEngineData,
schemaConflicts: {
someSchemaField: {
@@ -241,11 +413,11 @@ describe('EngineLogic', () => {
},
},
};
- mount({ engine: mockSchemaEngine });
+ mount({ engine });
expect(EngineLogic.values).toEqual({
- ...DEFAULT_VALUES,
- engine: mockSchemaEngine,
+ ...DEFAULT_VALUES_WITH_ENGINE,
+ engine,
hasSchemaConflicts: true,
});
});
@@ -253,15 +425,15 @@ describe('EngineLogic', () => {
describe('hasUnconfirmedSchemaFields', () => {
it('should be set based on engine.unconfirmedFields', () => {
- const mockUnconfirmedFieldsEngine = {
+ const engine = {
...mockEngineData,
unconfirmedFields: ['new_field_1', 'new_field_2'],
};
- mount({ engine: mockUnconfirmedFieldsEngine });
+ mount({ engine });
expect(EngineLogic.values).toEqual({
- ...DEFAULT_VALUES,
- engine: mockUnconfirmedFieldsEngine,
+ ...DEFAULT_VALUES_WITH_ENGINE,
+ engine,
hasUnconfirmedSchemaFields: true,
});
});
@@ -292,7 +464,7 @@ describe('EngineLogic', () => {
mount({ engine });
expect(EngineLogic.values).toEqual({
- ...DEFAULT_VALUES,
+ ...DEFAULT_VALUES_WITH_ENGINE,
engine,
searchKey: 'search-123xyz',
});
@@ -312,11 +484,22 @@ describe('EngineLogic', () => {
mount({ engine });
expect(EngineLogic.values).toEqual({
- ...DEFAULT_VALUES,
+ ...DEFAULT_VALUES_WITH_ENGINE,
engine,
searchKey: '',
});
});
});
});
+
+ describe('events', () => {
+ it('calls stopPolling before unmount', () => {
+ mount();
+ // Has to be a const to check state after unmount
+ const stopPollingSpy = jest.spyOn(EngineLogic.actions, 'stopPolling');
+
+ unmount();
+ expect(stopPollingSpy).toHaveBeenCalled();
+ });
+ });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts
index 5cbe89b364859..bfa77450176f6 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts
@@ -7,16 +7,20 @@
import { kea, MakeLogicType } from 'kea';
+import { flashErrorToast } from '../../../shared/flash_messages';
import { HttpLogic } from '../../../shared/http';
import { ApiTokenTypes } from '../credentials/constants';
import { ApiToken } from '../credentials/types';
+import { POLLING_DURATION, POLLING_ERROR_TITLE, POLLING_ERROR_TEXT } from './constants';
import { EngineDetails, EngineTypes } from './types';
interface EngineValues {
dataLoading: boolean;
engine: Partial;
engineName: string;
+ isEngineEmpty: boolean;
+ isEngineSchemaEmpty: boolean;
isMetaEngine: boolean;
isSampleEngine: boolean;
hasSchemaErrors: boolean;
@@ -24,6 +28,7 @@ interface EngineValues {
hasUnconfirmedSchemaFields: boolean;
engineNotFound: boolean;
searchKey: string;
+ intervalId: number | null;
}
interface EngineActions {
@@ -32,6 +37,10 @@ interface EngineActions {
setEngineNotFound(notFound: boolean): { notFound: boolean };
clearEngine(): void;
initializeEngine(): void;
+ pollEmptyEngine(): void;
+ onPollStart(intervalId: number): { intervalId: number };
+ stopPolling(): void;
+ onPollStop(): void;
}
export const EngineLogic = kea>({
@@ -42,6 +51,10 @@ export const EngineLogic = kea>({
setEngineNotFound: (notFound) => ({ notFound }),
clearEngine: true,
initializeEngine: true,
+ pollEmptyEngine: true,
+ onPollStart: (intervalId) => ({ intervalId }),
+ stopPolling: true,
+ onPollStop: true,
},
reducers: {
dataLoading: [
@@ -72,8 +85,20 @@ export const EngineLogic = kea>({
clearEngine: () => false,
},
],
+ intervalId: [
+ null,
+ {
+ onPollStart: (_, { intervalId }) => intervalId,
+ onPollStop: () => null,
+ },
+ ],
},
selectors: ({ selectors }) => ({
+ isEngineEmpty: [() => [selectors.engine], (engine) => !engine.document_count],
+ isEngineSchemaEmpty: [
+ () => [selectors.engine],
+ (engine) => Object.keys(engine.schema || {}).length === 0,
+ ],
isMetaEngine: [() => [selectors.engine], (engine) => engine?.type === EngineTypes.meta],
isSampleEngine: [() => [selectors.engine], (engine) => !!engine?.sample],
// Indexed engines
@@ -100,7 +125,9 @@ export const EngineLogic = kea>({
],
}),
listeners: ({ actions, values }) => ({
- initializeEngine: async () => {
+ initializeEngine: async (_, breakpoint) => {
+ breakpoint(); // Prevents errors if logic unmounts while fetching
+
const { engineName } = values;
const { http } = HttpLogic.values;
@@ -108,8 +135,39 @@ export const EngineLogic = kea>({
const response = await http.get(`/api/app_search/engines/${engineName}`);
actions.setEngineData(response);
} catch (error) {
- actions.setEngineNotFound(true);
+ if (error?.response?.status >= 400 && error?.response?.status < 500) {
+ actions.setEngineNotFound(true);
+ } else {
+ flashErrorToast(POLLING_ERROR_TITLE, {
+ text: POLLING_ERROR_TEXT,
+ toastLifeTimeMs: POLLING_DURATION * 0.75,
+ });
+ }
}
},
+ pollEmptyEngine: () => {
+ if (values.intervalId) return; // Ensure we only have one poll at a time
+
+ const id = window.setInterval(() => {
+ if (values.isEngineEmpty && values.isEngineSchemaEmpty) {
+ actions.initializeEngine(); // Re-fetch engine data when engine is empty
+ } else {
+ actions.stopPolling();
+ }
+ }, POLLING_DURATION);
+
+ actions.onPollStart(id);
+ },
+ stopPolling: () => {
+ if (values.intervalId !== null) {
+ clearInterval(values.intervalId);
+ actions.onPollStop();
+ }
+ },
+ }),
+ events: ({ actions }) => ({
+ beforeUnmount: () => {
+ actions.stopPolling();
+ },
}),
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx
index ee1c0578debfc..ed35bfbe97842 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx
@@ -41,7 +41,13 @@ describe('EngineRouter', () => {
engineNotFound: false,
myRole: {},
};
- const actions = { setEngineName: jest.fn(), initializeEngine: jest.fn(), clearEngine: jest.fn() };
+ const actions = {
+ setEngineName: jest.fn(),
+ initializeEngine: jest.fn(),
+ pollEmptyEngine: jest.fn(),
+ stopPolling: jest.fn(),
+ clearEngine: jest.fn(),
+ };
beforeEach(() => {
setMockValues(values);
@@ -58,12 +64,14 @@ describe('EngineRouter', () => {
expect(actions.setEngineName).toHaveBeenCalledWith('some-engine');
});
- it('initializes/fetches engine API data', () => {
+ it('initializes/fetches engine API data and starts a poll for empty engines', () => {
expect(actions.initializeEngine).toHaveBeenCalled();
+ expect(actions.pollEmptyEngine).toHaveBeenCalled();
});
- it('clears engine on unmount and on update', () => {
+ it('clears engine and stops polling on unmount / on engine change', () => {
unmountHandler();
+ expect(actions.stopPolling).toHaveBeenCalled();
expect(actions.clearEngine).toHaveBeenCalled();
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx
index 0f42483f44e0c..da8dd8467bb61 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx
@@ -66,12 +66,19 @@ export const EngineRouter: React.FC = () => {
const { engineName: engineNameFromUrl } = useParams() as { engineName: string };
const { engineName, dataLoading, engineNotFound } = useValues(EngineLogic);
- const { setEngineName, initializeEngine, clearEngine } = useActions(EngineLogic);
+ const { setEngineName, initializeEngine, pollEmptyEngine, stopPolling, clearEngine } = useActions(
+ EngineLogic
+ );
useEffect(() => {
setEngineName(engineNameFromUrl);
initializeEngine();
- return clearEngine;
+ pollEmptyEngine();
+
+ return () => {
+ stopPolling();
+ clearEngine();
+ };
}, [engineNameFromUrl]);
if (engineNotFound) {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx
index edacd74e046a2..a2e0ba4fcd44d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx
@@ -5,8 +5,7 @@
* 2.0.
*/
-import '../../../__mocks__/shallow_useeffect.mock';
-import { setMockValues, setMockActions } from '../../../__mocks__/kea_logic';
+import { setMockValues } from '../../../__mocks__/kea_logic';
import React from 'react';
@@ -20,18 +19,14 @@ import { EngineOverview } from './';
describe('EngineOverview', () => {
const values = {
dataLoading: false,
- documentCount: 0,
myRole: {},
+ isEngineEmpty: true,
isMetaEngine: false,
};
- const actions = {
- pollForOverviewMetrics: jest.fn(),
- };
beforeEach(() => {
jest.clearAllMocks();
setMockValues(values);
- setMockActions(actions);
});
it('renders', () => {
@@ -39,21 +34,10 @@ describe('EngineOverview', () => {
expect(wrapper.find('[data-test-subj="EngineOverview"]')).toHaveLength(1);
});
- it('initializes data on mount', () => {
- shallow( );
- expect(actions.pollForOverviewMetrics).toHaveBeenCalledTimes(1);
- });
-
- it('renders a loading page template if async data is still loading', () => {
- setMockValues({ ...values, dataLoading: true });
- const wrapper = shallow( );
- expect(wrapper.prop('isLoading')).toEqual(true);
- });
-
describe('EmptyEngineOverview', () => {
it('renders when the engine has no documents & the user can add documents', () => {
const myRole = { canManageEngineDocuments: true, canViewEngineCredentials: true };
- setMockValues({ ...values, myRole, documentCount: 0 });
+ setMockValues({ ...values, myRole });
const wrapper = shallow( );
expect(wrapper.find(EmptyEngineOverview)).toHaveLength(1);
});
@@ -61,7 +45,7 @@ describe('EngineOverview', () => {
describe('EngineOverviewMetrics', () => {
it('renders when the engine has documents', () => {
- setMockValues({ ...values, documentCount: 1 });
+ setMockValues({ ...values, isEngineEmpty: false });
const wrapper = shallow( );
expect(wrapper.find(EngineOverviewMetrics)).toHaveLength(1);
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx
index 4c15ffd8b7f94..a3f98d8c13e8e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx
@@ -5,38 +5,25 @@
* 2.0.
*/
-import React, { useEffect } from 'react';
+import React from 'react';
-import { useActions, useValues } from 'kea';
+import { useValues } from 'kea';
import { AppLogic } from '../../app_logic';
import { EngineLogic } from '../engine';
-import { AppSearchPageTemplate } from '../layout';
import { EmptyEngineOverview } from './engine_overview_empty';
import { EngineOverviewMetrics } from './engine_overview_metrics';
-import { EngineOverviewLogic } from './';
-
export const EngineOverview: React.FC = () => {
const {
myRole: { canManageEngineDocuments, canViewEngineCredentials },
} = useValues(AppLogic);
- const { isMetaEngine } = useValues(EngineLogic);
-
- const { pollForOverviewMetrics } = useActions(EngineOverviewLogic);
- const { dataLoading, documentCount } = useValues(EngineOverviewLogic);
-
- useEffect(() => {
- pollForOverviewMetrics();
- }, []);
-
- if (dataLoading) return ;
+ const { isEngineEmpty, isMetaEngine } = useValues(EngineLogic);
- const engineHasDocuments = documentCount > 0;
const canAddDocuments = canManageEngineDocuments && canViewEngineCredentials;
- const showEngineOverview = engineHasDocuments || !canAddDocuments || isMetaEngine;
+ const showEngineOverview = !isEngineEmpty || !canAddDocuments || isMetaEngine;
return (
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts
index c9c1defd46032..cc677d2642702 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts
@@ -20,7 +20,7 @@ import { nextTick } from '@kbn/test/jest';
import { EngineOverviewLogic } from './';
describe('EngineOverviewLogic', () => {
- const { mount, unmount } = new LogicMounter(EngineOverviewLogic);
+ const { mount } = new LogicMounter(EngineOverviewLogic);
const { http } = mockHttpValues;
const { flashAPIErrors } = mockFlashMessageHelpers;
@@ -41,7 +41,6 @@ describe('EngineOverviewLogic', () => {
queriesPerDay: [],
totalClicks: 0,
totalQueries: 0,
- timeoutId: null,
};
beforeEach(() => {
@@ -54,10 +53,10 @@ describe('EngineOverviewLogic', () => {
});
describe('actions', () => {
- describe('setPolledData', () => {
+ describe('onOverviewMetricsLoad', () => {
it('should set all received data as top-level values and set dataLoading to false', () => {
mount();
- EngineOverviewLogic.actions.setPolledData(mockEngineMetrics);
+ EngineOverviewLogic.actions.onOverviewMetricsLoad(mockEngineMetrics);
expect(EngineOverviewLogic.values).toEqual({
...DEFAULT_VALUES,
@@ -66,34 +65,20 @@ describe('EngineOverviewLogic', () => {
});
});
});
-
- describe('setTimeoutId', () => {
- describe('timeoutId', () => {
- it('should be set to the provided value', () => {
- mount();
- EngineOverviewLogic.actions.setTimeoutId(123);
-
- expect(EngineOverviewLogic.values).toEqual({
- ...DEFAULT_VALUES,
- timeoutId: 123,
- });
- });
- });
- });
});
describe('listeners', () => {
- describe('pollForOverviewMetrics', () => {
- it('fetches data and calls onPollingSuccess', async () => {
+ describe('loadOverviewMetrics', () => {
+ it('fetches data and calls onOverviewMetricsLoad', async () => {
mount();
- jest.spyOn(EngineOverviewLogic.actions, 'onPollingSuccess');
+ jest.spyOn(EngineOverviewLogic.actions, 'onOverviewMetricsLoad');
http.get.mockReturnValueOnce(Promise.resolve(mockEngineMetrics));
- EngineOverviewLogic.actions.pollForOverviewMetrics();
+ EngineOverviewLogic.actions.loadOverviewMetrics();
await nextTick();
expect(http.get).toHaveBeenCalledWith('/api/app_search/engines/some-engine/overview');
- expect(EngineOverviewLogic.actions.onPollingSuccess).toHaveBeenCalledWith(
+ expect(EngineOverviewLogic.actions.onOverviewMetricsLoad).toHaveBeenCalledWith(
mockEngineMetrics
);
});
@@ -102,47 +87,11 @@ describe('EngineOverviewLogic', () => {
mount();
http.get.mockReturnValue(Promise.reject('An error occurred'));
- EngineOverviewLogic.actions.pollForOverviewMetrics();
+ EngineOverviewLogic.actions.loadOverviewMetrics();
await nextTick();
expect(flashAPIErrors).toHaveBeenCalledWith('An error occurred');
});
});
-
- describe('onPollingSuccess', () => {
- it('starts a polling timeout and sets data', async () => {
- mount();
- jest.useFakeTimers();
- jest.spyOn(EngineOverviewLogic.actions, 'setTimeoutId');
- jest.spyOn(EngineOverviewLogic.actions, 'setPolledData');
-
- EngineOverviewLogic.actions.onPollingSuccess(mockEngineMetrics);
-
- expect(setTimeout).toHaveBeenCalledWith(
- EngineOverviewLogic.actions.pollForOverviewMetrics,
- 5000
- );
- expect(EngineOverviewLogic.actions.setTimeoutId).toHaveBeenCalledWith(expect.any(Number));
- expect(EngineOverviewLogic.actions.setPolledData).toHaveBeenCalledWith(mockEngineMetrics);
- });
- });
- });
-
- describe('unmount', () => {
- beforeEach(() => {
- jest.useFakeTimers();
- mount();
- });
-
- it('clears existing polling timeouts on unmount', () => {
- EngineOverviewLogic.actions.setTimeoutId(123);
- unmount();
- expect(clearTimeout).toHaveBeenCalled();
- });
-
- it("does not clear timeout if one hasn't been set", () => {
- unmount();
- expect(clearTimeout).not.toHaveBeenCalled();
- });
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.ts
index 78d5358fc4909..3f9c2e43a332b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.ts
@@ -11,8 +11,6 @@ import { flashAPIErrors } from '../../../shared/flash_messages';
import { HttpLogic } from '../../../shared/http';
import { EngineLogic } from '../engine';
-const POLLING_DURATION = 5000;
-
interface EngineOverviewApiData {
documentCount: number;
startDate: string;
@@ -23,95 +21,74 @@ interface EngineOverviewApiData {
}
interface EngineOverviewValues extends EngineOverviewApiData {
dataLoading: boolean;
- timeoutId: number | null;
}
interface EngineOverviewActions {
- setPolledData(engineMetrics: EngineOverviewApiData): EngineOverviewApiData;
- setTimeoutId(timeoutId: number): { timeoutId: number };
- pollForOverviewMetrics(): void;
- onPollingSuccess(engineMetrics: EngineOverviewApiData): EngineOverviewApiData;
+ loadOverviewMetrics(): void;
+ onOverviewMetricsLoad(response: EngineOverviewApiData): EngineOverviewApiData;
}
export const EngineOverviewLogic = kea
>({
path: ['enterprise_search', 'app_search', 'engine_overview_logic'],
actions: () => ({
- setPolledData: (engineMetrics) => engineMetrics,
- setTimeoutId: (timeoutId) => ({ timeoutId }),
- pollForOverviewMetrics: true,
- onPollingSuccess: (engineMetrics) => engineMetrics,
+ loadOverviewMetrics: true,
+ onOverviewMetricsLoad: (engineMetrics) => engineMetrics,
}),
reducers: () => ({
dataLoading: [
true,
{
- setPolledData: () => false,
+ onOverviewMetricsLoad: () => false,
},
],
startDate: [
'',
{
- setPolledData: (_, { startDate }) => startDate,
+ onOverviewMetricsLoad: (_, { startDate }) => startDate,
},
],
queriesPerDay: [
[],
{
- setPolledData: (_, { queriesPerDay }) => queriesPerDay,
+ onOverviewMetricsLoad: (_, { queriesPerDay }) => queriesPerDay,
},
],
operationsPerDay: [
[],
{
- setPolledData: (_, { operationsPerDay }) => operationsPerDay,
+ onOverviewMetricsLoad: (_, { operationsPerDay }) => operationsPerDay,
},
],
totalQueries: [
0,
{
- setPolledData: (_, { totalQueries }) => totalQueries,
+ onOverviewMetricsLoad: (_, { totalQueries }) => totalQueries,
},
],
totalClicks: [
0,
{
- setPolledData: (_, { totalClicks }) => totalClicks,
+ onOverviewMetricsLoad: (_, { totalClicks }) => totalClicks,
},
],
documentCount: [
0,
{
- setPolledData: (_, { documentCount }) => documentCount,
- },
- ],
- timeoutId: [
- null,
- {
- setTimeoutId: (_, { timeoutId }) => timeoutId,
+ onOverviewMetricsLoad: (_, { documentCount }) => documentCount,
},
],
}),
listeners: ({ actions }) => ({
- pollForOverviewMetrics: async () => {
+ loadOverviewMetrics: async () => {
const { http } = HttpLogic.values;
const { engineName } = EngineLogic.values;
try {
const response = await http.get(`/api/app_search/engines/${engineName}/overview`);
- actions.onPollingSuccess(response);
+ actions.onOverviewMetricsLoad(response);
} catch (e) {
flashAPIErrors(e);
}
},
- onPollingSuccess: (engineMetrics) => {
- const timeoutId = window.setTimeout(actions.pollForOverviewMetrics, POLLING_DURATION);
- actions.setTimeoutId(timeoutId);
- actions.setPolledData(engineMetrics);
- },
- }),
- events: ({ values }) => ({
- beforeUnmount() {
- if (values.timeoutId !== null) clearTimeout(values.timeoutId);
- },
}),
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx
index 620d913c5f9a7..14f182463d837 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { setMockValues, setMockActions } from '../../../__mocks__/kea_logic';
+import '../../../__mocks__/shallow_useeffect.mock';
import '../../__mocks__/engine_logic.mock';
import React from 'react';
@@ -17,6 +19,19 @@ import { TotalStats, TotalCharts, RecentApiLogs } from './components';
import { EngineOverviewMetrics } from './engine_overview_metrics';
describe('EngineOverviewMetrics', () => {
+ const values = {
+ dataLoading: false,
+ };
+ const actions = {
+ loadOverviewMetrics: jest.fn(),
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ setMockValues(values);
+ setMockActions(actions);
+ });
+
it('renders', () => {
const wrapper = shallow( );
@@ -25,4 +40,9 @@ describe('EngineOverviewMetrics', () => {
expect(wrapper.find(TotalCharts)).toHaveLength(1);
expect(wrapper.find(RecentApiLogs)).toHaveLength(1);
});
+
+ it('initializes data on mount', () => {
+ shallow( );
+ expect(actions.loadOverviewMetrics).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx
index b47ae21104ae9..3cc7138623735 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx
@@ -5,7 +5,9 @@
* 2.0.
*/
-import React from 'react';
+import React, { useEffect } from 'react';
+
+import { useActions, useValues } from 'kea';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -15,7 +17,16 @@ import { AppSearchPageTemplate } from '../layout';
import { TotalStats, TotalCharts, RecentApiLogs } from './components';
+import { EngineOverviewLogic } from './';
+
export const EngineOverviewMetrics: React.FC = () => {
+ const { loadOverviewMetrics } = useActions(EngineOverviewLogic);
+ const { dataLoading } = useValues(EngineOverviewLogic);
+
+ useEffect(() => {
+ loadOverviewMetrics();
+ }, []);
+
return (
{
defaultMessage: 'Engine overview',
}),
}}
+ isLoading={dataLoading}
>
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx
index 0ac59a33068ba..9f84bf4bd3b75 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx
@@ -24,7 +24,7 @@ import { SearchUILogic } from './search_ui_logic';
export const SearchUI: React.FC = () => {
const { loadFieldData } = useActions(SearchUILogic);
- const { engine } = useValues(EngineLogic);
+ const { isEngineSchemaEmpty } = useValues(EngineLogic);
useEffect(() => {
loadFieldData();
@@ -34,7 +34,7 @@ export const SearchUI: React.FC = () => {
}
>
From 91ca373000e483cf62135016105f256a73561cf9 Mon Sep 17 00:00:00 2001
From: Constance
Date: Wed, 23 Jun 2021 22:11:17 -0700
Subject: [PATCH 11/86] Convert our full page Loading component to an
Enterprise Search logo (#103189)
- while keeping our component-level LoadingOverlay to a spinner
---
.../public/applications/shared/loading/loading.test.tsx | 6 +++---
.../public/applications/shared/loading/loading.tsx | 8 +++++---
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.test.tsx
index 4ed242c6ed677..eb3e5f027a2d4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.test.tsx
@@ -9,7 +9,7 @@ import React from 'react';
import { shallow } from 'enzyme';
-import { EuiLoadingSpinner } from '@elastic/eui';
+import { EuiLoadingLogo, EuiLoadingSpinner } from '@elastic/eui';
import { Loading, LoadingOverlay } from './';
@@ -17,7 +17,7 @@ describe('Loading', () => {
it('renders', () => {
const wrapper = shallow( );
expect(wrapper.hasClass('enterpriseSearchLoading')).toBe(true);
- expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(1);
+ expect(wrapper.find(EuiLoadingLogo)).toHaveLength(1);
});
});
@@ -25,6 +25,6 @@ describe('LoadingOverlay', () => {
it('renders', () => {
const wrapper = shallow( );
expect(wrapper.hasClass('enterpriseSearchLoadingOverlay')).toBe(true);
- expect(wrapper.find(Loading)).toHaveLength(1);
+ expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(1);
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.tsx
index 627d8386dc1c0..477cc27f5c8ef 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/loading/loading.tsx
@@ -7,18 +7,20 @@
import React from 'react';
-import { EuiLoadingSpinner } from '@elastic/eui';
+import { EuiLoadingLogo, EuiLoadingSpinner } from '@elastic/eui';
import './loading.scss';
export const Loading: React.FC = () => (
-
+
);
export const LoadingOverlay: React.FC = () => (
);
From 2368e63668ebb12924177157ba237a8813c68936 Mon Sep 17 00:00:00 2001
From: Alexander Wert
Date: Thu, 24 Jun 2021 08:27:05 +0200
Subject: [PATCH 12/86] Exploratory View Mobile: Renamed Latency and Throuput
labels to align with metric names in APM (#102711)
---
.../configurations/constants/labels.ts | 4 ++--
.../mobile/kpi_over_time_config.ts | 24 +++++++++----------
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts
index 73739b7db12ef..eb8af4f26c01a 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts
@@ -272,7 +272,7 @@ export const CARRIER_LOCATION = i18n.translate(
export const RESPONSE_LATENCY = i18n.translate(
'xpack.observability.expView.fieldLabels.responseLatency',
{
- defaultMessage: 'Response latency',
+ defaultMessage: 'Latency',
}
);
@@ -294,7 +294,7 @@ export const CPU_USAGE = i18n.translate('xpack.observability.expView.fieldLabels
export const TRANSACTIONS_PER_MINUTE = i18n.translate(
'xpack.observability.expView.fieldLabels.transactionPerMinute',
{
- defaultMessage: 'Transactions per minute',
+ defaultMessage: 'Throughput',
}
);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts
index 2ed4d95760db7..9a2e86a8f7969 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts
@@ -71,18 +71,6 @@ export function getMobileKPIConfig({ indexPattern }: ConfigProps): DataSeries {
id: TRANSACTION_DURATION,
columnType: OPERATION_COLUMN,
},
- {
- label: MEMORY_USAGE,
- field: METRIC_SYSTEM_MEMORY_USAGE,
- id: METRIC_SYSTEM_MEMORY_USAGE,
- columnType: OPERATION_COLUMN,
- },
- {
- label: CPU_USAGE,
- field: METRIC_SYSTEM_CPU_USAGE,
- id: METRIC_SYSTEM_CPU_USAGE,
- columnType: OPERATION_COLUMN,
- },
{
field: RECORDS_FIELD,
id: RECORDS_FIELD,
@@ -95,6 +83,18 @@ export function getMobileKPIConfig({ indexPattern }: ConfigProps): DataSeries {
],
timeScale: 'm',
},
+ {
+ label: MEMORY_USAGE,
+ field: METRIC_SYSTEM_MEMORY_USAGE,
+ id: METRIC_SYSTEM_MEMORY_USAGE,
+ columnType: OPERATION_COLUMN,
+ },
+ {
+ label: CPU_USAGE,
+ field: METRIC_SYSTEM_CPU_USAGE,
+ id: METRIC_SYSTEM_CPU_USAGE,
+ columnType: OPERATION_COLUMN,
+ },
],
},
],
From aee0585bc531d8460d3ea3f5028568910c160374 Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Thu, 24 Jun 2021 09:35:33 +0200
Subject: [PATCH 13/86] [Lens] Do not reset columns on incomplete switch before
closing flyout (#102876)
---
.../dimension_panel/dimension_editor.tsx | 9 +---
.../dimension_panel/dimension_panel.test.tsx | 32 ++++++-------
.../operations/layer_helpers.test.ts | 48 +++++++++++++++++++
.../operations/layer_helpers.ts | 42 +++++++++++-----
.../test/functional/apps/lens/smokescreen.ts | 4 +-
5 files changed, 96 insertions(+), 39 deletions(-)
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx
index b35986c42054d..05100567c1b03 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx
@@ -117,21 +117,14 @@ export function DimensionEditor(props: DimensionEditorProps) {
const setStateWrapper = (
setter: IndexPatternLayer | ((prevLayer: IndexPatternLayer) => IndexPatternLayer)
) => {
- const prevOperationType =
- operationDefinitionMap[state.layers[layerId].columns[columnId]?.operationType]?.input;
-
const hypotheticalLayer = typeof setter === 'function' ? setter(state.layers[layerId]) : setter;
- const hasIncompleteColumns = Boolean(hypotheticalLayer.incompleteColumns?.[columnId]);
setState(
(prevState) => {
const layer = typeof setter === 'function' ? setter(prevState.layers[layerId]) : setter;
return mergeLayer({ state: prevState, layerId, newLayer: layer });
},
{
- isDimensionComplete:
- prevOperationType === 'fullReference'
- ? !hasIncompleteColumns
- : Boolean(hypotheticalLayer.columns[columnId]),
+ isDimensionComplete: Boolean(hypotheticalLayer.columns[columnId]),
}
);
};
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx
index afcecdf5be9b8..d757d8573f25a 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx
@@ -908,20 +908,21 @@ describe('IndexPatternDimensionEditorPanel', () => {
});
});
- it('should clean up when transitioning from incomplete reference-based operations to field operation', () => {
+ it('should keep current state and write incomplete column when transitioning from incomplete reference-based operations to field operation', () => {
+ const baseState = getStateWithColumns({
+ ...defaultProps.state.layers.first.columns,
+ col2: {
+ label: 'Counter rate',
+ dataType: 'number',
+ isBucketed: false,
+ operationType: 'counter_rate',
+ references: ['ref'],
+ },
+ });
wrapper = mount(
);
@@ -932,15 +933,12 @@ describe('IndexPatternDimensionEditorPanel', () => {
.simulate('click');
// Now check that the dimension gets cleaned up on state update
- expect(setState.mock.calls[0]).toEqual([
- expect.any(Function),
- { isDimensionComplete: false },
- ]);
+ expect(setState.mock.calls[0]).toEqual([expect.any(Function), { isDimensionComplete: true }]);
expect(setState.mock.calls[0][0](state)).toEqual({
- ...state,
+ ...baseState,
layers: {
first: {
- ...state.layers.first,
+ ...baseState.layers.first,
incompleteColumns: {
col2: { operationType: 'average' },
},
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts
index 7de1318cbac61..9eedae6d82d43 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts
@@ -1917,6 +1917,54 @@ describe('state_helpers', () => {
})
);
});
+
+ it('should keep state and set incomplete column on incompatible switch', () => {
+ const layer: IndexPatternLayer = {
+ indexPatternId: '1',
+ columnOrder: ['metric', 'ref'],
+ columns: {
+ metric: {
+ dataType: 'number' as const,
+ isBucketed: false,
+ sourceField: 'source',
+ operationType: 'unique_count' as const,
+ filter: { language: 'kuery', query: 'bytes > 4000' },
+ timeShift: '3h',
+ label: 'Cardinality',
+ customLabel: true,
+ },
+ ref: {
+ label: 'Reference',
+ dataType: 'number',
+ isBucketed: false,
+ operationType: 'differences',
+ references: ['metric'],
+ filter: { language: 'kuery', query: 'bytes > 4000' },
+ timeShift: '3h',
+ },
+ },
+ };
+ const result = replaceColumn({
+ layer,
+ indexPattern,
+ columnId: 'ref',
+ op: 'sum',
+ visualizationGroups: [],
+ });
+ expect(result.columnOrder).toEqual(layer.columnOrder);
+ expect(result.columns).toEqual(layer.columns);
+ expect(result.incompleteColumns).toEqual({
+ ref: {
+ operationType: 'sum',
+ filter: {
+ language: 'kuery',
+ query: 'bytes > 4000',
+ },
+ timeScale: undefined,
+ timeShift: '3h',
+ },
+ });
+ });
});
it('should allow making a replacement on an operation that is being referenced, even if it ends up invalid', () => {
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts
index fd3df9f97cecf..b5b1960b7b769 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts
@@ -19,6 +19,7 @@ import {
OperationType,
IndexPatternColumn,
RequiredReference,
+ OperationDefinition,
GenericOperationDefinition,
} from './definitions';
import type {
@@ -532,20 +533,15 @@ export function replaceColumn({
);
}
- // This logic comes after the transitions because they need to look at previous columns
- if (previousDefinition.input === 'fullReference') {
- (previousColumn as ReferenceBasedIndexPatternColumn).references.forEach((id: string) => {
- tempLayer = deleteColumn({
- layer: tempLayer,
- columnId: id,
- indexPattern,
- });
- });
- }
-
if (operationDefinition.input === 'none') {
let newColumn = operationDefinition.buildColumn({ ...baseOptions, layer: tempLayer });
newColumn = copyCustomLabel(newColumn, previousColumn);
+ tempLayer = removeOrphanedColumns(
+ previousDefinition,
+ previousColumn,
+ tempLayer,
+ indexPattern
+ );
const newLayer = { ...tempLayer, columns: { ...tempLayer.columns, [columnId]: newColumn } };
return updateDefaultLabels(
@@ -564,7 +560,6 @@ export function replaceColumn({
} & ColumnAdvancedParams = { operationType: op };
// if no field is available perform a full clean of the column from the layer
if (previousDefinition.input === 'fullReference') {
- tempLayer = deleteColumn({ layer: tempLayer, columnId, indexPattern });
const previousReferenceId = (previousColumn as ReferenceBasedIndexPatternColumn)
.references[0];
const referenceColumn = layer.columns[previousReferenceId];
@@ -598,6 +593,8 @@ export function replaceColumn({
};
}
+ tempLayer = removeOrphanedColumns(previousDefinition, previousColumn, tempLayer, indexPattern);
+
let newColumn = operationDefinition.buildColumn({ ...baseOptions, layer: tempLayer, field });
if (!shouldResetLabel) {
newColumn = copyCustomLabel(newColumn, previousColumn);
@@ -637,6 +634,27 @@ export function replaceColumn({
}
}
+function removeOrphanedColumns(
+ previousDefinition:
+ | OperationDefinition
+ | OperationDefinition
+ | OperationDefinition,
+ previousColumn: IndexPatternColumn,
+ tempLayer: IndexPatternLayer,
+ indexPattern: IndexPattern
+) {
+ if (previousDefinition.input === 'fullReference') {
+ (previousColumn as ReferenceBasedIndexPatternColumn).references.forEach((id: string) => {
+ tempLayer = deleteColumn({
+ layer: tempLayer,
+ columnId: id,
+ indexPattern,
+ });
+ });
+ }
+ return tempLayer;
+}
+
export function canTransition({
layer,
columnId,
diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts
index ec32d7620fcf9..78900e6fabca4 100644
--- a/x-pack/test/functional/apps/lens/smokescreen.ts
+++ b/x-pack/test/functional/apps/lens/smokescreen.ts
@@ -604,7 +604,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
);
});
- it('should not leave an incomplete column in the visualization config with reference-based operations', async () => {
+ it('should revert to previous configuration and not leave an incomplete column in the visualization config with reference-based operations', async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
await PageObjects.lens.goToTimeRange();
@@ -636,7 +636,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.lens.closeDimensionEditor();
expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql(
- undefined
+ 'Moving average of Count of records'
);
});
From 01a486000e52128a173802f715d4fc347665ad7d Mon Sep 17 00:00:00 2001
From: Liza Katz
Date: Thu, 24 Jun 2021 10:50:09 +0300
Subject: [PATCH 14/86] [Search Sessions] Split tasks (#99967)
* cancel the previous session
* split to 3 tasks
* fixes
* cancellation
* updated tests
* split out and improve jest tests
* cleanup previous session properly
* don't fail delete and cancel if item was already cleaned up
* test
* test
* ignore resource_not_found_exception when deleting an already cleared \ expired async search
* jest
* update jest
* api int
* fix jest
* testssss
* Code review @dosant
* types
* remove any
* Fix merge
* type
* test
* jest
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
src/plugins/data/config.ts | 12 +-
.../search/session/session_service.test.ts | 8 +
... => check_non_persiseted_sessions.test.ts} | 295 ++--------------
.../session/check_non_persiseted_sessions.ts | 129 +++++++
.../session/check_persisted_sessions.test.ts | 76 +++++
.../session/check_persisted_sessions.ts | 72 ++++
.../search/session/check_running_sessions.ts | 257 --------------
.../session/expire_persisted_sessions.ts | 74 ++++
.../session/get_search_session_page.test.ts | 282 +++++++++++++++
.../search/session/get_search_session_page.ts | 61 ++++
.../server/search/session/index.ts | 1 -
.../server/search/session/monitoring_task.ts | 119 -------
.../search/session/session_service.test.ts | 4 +
.../server/search/session/session_service.ts | 78 ++++-
.../server/search/session/setup_task.ts | 121 +++++++
.../server/search/session/types.ts | 47 +++
.../session/update_session_status.test.ts | 323 ++++++++++++++++++
.../search/session/update_session_status.ts | 128 +++++++
.../server/search/session/utils.ts | 9 +
x-pack/test/api_integration/config.ts | 1 +
20 files changed, 1446 insertions(+), 651 deletions(-)
rename x-pack/plugins/data_enhanced/server/search/session/{check_running_sessions.test.ts => check_non_persiseted_sessions.test.ts} (65%)
create mode 100644 x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.ts
create mode 100644 x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.test.ts
create mode 100644 x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts
delete mode 100644 x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts
create mode 100644 x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts
create mode 100644 x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.test.ts
create mode 100644 x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts
delete mode 100644 x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts
create mode 100644 x-pack/plugins/data_enhanced/server/search/session/setup_task.ts
create mode 100644 x-pack/plugins/data_enhanced/server/search/session/update_session_status.test.ts
create mode 100644 x-pack/plugins/data_enhanced/server/search/session/update_session_status.ts
diff --git a/src/plugins/data/config.ts b/src/plugins/data/config.ts
index 9306b64019bbc..1b7bfbc09ad16 100644
--- a/src/plugins/data/config.ts
+++ b/src/plugins/data/config.ts
@@ -44,10 +44,20 @@ export const searchSessionsConfigSchema = schema.object({
*/
pageSize: schema.number({ defaultValue: 100 }),
/**
- * trackingInterval controls how often we track search session objects progress
+ * trackingInterval controls how often we track persisted search session objects progress
*/
trackingInterval: schema.duration({ defaultValue: '10s' }),
+ /**
+ * cleanupInterval controls how often we track non-persisted search session objects for cleanup
+ */
+ cleanupInterval: schema.duration({ defaultValue: '60s' }),
+
+ /**
+ * expireInterval controls how often we track persisted search session objects for expiration
+ */
+ expireInterval: schema.duration({ defaultValue: '60m' }),
+
/**
* monitoringTaskTimeout controls for how long task manager waits for search session monitoring task to complete before considering it timed out,
* If tasks timeouts it receives cancel signal and next task starts in "trackingInterval" time
diff --git a/src/plugins/data/public/search/session/session_service.test.ts b/src/plugins/data/public/search/session/session_service.test.ts
index 39680c4948366..7f388a29cd454 100644
--- a/src/plugins/data/public/search/session/session_service.test.ts
+++ b/src/plugins/data/public/search/session/session_service.test.ts
@@ -98,6 +98,14 @@ describe('Session service', () => {
expect(nowProvider.reset).toHaveBeenCalled();
});
+ it("Can clear other apps' session", async () => {
+ sessionService.start();
+ expect(sessionService.getSessionId()).not.toBeUndefined();
+ currentAppId$.next('change');
+ sessionService.clear();
+ expect(sessionService.getSessionId()).toBeUndefined();
+ });
+
it("Can start a new session in case there is other apps' stale session", async () => {
const s1 = sessionService.start();
expect(sessionService.getSessionId()).not.toBeUndefined();
diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts b/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.test.ts
similarity index 65%
rename from x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts
rename to x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.test.ts
index c0a48d5d44862..0a80f1c06998f 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.test.ts
@@ -5,10 +5,7 @@
* 2.0.
*/
-import {
- checkRunningSessions as checkRunningSessions$,
- CheckRunningSessionsDeps,
-} from './check_running_sessions';
+import { checkNonPersistedSessions as checkNonPersistedSessions$ } from './check_non_persiseted_sessions';
import {
SearchSessionStatus,
SearchSessionSavedObjectAttributes,
@@ -16,22 +13,20 @@ import {
EQL_SEARCH_STRATEGY,
} from '../../../../../../src/plugins/data/common';
import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks';
-import { SearchSessionsConfig, SearchStatus } from './types';
+import { SearchSessionsConfig, CheckSearchSessionsDeps, SearchStatus } from './types';
import moment from 'moment';
import {
SavedObjectsBulkUpdateObject,
SavedObjectsDeleteOptions,
SavedObjectsClientContract,
} from '../../../../../../src/core/server';
-import { Subject } from 'rxjs';
-import { takeUntil } from 'rxjs/operators';
jest.useFakeTimers();
-const checkRunningSessions = (deps: CheckRunningSessionsDeps, config: SearchSessionsConfig) =>
- checkRunningSessions$(deps, config).toPromise();
+const checkNonPersistedSessions = (deps: CheckSearchSessionsDeps, config: SearchSessionsConfig) =>
+ checkNonPersistedSessions$(deps, config).toPromise();
-describe('getSearchStatus', () => {
+describe('checkNonPersistedSessions', () => {
let mockClient: any;
let savedObjectsClient: jest.Mocked;
const config: SearchSessionsConfig = {
@@ -42,7 +37,9 @@ describe('getSearchStatus', () => {
maxUpdateRetries: 3,
defaultExpiration: moment.duration(7, 'd'),
trackingInterval: moment.duration(10, 's'),
+ expireInterval: moment.duration(10, 'm'),
monitoringTaskTimeout: moment.duration(5, 'm'),
+ cleanupInterval: moment.duration(10, 's'),
management: {} as any,
};
const mockLogger: any = {
@@ -51,16 +48,6 @@ describe('getSearchStatus', () => {
error: jest.fn(),
};
- const emptySO = {
- attributes: {
- persisted: false,
- status: SearchSessionStatus.IN_PROGRESS,
- created: moment().subtract(moment.duration(3, 'm')),
- touched: moment().subtract(moment.duration(10, 's')),
- idMapping: {},
- },
- };
-
beforeEach(() => {
savedObjectsClient = savedObjectsClientMock.create();
mockClient = {
@@ -81,7 +68,7 @@ describe('getSearchStatus', () => {
total: 0,
} as any);
- await checkRunningSessions(
+ await checkNonPersistedSessions(
{
savedObjectsClient,
client: mockClient,
@@ -94,240 +81,7 @@ describe('getSearchStatus', () => {
expect(savedObjectsClient.delete).not.toBeCalled();
});
- describe('pagination', () => {
- test('fetches one page if not objects exist', async () => {
- savedObjectsClient.find.mockResolvedValueOnce({
- saved_objects: [],
- total: 0,
- } as any);
-
- await checkRunningSessions(
- {
- savedObjectsClient,
- client: mockClient,
- logger: mockLogger,
- },
- config
- );
-
- expect(savedObjectsClient.find).toHaveBeenCalledTimes(1);
- });
-
- test('fetches one page if less than page size object are returned', async () => {
- savedObjectsClient.find.mockResolvedValueOnce({
- saved_objects: [emptySO, emptySO],
- total: 5,
- } as any);
-
- await checkRunningSessions(
- {
- savedObjectsClient,
- client: mockClient,
- logger: mockLogger,
- },
- config
- );
-
- expect(savedObjectsClient.find).toHaveBeenCalledTimes(1);
- });
-
- test('fetches two pages if exactly page size objects are returned', async () => {
- let i = 0;
- savedObjectsClient.find.mockImplementation(() => {
- return new Promise((resolve) => {
- resolve({
- saved_objects: i++ === 0 ? [emptySO, emptySO, emptySO, emptySO, emptySO] : [],
- total: 5,
- page: i,
- } as any);
- });
- });
-
- await checkRunningSessions(
- {
- savedObjectsClient,
- client: mockClient,
- logger: mockLogger,
- },
- config
- );
-
- expect(savedObjectsClient.find).toHaveBeenCalledTimes(2);
-
- // validate that page number increases
- const { page: page1 } = savedObjectsClient.find.mock.calls[0][0];
- const { page: page2 } = savedObjectsClient.find.mock.calls[1][0];
- expect(page1).toBe(1);
- expect(page2).toBe(2);
- });
-
- test('fetches two pages if page size +1 objects are returned', async () => {
- let i = 0;
- savedObjectsClient.find.mockImplementation(() => {
- return new Promise((resolve) => {
- resolve({
- saved_objects: i++ === 0 ? [emptySO, emptySO, emptySO, emptySO, emptySO] : [emptySO],
- total: 5,
- page: i,
- } as any);
- });
- });
-
- await checkRunningSessions(
- {
- savedObjectsClient,
- client: mockClient,
- logger: mockLogger,
- },
- config
- );
-
- expect(savedObjectsClient.find).toHaveBeenCalledTimes(2);
- });
-
- test('fetching is abortable', async () => {
- let i = 0;
- const abort$ = new Subject();
- savedObjectsClient.find.mockImplementation(() => {
- return new Promise((resolve) => {
- if (++i === 2) {
- abort$.next();
- }
- resolve({
- saved_objects: i <= 5 ? [emptySO, emptySO, emptySO, emptySO, emptySO] : [],
- total: 25,
- page: i,
- } as any);
- });
- });
-
- await checkRunningSessions$(
- {
- savedObjectsClient,
- client: mockClient,
- logger: mockLogger,
- },
- config
- )
- .pipe(takeUntil(abort$))
- .toPromise();
-
- jest.runAllTimers();
-
- // if not for `abort$` then this would be called 6 times!
- expect(savedObjectsClient.find).toHaveBeenCalledTimes(2);
- });
-
- test('sorting is by "touched"', async () => {
- savedObjectsClient.find.mockResolvedValueOnce({
- saved_objects: [],
- total: 0,
- } as any);
-
- await checkRunningSessions(
- {
- savedObjectsClient,
- client: mockClient,
- logger: mockLogger,
- },
- config
- );
-
- expect(savedObjectsClient.find).toHaveBeenCalledWith(
- expect.objectContaining({ sortField: 'touched', sortOrder: 'asc' })
- );
- });
-
- test('sessions fetched in the beginning are processed even if sessions in the end fail', async () => {
- let i = 0;
- savedObjectsClient.find.mockImplementation(() => {
- return new Promise((resolve, reject) => {
- if (++i === 2) {
- reject(new Error('Fake find error...'));
- }
- resolve({
- saved_objects:
- i <= 5
- ? [
- i === 1
- ? {
- id: '123',
- attributes: {
- persisted: false,
- status: SearchSessionStatus.IN_PROGRESS,
- created: moment().subtract(moment.duration(3, 'm')),
- touched: moment().subtract(moment.duration(2, 'm')),
- idMapping: {
- 'map-key': {
- strategy: ENHANCED_ES_SEARCH_STRATEGY,
- id: 'async-id',
- },
- },
- },
- }
- : emptySO,
- emptySO,
- emptySO,
- emptySO,
- emptySO,
- ]
- : [],
- total: 25,
- page: i,
- } as any);
- });
- });
-
- await checkRunningSessions$(
- {
- savedObjectsClient,
- client: mockClient,
- logger: mockLogger,
- },
- config
- ).toPromise();
-
- jest.runAllTimers();
-
- expect(savedObjectsClient.find).toHaveBeenCalledTimes(2);
-
- // by checking that delete was called we validate that sessions from session that were successfully fetched were processed
- expect(mockClient.asyncSearch.delete).toBeCalled();
- const { id } = mockClient.asyncSearch.delete.mock.calls[0][0];
- expect(id).toBe('async-id');
- });
- });
-
describe('delete', () => {
- test('doesnt delete a persisted session', async () => {
- savedObjectsClient.find.mockResolvedValue({
- saved_objects: [
- {
- id: '123',
- attributes: {
- persisted: true,
- status: SearchSessionStatus.IN_PROGRESS,
- created: moment().subtract(moment.duration(30, 'm')),
- touched: moment().subtract(moment.duration(10, 'm')),
- idMapping: {},
- },
- },
- ],
- total: 1,
- } as any);
- await checkRunningSessions(
- {
- savedObjectsClient,
- client: mockClient,
- logger: mockLogger,
- },
- config
- );
-
- expect(savedObjectsClient.bulkUpdate).not.toBeCalled();
- expect(savedObjectsClient.delete).not.toBeCalled();
- });
-
test('doesnt delete a non persisted, recently touched session', async () => {
savedObjectsClient.find.mockResolvedValue({
saved_objects: [
@@ -336,6 +90,7 @@ describe('getSearchStatus', () => {
attributes: {
persisted: false,
status: SearchSessionStatus.IN_PROGRESS,
+ expires: moment().add(moment.duration(3, 'm')),
created: moment().subtract(moment.duration(3, 'm')),
touched: moment().subtract(moment.duration(10, 's')),
idMapping: {},
@@ -344,7 +99,7 @@ describe('getSearchStatus', () => {
],
total: 1,
} as any);
- await checkRunningSessions(
+ await checkNonPersistedSessions(
{
savedObjectsClient,
client: mockClient,
@@ -367,6 +122,7 @@ describe('getSearchStatus', () => {
status: SearchSessionStatus.COMPLETE,
created: moment().subtract(moment.duration(3, 'm')),
touched: moment().subtract(moment.duration(1, 'm')),
+ expires: moment().add(moment.duration(3, 'm')),
idMapping: {
'search-hash': {
id: 'search-id',
@@ -379,7 +135,7 @@ describe('getSearchStatus', () => {
],
total: 1,
} as any);
- await checkRunningSessions(
+ await checkNonPersistedSessions(
{
savedObjectsClient,
client: mockClient,
@@ -401,6 +157,7 @@ describe('getSearchStatus', () => {
attributes: {
persisted: false,
status: SearchSessionStatus.IN_PROGRESS,
+ expires: moment().add(moment.duration(3, 'm')),
created: moment().subtract(moment.duration(3, 'm')),
touched: moment().subtract(moment.duration(2, 'm')),
idMapping: {
@@ -415,7 +172,7 @@ describe('getSearchStatus', () => {
total: 1,
} as any);
- await checkRunningSessions(
+ await checkNonPersistedSessions(
{
savedObjectsClient,
client: mockClient,
@@ -441,6 +198,7 @@ describe('getSearchStatus', () => {
status: SearchSessionStatus.IN_PROGRESS,
created: moment().subtract(moment.duration(3, 'm')),
touched: moment().subtract(moment.duration(2, 'm')),
+ expires: moment().add(moment.duration(3, 'm')),
idMapping: {
'map-key': {
strategy: ENHANCED_ES_SEARCH_STRATEGY,
@@ -453,7 +211,7 @@ describe('getSearchStatus', () => {
total: 1,
} as any);
- await checkRunningSessions(
+ await checkNonPersistedSessions(
{
savedObjectsClient,
client: mockClient,
@@ -481,6 +239,7 @@ describe('getSearchStatus', () => {
attributes: {
persisted: false,
status: SearchSessionStatus.COMPLETE,
+ expires: moment().add(moment.duration(3, 'm')),
created: moment().subtract(moment.duration(30, 'm')),
touched: moment().subtract(moment.duration(6, 'm')),
idMapping: {
@@ -501,7 +260,7 @@ describe('getSearchStatus', () => {
total: 1,
} as any);
- await checkRunningSessions(
+ await checkNonPersistedSessions(
{
savedObjectsClient,
client: mockClient,
@@ -530,6 +289,7 @@ describe('getSearchStatus', () => {
attributes: {
persisted: false,
status: SearchSessionStatus.COMPLETE,
+ expires: moment().add(moment.duration(3, 'm')),
created: moment().subtract(moment.duration(30, 'm')),
touched: moment().subtract(moment.duration(6, 'm')),
idMapping: {
@@ -545,7 +305,7 @@ describe('getSearchStatus', () => {
total: 1,
} as any);
- await checkRunningSessions(
+ await checkNonPersistedSessions(
{
savedObjectsClient,
client: mockClient,
@@ -573,6 +333,7 @@ describe('getSearchStatus', () => {
status: SearchSessionStatus.IN_PROGRESS,
created: moment().subtract(moment.duration(3, 'm')),
touched: moment().subtract(moment.duration(10, 's')),
+ expires: moment().add(moment.duration(3, 'm')),
idMapping: {
'search-hash': {
id: 'search-id',
@@ -594,7 +355,7 @@ describe('getSearchStatus', () => {
},
});
- await checkRunningSessions(
+ await checkNonPersistedSessions(
{
savedObjectsClient,
client: mockClient,
@@ -614,6 +375,7 @@ describe('getSearchStatus', () => {
id: '123',
attributes: {
status: SearchSessionStatus.ERROR,
+ expires: moment().add(moment.duration(3, 'm')),
idMapping: {
'search-hash': {
id: 'search-id',
@@ -633,7 +395,7 @@ describe('getSearchStatus', () => {
total: 1,
} as any);
- await checkRunningSessions(
+ await checkNonPersistedSessions(
{
savedObjectsClient,
client: mockClient,
@@ -653,6 +415,7 @@ describe('getSearchStatus', () => {
namespaces: ['awesome'],
attributes: {
status: SearchSessionStatus.IN_PROGRESS,
+ expires: moment().add(moment.duration(3, 'm')),
touched: '123',
idMapping: {
'search-hash': {
@@ -676,7 +439,7 @@ describe('getSearchStatus', () => {
},
});
- await checkRunningSessions(
+ await checkNonPersistedSessions(
{
savedObjectsClient,
client: mockClient,
@@ -696,6 +459,7 @@ describe('getSearchStatus', () => {
const so = {
attributes: {
status: SearchSessionStatus.IN_PROGRESS,
+ expires: moment().add(moment.duration(3, 'm')),
touched: '123',
idMapping: {
'search-hash': {
@@ -719,7 +483,7 @@ describe('getSearchStatus', () => {
},
});
- await checkRunningSessions(
+ await checkNonPersistedSessions(
{
savedObjectsClient,
client: mockClient,
@@ -744,6 +508,7 @@ describe('getSearchStatus', () => {
savedObjectsClient.bulkUpdate = jest.fn();
const so = {
attributes: {
+ expires: moment().add(moment.duration(3, 'm')),
idMapping: {
'search-hash': {
id: 'search-id',
@@ -766,7 +531,7 @@ describe('getSearchStatus', () => {
},
});
- await checkRunningSessions(
+ await checkNonPersistedSessions(
{
savedObjectsClient,
client: mockClient,
diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.ts
new file mode 100644
index 0000000000000..8c75ce91cac6a
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.ts
@@ -0,0 +1,129 @@
+/*
+ * 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 { SavedObjectsFindResult } from 'kibana/server';
+import moment from 'moment';
+import { EMPTY } from 'rxjs';
+import { catchError, concatMap } from 'rxjs/operators';
+import {
+ nodeBuilder,
+ ENHANCED_ES_SEARCH_STRATEGY,
+ SEARCH_SESSION_TYPE,
+ SearchSessionSavedObjectAttributes,
+ SearchSessionStatus,
+ KueryNode,
+} from '../../../../../../src/plugins/data/common';
+import { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page';
+import { SearchSessionsConfig, CheckSearchSessionsDeps } from './types';
+import { bulkUpdateSessions, getAllSessionsStatusUpdates } from './update_session_status';
+
+export const SEARCH_SESSIONS_CLEANUP_TASK_TYPE = 'search_sessions_cleanup';
+export const SEARCH_SESSIONS_CLEANUP_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_CLEANUP_TASK_TYPE}`;
+
+function isSessionStale(
+ session: SavedObjectsFindResult,
+ config: SearchSessionsConfig
+) {
+ const curTime = moment();
+ // Delete cancelled sessions immediately
+ if (session.attributes.status === SearchSessionStatus.CANCELLED) return true;
+ // Delete if a running session wasn't polled for in the last notTouchedInProgressTimeout OR
+ // if a completed \ errored \ canceled session wasn't saved for within notTouchedTimeout
+ return (
+ (session.attributes.status === SearchSessionStatus.IN_PROGRESS &&
+ curTime.diff(moment(session.attributes.touched), 'ms') >
+ config.notTouchedInProgressTimeout.asMilliseconds()) ||
+ (session.attributes.status !== SearchSessionStatus.IN_PROGRESS &&
+ curTime.diff(moment(session.attributes.touched), 'ms') >
+ config.notTouchedTimeout.asMilliseconds())
+ );
+}
+
+function checkNonPersistedSessionsPage(
+ deps: CheckSearchSessionsDeps,
+ config: SearchSessionsConfig,
+ filter: KueryNode,
+ page: number
+) {
+ const { logger, client, savedObjectsClient } = deps;
+ logger.debug(`${SEARCH_SESSIONS_CLEANUP_TASK_TYPE} Fetching sessions from page ${page}`);
+ return getSearchSessionsPage$(deps, filter, config.pageSize, page).pipe(
+ concatMap(async (nonPersistedSearchSessions) => {
+ if (!nonPersistedSearchSessions.total) return nonPersistedSearchSessions;
+
+ logger.debug(
+ `${SEARCH_SESSIONS_CLEANUP_TASK_TYPE} Found ${nonPersistedSearchSessions.total} sessions, processing ${nonPersistedSearchSessions.saved_objects.length}`
+ );
+
+ const updatedSessions = await getAllSessionsStatusUpdates(deps, nonPersistedSearchSessions);
+ const deletedSessionIds: string[] = [];
+
+ await Promise.all(
+ nonPersistedSearchSessions.saved_objects.map(async (session) => {
+ if (isSessionStale(session, config)) {
+ // delete saved object to free up memory
+ // TODO: there's a potential rare edge case of deleting an object and then receiving a new trackId for that same session!
+ // Maybe we want to change state to deleted and cleanup later?
+ logger.debug(`Deleting stale session | ${session.id}`);
+ try {
+ deletedSessionIds.push(session.id);
+ await savedObjectsClient.delete(SEARCH_SESSION_TYPE, session.id, {
+ namespace: session.namespaces?.[0],
+ });
+ } catch (e) {
+ logger.error(
+ `${SEARCH_SESSIONS_CLEANUP_TASK_TYPE} Error while deleting session ${session.id}: ${e.message}`
+ );
+ }
+
+ // Send a delete request for each async search to ES
+ Object.keys(session.attributes.idMapping).map(async (searchKey: string) => {
+ const searchInfo = session.attributes.idMapping[searchKey];
+ if (searchInfo.strategy === ENHANCED_ES_SEARCH_STRATEGY) {
+ try {
+ await client.asyncSearch.delete({ id: searchInfo.id });
+ } catch (e) {
+ if (e.message !== 'resource_not_found_exception') {
+ logger.error(
+ `${SEARCH_SESSIONS_CLEANUP_TASK_TYPE} Error while deleting async_search ${searchInfo.id}: ${e.message}`
+ );
+ }
+ }
+ }
+ });
+ }
+ })
+ );
+
+ const nonDeletedSessions = updatedSessions.filter((updateSession) => {
+ return deletedSessionIds.indexOf(updateSession.id) === -1;
+ });
+
+ await bulkUpdateSessions(deps, nonDeletedSessions);
+
+ return nonPersistedSearchSessions;
+ })
+ );
+}
+
+export function checkNonPersistedSessions(
+ deps: CheckSearchSessionsDeps,
+ config: SearchSessionsConfig
+) {
+ const { logger } = deps;
+
+ const filters = nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.persisted`, 'false');
+
+ return checkSearchSessionsByPage(checkNonPersistedSessionsPage, deps, config, filters).pipe(
+ catchError((e) => {
+ logger.error(
+ `${SEARCH_SESSIONS_CLEANUP_TASK_TYPE} Error while processing sessions: ${e?.message}`
+ );
+ return EMPTY;
+ })
+ );
+}
diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.test.ts b/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.test.ts
new file mode 100644
index 0000000000000..e0b1b74b57d02
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.test.ts
@@ -0,0 +1,76 @@
+/*
+ * 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 { checkPersistedSessionsProgress } from './check_persisted_sessions';
+import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks';
+import { SearchSessionsConfig } from './types';
+import moment from 'moment';
+import { SavedObjectsClientContract } from '../../../../../../src/core/server';
+
+describe('checkPersistedSessionsProgress', () => {
+ let mockClient: any;
+ let savedObjectsClient: jest.Mocked;
+ const config: SearchSessionsConfig = {
+ enabled: true,
+ pageSize: 5,
+ notTouchedInProgressTimeout: moment.duration(1, 'm'),
+ notTouchedTimeout: moment.duration(5, 'm'),
+ maxUpdateRetries: 3,
+ defaultExpiration: moment.duration(7, 'd'),
+ trackingInterval: moment.duration(10, 's'),
+ cleanupInterval: moment.duration(10, 's'),
+ expireInterval: moment.duration(10, 'm'),
+ monitoringTaskTimeout: moment.duration(5, 'm'),
+ management: {} as any,
+ };
+ const mockLogger: any = {
+ debug: jest.fn(),
+ warn: jest.fn(),
+ error: jest.fn(),
+ };
+
+ beforeEach(() => {
+ savedObjectsClient = savedObjectsClientMock.create();
+ mockClient = {
+ asyncSearch: {
+ status: jest.fn(),
+ delete: jest.fn(),
+ },
+ eql: {
+ status: jest.fn(),
+ delete: jest.fn(),
+ },
+ };
+ });
+
+ test('fetches only running persisted sessions', async () => {
+ savedObjectsClient.find.mockResolvedValue({
+ saved_objects: [],
+ total: 0,
+ } as any);
+
+ await checkPersistedSessionsProgress(
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ config
+ );
+
+ const [findInput] = savedObjectsClient.find.mock.calls[0];
+
+ expect(findInput.filter.arguments[0].arguments[0].value).toBe(
+ 'search-session.attributes.persisted'
+ );
+ expect(findInput.filter.arguments[0].arguments[1].value).toBe('true');
+ expect(findInput.filter.arguments[1].arguments[0].value).toBe(
+ 'search-session.attributes.status'
+ );
+ expect(findInput.filter.arguments[1].arguments[1].value).toBe('in_progress');
+ });
+});
diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts
new file mode 100644
index 0000000000000..0d51e97952275
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts
@@ -0,0 +1,72 @@
+/*
+ * 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 { EMPTY, Observable } from 'rxjs';
+import { catchError, concatMap } from 'rxjs/operators';
+import {
+ nodeBuilder,
+ SEARCH_SESSION_TYPE,
+ SearchSessionStatus,
+ KueryNode,
+} from '../../../../../../src/plugins/data/common';
+import { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page';
+import { SearchSessionsConfig, CheckSearchSessionsDeps, SearchSessionsResponse } from './types';
+import { bulkUpdateSessions, getAllSessionsStatusUpdates } from './update_session_status';
+
+export const SEARCH_SESSIONS_TASK_TYPE = 'search_sessions_monitor';
+export const SEARCH_SESSIONS_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_TASK_TYPE}`;
+
+function checkPersistedSessionsPage(
+ deps: CheckSearchSessionsDeps,
+ config: SearchSessionsConfig,
+ filter: KueryNode,
+ page: number
+): Observable {
+ const { logger } = deps;
+ logger.debug(`${SEARCH_SESSIONS_TASK_TYPE} Fetching sessions from page ${page}`);
+ return getSearchSessionsPage$(deps, filter, config.pageSize, page).pipe(
+ concatMap(async (persistedSearchSessions) => {
+ if (!persistedSearchSessions.total) return persistedSearchSessions;
+
+ logger.debug(
+ `${SEARCH_SESSIONS_TASK_TYPE} Found ${persistedSearchSessions.total} sessions, processing ${persistedSearchSessions.saved_objects.length}`
+ );
+
+ const updatedSessions = await getAllSessionsStatusUpdates(deps, persistedSearchSessions);
+ await bulkUpdateSessions(deps, updatedSessions);
+
+ return persistedSearchSessions;
+ })
+ );
+}
+
+export function checkPersistedSessionsProgress(
+ deps: CheckSearchSessionsDeps,
+ config: SearchSessionsConfig
+) {
+ const { logger } = deps;
+
+ const persistedSessionsFilter = nodeBuilder.and([
+ nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.persisted`, 'true'),
+ nodeBuilder.is(
+ `${SEARCH_SESSION_TYPE}.attributes.status`,
+ SearchSessionStatus.IN_PROGRESS.toString()
+ ),
+ ]);
+
+ return checkSearchSessionsByPage(
+ checkPersistedSessionsPage,
+ deps,
+ config,
+ persistedSessionsFilter
+ ).pipe(
+ catchError((e) => {
+ logger.error(`${SEARCH_SESSIONS_TASK_TYPE} Error while processing sessions: ${e?.message}`);
+ return EMPTY;
+ })
+ );
+}
diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts
deleted file mode 100644
index 6787d31ed2b74..0000000000000
--- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * 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 {
- ElasticsearchClient,
- Logger,
- SavedObjectsClientContract,
- SavedObjectsFindResult,
- SavedObjectsUpdateResponse,
-} from 'kibana/server';
-import moment from 'moment';
-import { EMPTY, from, Observable } from 'rxjs';
-import { catchError, concatMap } from 'rxjs/operators';
-import {
- nodeBuilder,
- ENHANCED_ES_SEARCH_STRATEGY,
- SEARCH_SESSION_TYPE,
- SearchSessionRequestInfo,
- SearchSessionSavedObjectAttributes,
- SearchSessionStatus,
-} from '../../../../../../src/plugins/data/common';
-import { getSearchStatus } from './get_search_status';
-import { getSessionStatus } from './get_session_status';
-import { SearchSessionsConfig, SearchStatus } from './types';
-
-export interface CheckRunningSessionsDeps {
- savedObjectsClient: SavedObjectsClientContract;
- client: ElasticsearchClient;
- logger: Logger;
-}
-
-function isSessionStale(
- session: SavedObjectsFindResult,
- config: SearchSessionsConfig,
- logger: Logger
-) {
- const curTime = moment();
- // Delete if a running session wasn't polled for in the last notTouchedInProgressTimeout OR
- // if a completed \ errored \ canceled session wasn't saved for within notTouchedTimeout
- return (
- (session.attributes.status === SearchSessionStatus.IN_PROGRESS &&
- curTime.diff(moment(session.attributes.touched), 'ms') >
- config.notTouchedInProgressTimeout.asMilliseconds()) ||
- (session.attributes.status !== SearchSessionStatus.IN_PROGRESS &&
- curTime.diff(moment(session.attributes.touched), 'ms') >
- config.notTouchedTimeout.asMilliseconds())
- );
-}
-
-async function updateSessionStatus(
- session: SavedObjectsFindResult,
- client: ElasticsearchClient,
- logger: Logger
-) {
- let sessionUpdated = false;
-
- // Check statuses of all running searches
- await Promise.all(
- Object.keys(session.attributes.idMapping).map(async (searchKey: string) => {
- const updateSearchRequest = (
- currentStatus: Pick
- ) => {
- sessionUpdated = true;
- session.attributes.idMapping[searchKey] = {
- ...session.attributes.idMapping[searchKey],
- ...currentStatus,
- };
- };
-
- const searchInfo = session.attributes.idMapping[searchKey];
- if (searchInfo.status === SearchStatus.IN_PROGRESS) {
- try {
- const currentStatus = await getSearchStatus(client, searchInfo.id);
-
- if (currentStatus.status !== searchInfo.status) {
- logger.debug(`search ${searchInfo.id} | status changed to ${currentStatus.status}`);
- updateSearchRequest(currentStatus);
- }
- } catch (e) {
- logger.error(e);
- updateSearchRequest({
- status: SearchStatus.ERROR,
- error: e.message || e.meta.error?.caused_by?.reason,
- });
- }
- }
- })
- );
-
- // And only then derive the session's status
- const sessionStatus = getSessionStatus(session.attributes);
- if (sessionStatus !== session.attributes.status) {
- const now = new Date().toISOString();
- session.attributes.status = sessionStatus;
- session.attributes.touched = now;
- if (sessionStatus === SearchSessionStatus.COMPLETE) {
- session.attributes.completed = now;
- } else if (session.attributes.completed) {
- session.attributes.completed = null;
- }
- sessionUpdated = true;
- }
-
- return sessionUpdated;
-}
-
-function getSavedSearchSessionsPage$(
- { savedObjectsClient, logger }: CheckRunningSessionsDeps,
- config: SearchSessionsConfig,
- page: number
-) {
- logger.debug(`Fetching saved search sessions page ${page}`);
- return from(
- savedObjectsClient.find({
- page,
- perPage: config.pageSize,
- type: SEARCH_SESSION_TYPE,
- namespaces: ['*'],
- // process older sessions first
- sortField: 'touched',
- sortOrder: 'asc',
- filter: nodeBuilder.or([
- nodeBuilder.and([
- nodeBuilder.is(
- `${SEARCH_SESSION_TYPE}.attributes.status`,
- SearchSessionStatus.IN_PROGRESS.toString()
- ),
- nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.persisted`, 'true'),
- ]),
- nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.persisted`, 'false'),
- ]),
- })
- );
-}
-
-function checkRunningSessionsPage(
- deps: CheckRunningSessionsDeps,
- config: SearchSessionsConfig,
- page: number
-) {
- const { logger, client, savedObjectsClient } = deps;
- return getSavedSearchSessionsPage$(deps, config, page).pipe(
- concatMap(async (runningSearchSessionsResponse) => {
- if (!runningSearchSessionsResponse.total) return;
-
- logger.debug(
- `Found ${runningSearchSessionsResponse.total} running sessions, processing ${runningSearchSessionsResponse.saved_objects.length} sessions from page ${page}`
- );
-
- const updatedSessions = new Array<
- SavedObjectsFindResult
- >();
-
- await Promise.all(
- runningSearchSessionsResponse.saved_objects.map(async (session) => {
- const updated = await updateSessionStatus(session, client, logger);
- let deleted = false;
-
- if (!session.attributes.persisted) {
- if (isSessionStale(session, config, logger)) {
- // delete saved object to free up memory
- // TODO: there's a potential rare edge case of deleting an object and then receiving a new trackId for that same session!
- // Maybe we want to change state to deleted and cleanup later?
- logger.debug(`Deleting stale session | ${session.id}`);
- try {
- await savedObjectsClient.delete(SEARCH_SESSION_TYPE, session.id, {
- namespace: session.namespaces?.[0],
- });
- deleted = true;
- } catch (e) {
- logger.error(
- `Error while deleting stale search session ${session.id}: ${e.message}`
- );
- }
-
- // Send a delete request for each async search to ES
- Object.keys(session.attributes.idMapping).map(async (searchKey: string) => {
- const searchInfo = session.attributes.idMapping[searchKey];
- if (searchInfo.strategy === ENHANCED_ES_SEARCH_STRATEGY) {
- try {
- await client.asyncSearch.delete({ id: searchInfo.id });
- } catch (e) {
- logger.error(
- `Error while deleting async_search ${searchInfo.id}: ${e.message}`
- );
- }
- }
- });
- }
- }
-
- if (updated && !deleted) {
- updatedSessions.push(session);
- }
- })
- );
-
- // Do a bulk update
- if (updatedSessions.length) {
- // If there's an error, we'll try again in the next iteration, so there's no need to check the output.
- const updatedResponse = await savedObjectsClient.bulkUpdate(
- updatedSessions.map((session) => ({
- ...session,
- namespace: session.namespaces?.[0],
- }))
- );
-
- const success: Array> = [];
- const fail: Array> = [];
-
- updatedResponse.saved_objects.forEach((savedObjectResponse) => {
- if ('error' in savedObjectResponse) {
- fail.push(savedObjectResponse);
- logger.error(
- `Error while updating search session ${savedObjectResponse?.id}: ${savedObjectResponse.error?.message}`
- );
- } else {
- success.push(savedObjectResponse);
- }
- });
-
- logger.debug(`Updating search sessions: success: ${success.length}, fail: ${fail.length}`);
- }
-
- return runningSearchSessionsResponse;
- })
- );
-}
-
-export function checkRunningSessions(deps: CheckRunningSessionsDeps, config: SearchSessionsConfig) {
- const { logger } = deps;
-
- const checkRunningSessionsByPage = (nextPage = 1): Observable =>
- checkRunningSessionsPage(deps, config, nextPage).pipe(
- concatMap((result) => {
- if (!result || !result.saved_objects || result.saved_objects.length < config.pageSize) {
- return EMPTY;
- } else {
- // TODO: while processing previous page session list might have been changed and we might skip a session,
- // because it would appear now on a different "page".
- // This isn't critical, as we would pick it up on a next task iteration, but maybe we could improve this somehow
- return checkRunningSessionsByPage(result.page + 1);
- }
- })
- );
-
- return checkRunningSessionsByPage().pipe(
- catchError((e) => {
- logger.error(`Error while processing search sessions: ${e?.message}`);
- return EMPTY;
- })
- );
-}
diff --git a/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts
new file mode 100644
index 0000000000000..e261c324f440f
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts
@@ -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 { EMPTY, Observable } from 'rxjs';
+import { catchError, concatMap } from 'rxjs/operators';
+import {
+ nodeBuilder,
+ SEARCH_SESSION_TYPE,
+ SearchSessionStatus,
+ KueryNode,
+} from '../../../../../../src/plugins/data/common';
+import { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page';
+import { SearchSessionsConfig, CheckSearchSessionsDeps, SearchSessionsResponse } from './types';
+import { bulkUpdateSessions, getAllSessionsStatusUpdates } from './update_session_status';
+
+export const SEARCH_SESSIONS_EXPIRE_TASK_TYPE = 'search_sessions_expire';
+export const SEARCH_SESSIONS_EXPIRE_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_EXPIRE_TASK_TYPE}`;
+
+function checkSessionExpirationPage(
+ deps: CheckSearchSessionsDeps,
+ config: SearchSessionsConfig,
+ filter: KueryNode,
+ page: number
+): Observable {
+ const { logger } = deps;
+ logger.debug(`${SEARCH_SESSIONS_EXPIRE_TASK_TYPE} Fetching sessions from page ${page}`);
+ return getSearchSessionsPage$(deps, filter, config.pageSize, page).pipe(
+ concatMap(async (searchSessions) => {
+ if (!searchSessions.total) return searchSessions;
+
+ logger.debug(
+ `${SEARCH_SESSIONS_EXPIRE_TASK_TYPE} Found ${searchSessions.total} sessions, processing ${searchSessions.saved_objects.length}`
+ );
+
+ const updatedSessions = await getAllSessionsStatusUpdates(deps, searchSessions);
+ await bulkUpdateSessions(deps, updatedSessions);
+
+ return searchSessions;
+ })
+ );
+}
+
+export function checkPersistedCompletedSessionExpiration(
+ deps: CheckSearchSessionsDeps,
+ config: SearchSessionsConfig
+) {
+ const { logger } = deps;
+
+ const persistedSessionsFilter = nodeBuilder.and([
+ nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.persisted`, 'true'),
+ nodeBuilder.is(
+ `${SEARCH_SESSION_TYPE}.attributes.status`,
+ SearchSessionStatus.COMPLETE.toString()
+ ),
+ ]);
+
+ return checkSearchSessionsByPage(
+ checkSessionExpirationPage,
+ deps,
+ config,
+ persistedSessionsFilter
+ ).pipe(
+ catchError((e) => {
+ logger.error(
+ `${SEARCH_SESSIONS_EXPIRE_TASK_TYPE} Error while processing sessions: ${e?.message}`
+ );
+ return EMPTY;
+ })
+ );
+}
diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.test.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.test.ts
new file mode 100644
index 0000000000000..df2b7d964642d
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.test.ts
@@ -0,0 +1,282 @@
+/*
+ * 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 { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page';
+import {
+ SearchSessionStatus,
+ ENHANCED_ES_SEARCH_STRATEGY,
+} from '../../../../../../src/plugins/data/common';
+import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks';
+import { SearchSessionsConfig, SearchStatus } from './types';
+import moment from 'moment';
+import { SavedObjectsClientContract } from '../../../../../../src/core/server';
+import { of, Subject, throwError } from 'rxjs';
+import { takeUntil } from 'rxjs/operators';
+jest.useFakeTimers();
+
+describe('checkSearchSessionsByPage', () => {
+ const mockClient = {} as any;
+ let savedObjectsClient: jest.Mocked;
+ const config: SearchSessionsConfig = {
+ enabled: true,
+ pageSize: 5,
+ management: {} as any,
+ } as any;
+ const mockLogger: any = {
+ debug: jest.fn(),
+ warn: jest.fn(),
+ error: jest.fn(),
+ };
+
+ const emptySO = {
+ attributes: {
+ persisted: false,
+ status: SearchSessionStatus.IN_PROGRESS,
+ created: moment().subtract(moment.duration(3, 'm')),
+ touched: moment().subtract(moment.duration(10, 's')),
+ idMapping: {},
+ },
+ };
+
+ beforeEach(() => {
+ savedObjectsClient = savedObjectsClientMock.create();
+ });
+
+ describe('getSearchSessionsPage$', () => {
+ test('sorting is by "touched"', async () => {
+ savedObjectsClient.find.mockResolvedValueOnce({
+ saved_objects: [],
+ total: 0,
+ } as any);
+
+ await getSearchSessionsPage$(
+ {
+ savedObjectsClient,
+ } as any,
+ {
+ type: 'literal',
+ },
+ 1,
+ 1
+ );
+
+ expect(savedObjectsClient.find).toHaveBeenCalledWith(
+ expect.objectContaining({ sortField: 'touched', sortOrder: 'asc' })
+ );
+ });
+ });
+
+ describe('pagination', () => {
+ test('fetches one page if got empty response', async () => {
+ const checkFn = jest.fn().mockReturnValue(of(undefined));
+
+ await checkSearchSessionsByPage(
+ checkFn,
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ config,
+ []
+ ).toPromise();
+
+ expect(checkFn).toHaveBeenCalledTimes(1);
+ });
+
+ test('fetches one page if got response with no saved objects', async () => {
+ const checkFn = jest.fn().mockReturnValue(
+ of({
+ total: 0,
+ })
+ );
+
+ await checkSearchSessionsByPage(
+ checkFn,
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ config,
+ []
+ ).toPromise();
+
+ expect(checkFn).toHaveBeenCalledTimes(1);
+ });
+
+ test('fetches one page if less than page size object are returned', async () => {
+ const checkFn = jest.fn().mockReturnValue(
+ of({
+ saved_objects: [emptySO, emptySO],
+ total: 5,
+ })
+ );
+
+ await checkSearchSessionsByPage(
+ checkFn,
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ config,
+ []
+ ).toPromise();
+
+ expect(checkFn).toHaveBeenCalledTimes(1);
+ });
+
+ test('fetches two pages if exactly page size objects are returned', async () => {
+ let i = 0;
+
+ const checkFn = jest.fn().mockImplementation(() =>
+ of({
+ saved_objects: i++ === 0 ? [emptySO, emptySO, emptySO, emptySO, emptySO] : [],
+ total: 5,
+ page: i,
+ })
+ );
+
+ await checkSearchSessionsByPage(
+ checkFn,
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ config,
+ []
+ ).toPromise();
+
+ expect(checkFn).toHaveBeenCalledTimes(2);
+
+ // validate that page number increases
+ const page1 = checkFn.mock.calls[0][3];
+ const page2 = checkFn.mock.calls[1][3];
+ expect(page1).toBe(1);
+ expect(page2).toBe(2);
+ });
+
+ test('fetches two pages if page size +1 objects are returned', async () => {
+ let i = 0;
+
+ const checkFn = jest.fn().mockImplementation(() =>
+ of({
+ saved_objects: i++ === 0 ? [emptySO, emptySO, emptySO, emptySO, emptySO] : [emptySO],
+ total: i === 0 ? 5 : 1,
+ page: i,
+ })
+ );
+
+ await checkSearchSessionsByPage(
+ checkFn,
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ config,
+ []
+ ).toPromise();
+
+ expect(checkFn).toHaveBeenCalledTimes(2);
+ });
+
+ test('sessions fetched in the beginning are processed even if sessions in the end fail', async () => {
+ let i = 0;
+
+ const checkFn = jest.fn().mockImplementation(() => {
+ if (++i === 2) {
+ return throwError('Fake find error...');
+ }
+ return of({
+ saved_objects:
+ i <= 5
+ ? [
+ i === 1
+ ? {
+ id: '123',
+ attributes: {
+ persisted: false,
+ status: SearchSessionStatus.IN_PROGRESS,
+ created: moment().subtract(moment.duration(3, 'm')),
+ touched: moment().subtract(moment.duration(2, 'm')),
+ idMapping: {
+ 'map-key': {
+ strategy: ENHANCED_ES_SEARCH_STRATEGY,
+ id: 'async-id',
+ status: SearchStatus.IN_PROGRESS,
+ },
+ },
+ },
+ }
+ : emptySO,
+ emptySO,
+ emptySO,
+ emptySO,
+ emptySO,
+ ]
+ : [],
+ total: 25,
+ page: i,
+ });
+ });
+
+ await checkSearchSessionsByPage(
+ checkFn,
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ config,
+ []
+ )
+ .toPromise()
+ .catch(() => {});
+
+ expect(checkFn).toHaveBeenCalledTimes(2);
+ });
+
+ test('fetching is abortable', async () => {
+ let i = 0;
+ const abort$ = new Subject();
+
+ const checkFn = jest.fn().mockImplementation(() => {
+ if (++i === 2) {
+ abort$.next();
+ }
+
+ return of({
+ saved_objects: i <= 5 ? [emptySO, emptySO, emptySO, emptySO, emptySO] : [],
+ total: 25,
+ page: i,
+ });
+ });
+
+ await checkSearchSessionsByPage(
+ checkFn,
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ config,
+ []
+ )
+ .pipe(takeUntil(abort$))
+ .toPromise()
+ .catch(() => {});
+
+ jest.runAllTimers();
+
+ // if not for `abort$` then this would be called 6 times!
+ expect(checkFn).toHaveBeenCalledTimes(2);
+ });
+ });
+});
diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts
new file mode 100644
index 0000000000000..74306bac39f7d
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts
@@ -0,0 +1,61 @@
+/*
+ * 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 { SavedObjectsClientContract, Logger } from 'kibana/server';
+import { from, Observable, EMPTY } from 'rxjs';
+import { concatMap } from 'rxjs/operators';
+import {
+ SearchSessionSavedObjectAttributes,
+ SEARCH_SESSION_TYPE,
+ KueryNode,
+} from '../../../../../../src/plugins/data/common';
+import { CheckSearchSessionsDeps, CheckSearchSessionsFn, SearchSessionsConfig } from './types';
+
+export interface GetSessionsDeps {
+ savedObjectsClient: SavedObjectsClientContract;
+ logger: Logger;
+}
+
+export function getSearchSessionsPage$(
+ { savedObjectsClient }: GetSessionsDeps,
+ filter: KueryNode,
+ pageSize: number,
+ page: number
+) {
+ return from(
+ savedObjectsClient.find({
+ page,
+ perPage: pageSize,
+ type: SEARCH_SESSION_TYPE,
+ namespaces: ['*'],
+ // process older sessions first
+ sortField: 'touched',
+ sortOrder: 'asc',
+ filter,
+ })
+ );
+}
+
+export const checkSearchSessionsByPage = (
+ checkFn: CheckSearchSessionsFn,
+ deps: CheckSearchSessionsDeps,
+ config: SearchSessionsConfig,
+ filters: any,
+ nextPage = 1
+): Observable =>
+ checkFn(deps, config, filters, nextPage).pipe(
+ concatMap((result) => {
+ if (!result || !result.saved_objects || result.saved_objects.length < config.pageSize) {
+ return EMPTY;
+ } else {
+ // TODO: while processing previous page session list might have been changed and we might skip a session,
+ // because it would appear now on a different "page".
+ // This isn't critical, as we would pick it up on a next task iteration, but maybe we could improve this somehow
+ return checkSearchSessionsByPage(checkFn, deps, config, filters, result.page + 1);
+ }
+ })
+ );
diff --git a/x-pack/plugins/data_enhanced/server/search/session/index.ts b/x-pack/plugins/data_enhanced/server/search/session/index.ts
index deadeb3f8f07a..1e6841211bb66 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/index.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/index.ts
@@ -6,4 +6,3 @@
*/
export * from './session_service';
-export { registerSearchSessionsTask, scheduleSearchSessionsTasks } from './monitoring_task';
diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts
deleted file mode 100644
index 7b7b1412987be..0000000000000
--- a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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 { Duration } from 'moment';
-import { filter, takeUntil } from 'rxjs/operators';
-import { BehaviorSubject } from 'rxjs';
-import {
- TaskManagerSetupContract,
- TaskManagerStartContract,
- RunContext,
- TaskRunCreatorFunction,
-} from '../../../../task_manager/server';
-import { checkRunningSessions } from './check_running_sessions';
-import { CoreSetup, SavedObjectsClient, Logger } from '../../../../../../src/core/server';
-import { ConfigSchema } from '../../../config';
-import { SEARCH_SESSION_TYPE } from '../../../../../../src/plugins/data/common';
-import { DataEnhancedStartDependencies } from '../../type';
-
-export const SEARCH_SESSIONS_TASK_TYPE = 'search_sessions_monitor';
-export const SEARCH_SESSIONS_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_TASK_TYPE}`;
-
-interface SearchSessionTaskDeps {
- taskManager: TaskManagerSetupContract;
- logger: Logger;
- config: ConfigSchema;
-}
-
-function searchSessionRunner(
- core: CoreSetup,
- { logger, config }: SearchSessionTaskDeps
-): TaskRunCreatorFunction {
- return ({ taskInstance }: RunContext) => {
- const aborted$ = new BehaviorSubject(false);
- return {
- async run() {
- const sessionConfig = config.search.sessions;
- const [coreStart] = await core.getStartServices();
- if (!sessionConfig.enabled) {
- logger.debug('Search sessions are disabled. Skipping task.');
- return;
- }
- if (aborted$.getValue()) return;
-
- const internalRepo = coreStart.savedObjects.createInternalRepository([SEARCH_SESSION_TYPE]);
- const internalSavedObjectsClient = new SavedObjectsClient(internalRepo);
- await checkRunningSessions(
- {
- savedObjectsClient: internalSavedObjectsClient,
- client: coreStart.elasticsearch.client.asInternalUser,
- logger,
- },
- sessionConfig
- )
- .pipe(takeUntil(aborted$.pipe(filter((aborted) => aborted))))
- .toPromise();
-
- return {
- state: {},
- };
- },
- cancel: async () => {
- aborted$.next(true);
- },
- };
- };
-}
-
-export function registerSearchSessionsTask(
- core: CoreSetup,
- deps: SearchSessionTaskDeps
-) {
- deps.taskManager.registerTaskDefinitions({
- [SEARCH_SESSIONS_TASK_TYPE]: {
- title: 'Search Sessions Monitor',
- createTaskRunner: searchSessionRunner(core, deps),
- timeout: `${deps.config.search.sessions.monitoringTaskTimeout.asSeconds()}s`,
- },
- });
-}
-
-export async function unscheduleSearchSessionsTask(
- taskManager: TaskManagerStartContract,
- logger: Logger
-) {
- try {
- await taskManager.removeIfExists(SEARCH_SESSIONS_TASK_ID);
- logger.debug(`Search sessions cleared`);
- } catch (e) {
- logger.error(`Error clearing task, received ${e.message}`);
- }
-}
-
-export async function scheduleSearchSessionsTasks(
- taskManager: TaskManagerStartContract,
- logger: Logger,
- trackingInterval: Duration
-) {
- await taskManager.removeIfExists(SEARCH_SESSIONS_TASK_ID);
-
- try {
- await taskManager.ensureScheduled({
- id: SEARCH_SESSIONS_TASK_ID,
- taskType: SEARCH_SESSIONS_TASK_TYPE,
- schedule: {
- interval: `${trackingInterval.asSeconds()}s`,
- },
- state: {},
- params: {},
- });
-
- logger.debug(`Search sessions task, scheduled to run`);
- } catch (e) {
- logger.error(`Error scheduling task, received ${e.message}`);
- }
-}
diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts
index 374dbee2384d5..dd1eafa5d60f8 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts
@@ -79,7 +79,9 @@ describe('SearchSessionService', () => {
maxUpdateRetries: MAX_UPDATE_RETRIES,
defaultExpiration: moment.duration(7, 'd'),
monitoringTaskTimeout: moment.duration(5, 'm'),
+ cleanupInterval: moment.duration(10, 's'),
trackingInterval: moment.duration(10, 's'),
+ expireInterval: moment.duration(10, 'm'),
management: {} as any,
},
},
@@ -157,7 +159,9 @@ describe('SearchSessionService', () => {
maxUpdateRetries: MAX_UPDATE_RETRIES,
defaultExpiration: moment.duration(7, 'd'),
trackingInterval: moment.duration(10, 's'),
+ expireInterval: moment.duration(10, 'm'),
monitoringTaskTimeout: moment.duration(5, 'm'),
+ cleanupInterval: moment.duration(10, 's'),
management: {} as any,
},
},
diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts
index 81a12f607935d..0998c1f42e183 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts
@@ -43,11 +43,26 @@ import { createRequestHash } from './utils';
import { ConfigSchema } from '../../../config';
import {
registerSearchSessionsTask,
- scheduleSearchSessionsTasks,
+ scheduleSearchSessionsTask,
unscheduleSearchSessionsTask,
-} from './monitoring_task';
+} from './setup_task';
import { SearchSessionsConfig, SearchStatus } from './types';
import { DataEnhancedStartDependencies } from '../../type';
+import {
+ checkPersistedSessionsProgress,
+ SEARCH_SESSIONS_TASK_ID,
+ SEARCH_SESSIONS_TASK_TYPE,
+} from './check_persisted_sessions';
+import {
+ SEARCH_SESSIONS_CLEANUP_TASK_TYPE,
+ checkNonPersistedSessions,
+ SEARCH_SESSIONS_CLEANUP_TASK_ID,
+} from './check_non_persiseted_sessions';
+import {
+ SEARCH_SESSIONS_EXPIRE_TASK_TYPE,
+ SEARCH_SESSIONS_EXPIRE_TASK_ID,
+ checkPersistedCompletedSessionExpiration,
+} from './expire_persisted_sessions';
export interface SearchSessionDependencies {
savedObjectsClient: SavedObjectsClientContract;
@@ -89,11 +104,35 @@ export class SearchSessionService
}
public setup(core: CoreSetup, deps: SetupDependencies) {
- registerSearchSessionsTask(core, {
+ const taskDeps = {
config: this.config,
taskManager: deps.taskManager,
logger: this.logger,
- });
+ };
+
+ registerSearchSessionsTask(
+ core,
+ taskDeps,
+ SEARCH_SESSIONS_TASK_TYPE,
+ 'persisted session progress',
+ checkPersistedSessionsProgress
+ );
+
+ registerSearchSessionsTask(
+ core,
+ taskDeps,
+ SEARCH_SESSIONS_CLEANUP_TASK_TYPE,
+ 'non persisted session cleanup',
+ checkNonPersistedSessions
+ );
+
+ registerSearchSessionsTask(
+ core,
+ taskDeps,
+ SEARCH_SESSIONS_EXPIRE_TASK_TYPE,
+ 'complete session expiration',
+ checkPersistedCompletedSessionExpiration
+ );
}
public async start(core: CoreStart, deps: StartDependencies) {
@@ -103,14 +142,37 @@ export class SearchSessionService
public stop() {}
private setupMonitoring = async (core: CoreStart, deps: StartDependencies) => {
+ const taskDeps = {
+ config: this.config,
+ taskManager: deps.taskManager,
+ logger: this.logger,
+ };
+
if (this.sessionConfig.enabled) {
- scheduleSearchSessionsTasks(
- deps.taskManager,
- this.logger,
+ scheduleSearchSessionsTask(
+ taskDeps,
+ SEARCH_SESSIONS_TASK_ID,
+ SEARCH_SESSIONS_TASK_TYPE,
this.sessionConfig.trackingInterval
);
+
+ scheduleSearchSessionsTask(
+ taskDeps,
+ SEARCH_SESSIONS_CLEANUP_TASK_ID,
+ SEARCH_SESSIONS_CLEANUP_TASK_TYPE,
+ this.sessionConfig.cleanupInterval
+ );
+
+ scheduleSearchSessionsTask(
+ taskDeps,
+ SEARCH_SESSIONS_EXPIRE_TASK_ID,
+ SEARCH_SESSIONS_EXPIRE_TASK_TYPE,
+ this.sessionConfig.expireInterval
+ );
} else {
- unscheduleSearchSessionsTask(deps.taskManager, this.logger);
+ unscheduleSearchSessionsTask(taskDeps, SEARCH_SESSIONS_TASK_ID);
+ unscheduleSearchSessionsTask(taskDeps, SEARCH_SESSIONS_CLEANUP_TASK_ID);
+ unscheduleSearchSessionsTask(taskDeps, SEARCH_SESSIONS_EXPIRE_TASK_ID);
}
};
diff --git a/x-pack/plugins/data_enhanced/server/search/session/setup_task.ts b/x-pack/plugins/data_enhanced/server/search/session/setup_task.ts
new file mode 100644
index 0000000000000..a4c9b6039ff64
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/server/search/session/setup_task.ts
@@ -0,0 +1,121 @@
+/*
+ * 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 { Duration } from 'moment';
+import { filter, takeUntil } from 'rxjs/operators';
+import { BehaviorSubject } from 'rxjs';
+import { RunContext, TaskRunCreatorFunction } from '../../../../task_manager/server';
+import { CoreSetup, SavedObjectsClient } from '../../../../../../src/core/server';
+import { SEARCH_SESSION_TYPE } from '../../../../../../src/plugins/data/common';
+import { DataEnhancedStartDependencies } from '../../type';
+import {
+ SearchSessionTaskSetupDeps,
+ SearchSessionTaskStartDeps,
+ SearchSessionTaskFn,
+} from './types';
+
+export function searchSessionTaskRunner(
+ core: CoreSetup,
+ deps: SearchSessionTaskSetupDeps,
+ title: string,
+ checkFn: SearchSessionTaskFn
+): TaskRunCreatorFunction {
+ const { logger, config } = deps;
+ return ({ taskInstance }: RunContext) => {
+ const aborted$ = new BehaviorSubject(false);
+ return {
+ async run() {
+ try {
+ const sessionConfig = config.search.sessions;
+ const [coreStart] = await core.getStartServices();
+ if (!sessionConfig.enabled) {
+ logger.debug(`Search sessions are disabled. Skipping task ${title}.`);
+ return;
+ }
+ if (aborted$.getValue()) return;
+
+ const internalRepo = coreStart.savedObjects.createInternalRepository([
+ SEARCH_SESSION_TYPE,
+ ]);
+ const internalSavedObjectsClient = new SavedObjectsClient(internalRepo);
+ await checkFn(
+ {
+ logger,
+ client: coreStart.elasticsearch.client.asInternalUser,
+ savedObjectsClient: internalSavedObjectsClient,
+ },
+ sessionConfig
+ )
+ .pipe(takeUntil(aborted$.pipe(filter((aborted) => aborted))))
+ .toPromise();
+
+ return {
+ state: {},
+ };
+ } catch (e) {
+ logger.error(`An error occurred. Skipping task ${title}.`);
+ }
+ },
+ cancel: async () => {
+ aborted$.next(true);
+ },
+ };
+ };
+}
+
+export function registerSearchSessionsTask(
+ core: CoreSetup,
+ deps: SearchSessionTaskSetupDeps,
+ taskType: string,
+ title: string,
+ checkFn: SearchSessionTaskFn
+) {
+ deps.taskManager.registerTaskDefinitions({
+ [taskType]: {
+ title,
+ createTaskRunner: searchSessionTaskRunner(core, deps, title, checkFn),
+ timeout: `${deps.config.search.sessions.monitoringTaskTimeout.asSeconds()}s`,
+ },
+ });
+}
+
+export async function unscheduleSearchSessionsTask(
+ { taskManager, logger }: SearchSessionTaskStartDeps,
+ taskId: string
+) {
+ try {
+ await taskManager.removeIfExists(taskId);
+ logger.debug(`${taskId} cleared`);
+ } catch (e) {
+ logger.error(`${taskId} Error clearing task ${e.message}`);
+ }
+}
+
+export async function scheduleSearchSessionsTask(
+ { taskManager, logger }: SearchSessionTaskStartDeps,
+ taskId: string,
+ taskType: string,
+ interval: Duration
+) {
+ await taskManager.removeIfExists(taskId);
+
+ try {
+ await taskManager.ensureScheduled({
+ id: taskId,
+ taskType,
+ schedule: {
+ interval: `${interval.asSeconds()}s`,
+ },
+ state: {},
+ params: {},
+ });
+
+ logger.debug(`${taskId} scheduled to run`);
+ } catch (e) {
+ logger.error(`${taskId} Error scheduling task ${e.message}`);
+ }
+}
diff --git a/x-pack/plugins/data_enhanced/server/search/session/types.ts b/x-pack/plugins/data_enhanced/server/search/session/types.ts
index 0fa384e55f7d7..eadc3821c1043 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/types.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/types.ts
@@ -5,6 +5,18 @@
* 2.0.
*/
+import {
+ ElasticsearchClient,
+ Logger,
+ SavedObjectsClientContract,
+ SavedObjectsFindResponse,
+} from 'kibana/server';
+import { Observable } from 'rxjs';
+import { KueryNode, SearchSessionSavedObjectAttributes } from 'src/plugins/data/common';
+import {
+ TaskManagerSetupContract,
+ TaskManagerStartContract,
+} from '../../../../../../x-pack/plugins/task_manager/server';
import { ConfigSchema } from '../../../config';
export enum SearchStatus {
@@ -14,3 +26,38 @@ export enum SearchStatus {
}
export type SearchSessionsConfig = ConfigSchema['search']['sessions'];
+
+export interface CheckSearchSessionsDeps {
+ savedObjectsClient: SavedObjectsClientContract;
+ client: ElasticsearchClient;
+ logger: Logger;
+}
+
+export interface SearchSessionTaskSetupDeps {
+ taskManager: TaskManagerSetupContract;
+ logger: Logger;
+ config: ConfigSchema;
+}
+
+export interface SearchSessionTaskStartDeps {
+ taskManager: TaskManagerStartContract;
+ logger: Logger;
+ config: ConfigSchema;
+}
+
+export type SearchSessionTaskFn = (
+ deps: CheckSearchSessionsDeps,
+ config: SearchSessionsConfig
+) => Observable;
+
+export type SearchSessionsResponse = SavedObjectsFindResponse<
+ SearchSessionSavedObjectAttributes,
+ unknown
+>;
+
+export type CheckSearchSessionsFn = (
+ deps: CheckSearchSessionsDeps,
+ config: SearchSessionsConfig,
+ filter: KueryNode,
+ page: number
+) => Observable;
diff --git a/x-pack/plugins/data_enhanced/server/search/session/update_session_status.test.ts b/x-pack/plugins/data_enhanced/server/search/session/update_session_status.test.ts
new file mode 100644
index 0000000000000..485a30fd54951
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/server/search/session/update_session_status.test.ts
@@ -0,0 +1,323 @@
+/*
+ * 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 { bulkUpdateSessions, updateSessionStatus } from './update_session_status';
+import {
+ SearchSessionStatus,
+ SearchSessionSavedObjectAttributes,
+} from '../../../../../../src/plugins/data/common';
+import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks';
+import { SearchStatus } from './types';
+import moment from 'moment';
+import {
+ SavedObjectsBulkUpdateObject,
+ SavedObjectsClientContract,
+ SavedObjectsFindResult,
+} from '../../../../../../src/core/server';
+
+describe('bulkUpdateSessions', () => {
+ let mockClient: any;
+ let savedObjectsClient: jest.Mocked;
+ const mockLogger: any = {
+ debug: jest.fn(),
+ warn: jest.fn(),
+ error: jest.fn(),
+ };
+
+ beforeEach(() => {
+ savedObjectsClient = savedObjectsClientMock.create();
+ mockClient = {
+ asyncSearch: {
+ status: jest.fn(),
+ delete: jest.fn(),
+ },
+ eql: {
+ status: jest.fn(),
+ delete: jest.fn(),
+ },
+ };
+ });
+
+ describe('updateSessionStatus', () => {
+ test('updates expired session', async () => {
+ const so: SavedObjectsFindResult = {
+ id: '123',
+ attributes: {
+ persisted: false,
+ status: SearchSessionStatus.IN_PROGRESS,
+ expires: moment().subtract(moment.duration(5, 'd')),
+ idMapping: {
+ 'search-hash': {
+ id: 'search-id',
+ strategy: 'cool',
+ status: SearchStatus.IN_PROGRESS,
+ },
+ },
+ },
+ } as any;
+
+ const updated = await updateSessionStatus(
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ so
+ );
+
+ expect(updated).toBeTruthy();
+ expect(so.attributes.status).toBe(SearchSessionStatus.EXPIRED);
+ });
+
+ test('does nothing if the search is still running', async () => {
+ const so = {
+ id: '123',
+ attributes: {
+ persisted: false,
+ status: SearchSessionStatus.IN_PROGRESS,
+ created: moment().subtract(moment.duration(3, 'm')),
+ touched: moment().subtract(moment.duration(10, 's')),
+ expires: moment().add(moment.duration(5, 'd')),
+ idMapping: {
+ 'search-hash': {
+ id: 'search-id',
+ strategy: 'cool',
+ status: SearchStatus.IN_PROGRESS,
+ },
+ },
+ },
+ } as any;
+
+ mockClient.asyncSearch.status.mockResolvedValue({
+ body: {
+ is_partial: true,
+ is_running: true,
+ },
+ });
+
+ const updated = await updateSessionStatus(
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ so
+ );
+
+ expect(updated).toBeFalsy();
+ expect(so.attributes.status).toBe(SearchSessionStatus.IN_PROGRESS);
+ });
+
+ test("doesn't re-check completed or errored searches", async () => {
+ const so = {
+ id: '123',
+ attributes: {
+ expires: moment().add(moment.duration(5, 'd')),
+ status: SearchSessionStatus.ERROR,
+ idMapping: {
+ 'search-hash': {
+ id: 'search-id',
+ strategy: 'cool',
+ status: SearchStatus.COMPLETE,
+ },
+ 'another-search-hash': {
+ id: 'search-id',
+ strategy: 'cool',
+ status: SearchStatus.ERROR,
+ },
+ },
+ },
+ } as any;
+
+ const updated = await updateSessionStatus(
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ so
+ );
+
+ expect(updated).toBeFalsy();
+ expect(mockClient.asyncSearch.status).not.toBeCalled();
+ });
+
+ test('updates to complete if the search is done', async () => {
+ savedObjectsClient.bulkUpdate = jest.fn();
+ const so = {
+ attributes: {
+ status: SearchSessionStatus.IN_PROGRESS,
+ touched: '123',
+ expires: moment().add(moment.duration(5, 'd')),
+ idMapping: {
+ 'search-hash': {
+ id: 'search-id',
+ strategy: 'cool',
+ status: SearchStatus.IN_PROGRESS,
+ },
+ },
+ },
+ } as any;
+ mockClient.asyncSearch.status.mockResolvedValue({
+ body: {
+ is_partial: false,
+ is_running: false,
+ completion_status: 200,
+ },
+ });
+
+ const updated = await updateSessionStatus(
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ so
+ );
+
+ expect(updated).toBeTruthy();
+
+ expect(mockClient.asyncSearch.status).toBeCalledWith({ id: 'search-id' });
+ expect(so.attributes.status).toBe(SearchSessionStatus.COMPLETE);
+ expect(so.attributes.status).toBe(SearchSessionStatus.COMPLETE);
+ expect(so.attributes.touched).not.toBe('123');
+ expect(so.attributes.completed).not.toBeUndefined();
+ expect(so.attributes.idMapping['search-hash'].status).toBe(SearchStatus.COMPLETE);
+ expect(so.attributes.idMapping['search-hash'].error).toBeUndefined();
+ });
+
+ test('updates to error if the search is errored', async () => {
+ savedObjectsClient.bulkUpdate = jest.fn();
+ const so = {
+ attributes: {
+ expires: moment().add(moment.duration(5, 'd')),
+ idMapping: {
+ 'search-hash': {
+ id: 'search-id',
+ strategy: 'cool',
+ status: SearchStatus.IN_PROGRESS,
+ },
+ },
+ },
+ } as any;
+
+ mockClient.asyncSearch.status.mockResolvedValue({
+ body: {
+ is_partial: false,
+ is_running: false,
+ completion_status: 500,
+ },
+ });
+
+ const updated = await updateSessionStatus(
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ so
+ );
+
+ expect(updated).toBeTruthy();
+ expect(so.attributes.status).toBe(SearchSessionStatus.ERROR);
+ expect(so.attributes.touched).not.toBe('123');
+ expect(so.attributes.idMapping['search-hash'].status).toBe(SearchStatus.ERROR);
+ expect(so.attributes.idMapping['search-hash'].error).toBe(
+ 'Search completed with a 500 status'
+ );
+ });
+ });
+
+ describe('bulkUpdateSessions', () => {
+ test('does nothing if there are no open sessions', async () => {
+ await bulkUpdateSessions(
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ []
+ );
+
+ expect(savedObjectsClient.bulkUpdate).not.toBeCalled();
+ expect(savedObjectsClient.delete).not.toBeCalled();
+ });
+
+ test('updates in space', async () => {
+ const so = {
+ namespaces: ['awesome'],
+ attributes: {
+ expires: moment().add(moment.duration(5, 'd')),
+ status: SearchSessionStatus.IN_PROGRESS,
+ touched: '123',
+ idMapping: {
+ 'search-hash': {
+ id: 'search-id',
+ strategy: 'cool',
+ status: SearchStatus.IN_PROGRESS,
+ },
+ },
+ },
+ } as any;
+
+ savedObjectsClient.bulkUpdate = jest.fn().mockResolvedValue({
+ saved_objects: [so],
+ });
+
+ await bulkUpdateSessions(
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ [so]
+ );
+
+ const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0];
+ const updatedAttributes = updateInput[0] as SavedObjectsBulkUpdateObject;
+ expect(updatedAttributes.namespace).toBe('awesome');
+ });
+
+ test('logs failures', async () => {
+ const so = {
+ namespaces: ['awesome'],
+ attributes: {
+ expires: moment().add(moment.duration(5, 'd')),
+ status: SearchSessionStatus.IN_PROGRESS,
+ touched: '123',
+ idMapping: {
+ 'search-hash': {
+ id: 'search-id',
+ strategy: 'cool',
+ status: SearchStatus.IN_PROGRESS,
+ },
+ },
+ },
+ } as any;
+
+ savedObjectsClient.bulkUpdate = jest.fn().mockResolvedValue({
+ saved_objects: [
+ {
+ error: 'nope',
+ },
+ ],
+ });
+
+ await bulkUpdateSessions(
+ {
+ savedObjectsClient,
+ client: mockClient,
+ logger: mockLogger,
+ },
+ [so]
+ );
+
+ expect(savedObjectsClient.bulkUpdate).toBeCalledTimes(1);
+ expect(mockLogger.error).toBeCalledTimes(1);
+ });
+ });
+});
diff --git a/x-pack/plugins/data_enhanced/server/search/session/update_session_status.ts b/x-pack/plugins/data_enhanced/server/search/session/update_session_status.ts
new file mode 100644
index 0000000000000..1c484467bef63
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/server/search/session/update_session_status.ts
@@ -0,0 +1,128 @@
+/*
+ * 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 { SavedObjectsFindResult, SavedObjectsUpdateResponse } from 'kibana/server';
+import {
+ SearchSessionRequestInfo,
+ SearchSessionSavedObjectAttributes,
+ SearchSessionStatus,
+} from '../../../../../../src/plugins/data/common';
+import { getSearchStatus } from './get_search_status';
+import { getSessionStatus } from './get_session_status';
+import { CheckSearchSessionsDeps, SearchSessionsResponse, SearchStatus } from './types';
+import { isSearchSessionExpired } from './utils';
+
+export async function updateSessionStatus(
+ { logger, client }: CheckSearchSessionsDeps,
+ session: SavedObjectsFindResult
+) {
+ let sessionUpdated = false;
+ const isExpired = isSearchSessionExpired(session);
+
+ if (!isExpired) {
+ // Check statuses of all running searches
+ await Promise.all(
+ Object.keys(session.attributes.idMapping).map(async (searchKey: string) => {
+ const updateSearchRequest = (
+ currentStatus: Pick
+ ) => {
+ sessionUpdated = true;
+ session.attributes.idMapping[searchKey] = {
+ ...session.attributes.idMapping[searchKey],
+ ...currentStatus,
+ };
+ };
+
+ const searchInfo = session.attributes.idMapping[searchKey];
+ if (searchInfo.status === SearchStatus.IN_PROGRESS) {
+ try {
+ const currentStatus = await getSearchStatus(client, searchInfo.id);
+
+ if (currentStatus.status !== searchInfo.status) {
+ logger.debug(`search ${searchInfo.id} | status changed to ${currentStatus.status}`);
+ updateSearchRequest(currentStatus);
+ }
+ } catch (e) {
+ logger.error(e);
+ updateSearchRequest({
+ status: SearchStatus.ERROR,
+ error: e.message || e.meta.error?.caused_by?.reason,
+ });
+ }
+ }
+ })
+ );
+ }
+
+ // And only then derive the session's status
+ const sessionStatus = isExpired
+ ? SearchSessionStatus.EXPIRED
+ : getSessionStatus(session.attributes);
+ if (sessionStatus !== session.attributes.status) {
+ const now = new Date().toISOString();
+ session.attributes.status = sessionStatus;
+ session.attributes.touched = now;
+ if (sessionStatus === SearchSessionStatus.COMPLETE) {
+ session.attributes.completed = now;
+ } else if (session.attributes.completed) {
+ session.attributes.completed = null;
+ }
+ sessionUpdated = true;
+ }
+
+ return sessionUpdated;
+}
+
+export async function getAllSessionsStatusUpdates(
+ deps: CheckSearchSessionsDeps,
+ searchSessions: SearchSessionsResponse
+) {
+ const updatedSessions = new Array>();
+
+ await Promise.all(
+ searchSessions.saved_objects.map(async (session) => {
+ const updated = await updateSessionStatus(deps, session);
+
+ if (updated) {
+ updatedSessions.push(session);
+ }
+ })
+ );
+
+ return updatedSessions;
+}
+
+export async function bulkUpdateSessions(
+ { logger, savedObjectsClient }: CheckSearchSessionsDeps,
+ updatedSessions: Array>
+) {
+ if (updatedSessions.length) {
+ // If there's an error, we'll try again in the next iteration, so there's no need to check the output.
+ const updatedResponse = await savedObjectsClient.bulkUpdate(
+ updatedSessions.map((session) => ({
+ ...session,
+ namespace: session.namespaces?.[0],
+ }))
+ );
+
+ const success: Array> = [];
+ const fail: Array> = [];
+
+ updatedResponse.saved_objects.forEach((savedObjectResponse) => {
+ if ('error' in savedObjectResponse) {
+ fail.push(savedObjectResponse);
+ logger.error(
+ `Error while updating search session ${savedObjectResponse?.id}: ${savedObjectResponse.error?.message}`
+ );
+ } else {
+ success.push(savedObjectResponse);
+ }
+ });
+
+ logger.debug(`Updating search sessions: success: ${success.length}, fail: ${fail.length}`);
+ }
+}
diff --git a/x-pack/plugins/data_enhanced/server/search/session/utils.ts b/x-pack/plugins/data_enhanced/server/search/session/utils.ts
index 7b1f1a7564626..55c875602694f 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/utils.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/utils.ts
@@ -7,6 +7,9 @@
import { createHash } from 'crypto';
import stringify from 'json-stable-stringify';
+import { SavedObjectsFindResult } from 'kibana/server';
+import moment from 'moment';
+import { SearchSessionSavedObjectAttributes } from 'src/plugins/data/common';
/**
* Generate the hash for this request so that, in the future, this hash can be used to look up
@@ -17,3 +20,9 @@ export function createRequestHash(keys: Record) {
const { preference, ...params } = keys;
return createHash(`sha256`).update(stringify(params)).digest('hex');
}
+
+export function isSearchSessionExpired(
+ session: SavedObjectsFindResult
+) {
+ return moment(session.attributes.expires).isBefore(moment());
+}
diff --git a/x-pack/test/api_integration/config.ts b/x-pack/test/api_integration/config.ts
index 6708a6d55f402..550148531e2ec 100644
--- a/x-pack/test/api_integration/config.ts
+++ b/x-pack/test/api_integration/config.ts
@@ -33,6 +33,7 @@ export async function getApiIntegrationConfig({ readConfigFile }: FtrConfigProvi
'--xpack.data_enhanced.search.sessions.enabled=true', // enable WIP send to background UI
'--xpack.data_enhanced.search.sessions.notTouchedTimeout=15s', // shorten notTouchedTimeout for quicker testing
'--xpack.data_enhanced.search.sessions.trackingInterval=5s', // shorten trackingInterval for quicker testing
+ '--xpack.data_enhanced.search.sessions.cleanupInterval=5s', // shorten cleanupInterval for quicker testing
],
},
esTestCluster: {
From 6a1e4b8d4de7f50b3f2e42d2d44381ff120fd37a Mon Sep 17 00:00:00 2001
From: Robert Oskamp
Date: Thu, 24 Jun 2021 10:58:18 +0200
Subject: [PATCH 15/86] [ML] Functional tests - fix and re-enable close_jobs
API tests (#103114)
This PR fixes and re-enables the close_jobs API tests after a backend change.
---
.../apis/ml/jobs/close_jobs.ts | 273 ++++++++----------
1 file changed, 124 insertions(+), 149 deletions(-)
diff --git a/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts b/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts
index 4c639d3a166cd..40485205f9fb5 100644
--- a/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts
+++ b/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts
@@ -20,68 +20,6 @@ export default ({ getService }: FtrProviderContext) => {
const testSetupJobConfigs = [SINGLE_METRIC_JOB_CONFIG, MULTI_METRIC_JOB_CONFIG];
- const testDataList = [
- {
- testTitle: 'as ML Poweruser',
- user: USER.ML_POWERUSER,
- requestBody: {
- jobIds: [SINGLE_METRIC_JOB_CONFIG.job_id, MULTI_METRIC_JOB_CONFIG.job_id],
- },
- expected: {
- responseCode: 200,
- responseBody: {
- [SINGLE_METRIC_JOB_CONFIG.job_id]: { closed: true },
- [MULTI_METRIC_JOB_CONFIG.job_id]: { closed: true },
- },
- },
- },
- ];
-
- const testDataListFailed = [
- {
- testTitle: 'as ML Poweruser',
- user: USER.ML_POWERUSER,
- requestBody: {
- jobIds: [SINGLE_METRIC_JOB_CONFIG.job_id, MULTI_METRIC_JOB_CONFIG.job_id],
- },
- expected: {
- responseCode: 200,
-
- responseBody: {
- [SINGLE_METRIC_JOB_CONFIG.job_id]: { closed: false, error: { status: 409 } },
- [MULTI_METRIC_JOB_CONFIG.job_id]: { closed: false, error: { status: 409 } },
- },
- },
- },
- ];
-
- const testDataListUnauthorized = [
- {
- testTitle: 'as ML Unauthorized user',
- user: USER.ML_UNAUTHORIZED,
- requestBody: {
- jobIds: [SINGLE_METRIC_JOB_CONFIG.job_id, MULTI_METRIC_JOB_CONFIG.job_id],
- },
- // Note that the jobs and datafeeds are loaded async so the actual error message is not deterministic.
- expected: {
- responseCode: 403,
- error: 'Forbidden',
- },
- },
- {
- testTitle: 'as ML Viewer',
- user: USER.ML_VIEWER,
- requestBody: {
- jobIds: [SINGLE_METRIC_JOB_CONFIG.job_id, MULTI_METRIC_JOB_CONFIG.job_id],
- },
- // Note that the jobs and datafeeds are loaded async so the actual error message is not deterministic.
- expected: {
- responseCode: 403,
- error: 'Forbidden',
- },
- },
- ];
-
async function runCloseJobsRequest(
user: USER,
requestBody: object,
@@ -97,19 +35,22 @@ export default ({ getService }: FtrProviderContext) => {
return body;
}
- // failing ES snapshot promotion after backend change, see https://github.com/elastic/kibana/issues/103023
- describe.skip('close_jobs', function () {
+ async function startDatafeedsInRealtime() {
+ for (const job of testSetupJobConfigs) {
+ const datafeedId = `datafeed-${job.job_id}`;
+ await ml.api.startDatafeed(datafeedId, { start: '0' });
+ await ml.api.waitForDatafeedState(datafeedId, DATAFEED_STATE.STARTED);
+ }
+ }
+
+ describe('close_jobs', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp');
await ml.testResources.setKibanaTimeZoneToUTC();
});
- after(async () => {
- await ml.api.cleanMlIndices();
- });
-
- it('sets up jobs', async () => {
+ beforeEach(async () => {
for (const job of testSetupJobConfigs) {
const datafeedId = `datafeed-${job.job_id}`;
await ml.api.createAnomalyDetectionJob(job);
@@ -119,98 +60,132 @@ export default ({ getService }: FtrProviderContext) => {
datafeed_id: datafeedId,
job_id: job.job_id,
});
- await ml.api.startDatafeed(datafeedId, { start: '0' });
- await ml.api.waitForDatafeedState(datafeedId, DATAFEED_STATE.STARTED);
}
});
- describe('rejects request', function () {
- for (const testData of testDataListUnauthorized) {
- describe('fails to close job ID supplied', function () {
- it(`${testData.testTitle}`, async () => {
- const body = await runCloseJobsRequest(
- testData.user,
- testData.requestBody,
- testData.expected.responseCode
- );
-
- expect(body).to.have.property('error').eql(testData.expected.error);
-
- // ensure jobs are still open
- for (const id of testData.requestBody.jobIds) {
- await ml.api.waitForJobState(id, JOB_STATE.OPENED);
- }
- });
- });
+ afterEach(async () => {
+ for (const job of testSetupJobConfigs) {
+ await ml.api.deleteAnomalyDetectionJobES(job.job_id);
}
+ await ml.api.cleanMlIndices();
});
- describe('close jobs fail because they are running', function () {
- for (const testData of testDataListFailed) {
- it(`${testData.testTitle}`, async () => {
- const body = await runCloseJobsRequest(
- testData.user,
- testData.requestBody,
- testData.expected.responseCode
- );
- const expectedResponse = testData.expected.responseBody;
- const expectedRspJobIds = Object.keys(expectedResponse).sort((a, b) =>
- a.localeCompare(b)
- );
- const actualRspJobIds = Object.keys(body).sort((a, b) => a.localeCompare(b));
-
- expect(actualRspJobIds).to.have.length(expectedRspJobIds.length);
- expect(actualRspJobIds).to.eql(expectedRspJobIds);
-
- expectedRspJobIds.forEach((id) => {
- expect(body[id].closed).to.eql(testData.expected.responseBody[id].closed);
- expect(body[id].error.status).to.eql(testData.expected.responseBody[id].error.status);
- });
-
- // ensure jobs are still open
- for (const id of testData.requestBody.jobIds) {
- await ml.api.waitForJobState(id, JOB_STATE.OPENED);
- }
- });
+ it('rejects request for ML Unauthorized user', async () => {
+ await startDatafeedsInRealtime();
+
+ const jobIds = [SINGLE_METRIC_JOB_CONFIG.job_id, MULTI_METRIC_JOB_CONFIG.job_id];
+ const body = await runCloseJobsRequest(USER.ML_UNAUTHORIZED, { jobIds }, 403);
+
+ expect(body).to.have.property('error').eql('Forbidden');
+
+ // ensure jobs are still open
+ for (const id of jobIds) {
+ await ml.api.waitForJobState(id, JOB_STATE.OPENED);
+ }
+ });
+
+ it('rejects request for ML Viewer user', async () => {
+ await startDatafeedsInRealtime();
+
+ const jobIds = [SINGLE_METRIC_JOB_CONFIG.job_id, MULTI_METRIC_JOB_CONFIG.job_id];
+ const body = await runCloseJobsRequest(USER.ML_VIEWER, { jobIds }, 403);
+
+ expect(body).to.have.property('error').eql('Forbidden');
+
+ // ensure jobs are still open
+ for (const id of jobIds) {
+ await ml.api.waitForJobState(id, JOB_STATE.OPENED);
+ }
+ });
+
+ it('succeeds for ML Poweruser with datafeed started', async () => {
+ await startDatafeedsInRealtime();
+
+ const jobIds = [SINGLE_METRIC_JOB_CONFIG.job_id, MULTI_METRIC_JOB_CONFIG.job_id];
+ const body = await runCloseJobsRequest(USER.ML_POWERUSER, { jobIds }, 200);
+
+ const expectedRspBody = {
+ [SINGLE_METRIC_JOB_CONFIG.job_id]: { closed: true },
+ [MULTI_METRIC_JOB_CONFIG.job_id]: { closed: true },
+ };
+ const expectedRspJobIds = Object.keys(expectedRspBody).sort((a, b) => a.localeCompare(b));
+ const actualRspJobIds = Object.keys(body).sort((a, b) => a.localeCompare(b));
+
+ expect(actualRspJobIds).to.have.length(expectedRspJobIds.length);
+ expect(actualRspJobIds).to.eql(expectedRspJobIds);
+
+ expectedRspJobIds.forEach((id) => {
+ expect(body[id].closed).to.eql(expectedRspBody[id].closed);
+ });
+
+ // datafeeds should be stopped automatically
+ for (const id of jobIds) {
+ await ml.api.waitForDatafeedState(`datafeed-${id}`, DATAFEED_STATE.STOPPED);
+ }
+
+ // ensure jobs are actually closed
+ for (const id of jobIds) {
+ await ml.api.waitForJobState(id, JOB_STATE.CLOSED);
}
});
- describe('stops datafeeds', function () {
- it('stops datafeeds', async () => {
- for (const job of testSetupJobConfigs) {
- const datafeedId = `datafeed-${job.job_id}`;
- await ml.api.stopDatafeed(datafeedId);
- await ml.api.waitForDatafeedState(datafeedId, DATAFEED_STATE.STOPPED);
- }
+ it('succeeds for ML Poweruser with datafeed stopped', async () => {
+ const jobIds = [SINGLE_METRIC_JOB_CONFIG.job_id, MULTI_METRIC_JOB_CONFIG.job_id];
+ const body = await runCloseJobsRequest(USER.ML_POWERUSER, { jobIds }, 200);
+
+ const expectedRspBody = {
+ [SINGLE_METRIC_JOB_CONFIG.job_id]: { closed: true },
+ [MULTI_METRIC_JOB_CONFIG.job_id]: { closed: true },
+ };
+ const expectedRspJobIds = Object.keys(expectedRspBody).sort((a, b) => a.localeCompare(b));
+ const actualRspJobIds = Object.keys(body).sort((a, b) => a.localeCompare(b));
+
+ expect(actualRspJobIds).to.have.length(expectedRspJobIds.length);
+ expect(actualRspJobIds).to.eql(expectedRspJobIds);
+
+ expectedRspJobIds.forEach((id) => {
+ expect(body[id].closed).to.eql(expectedRspBody[id].closed);
});
+
+ // datafeeds should still be stopped
+ for (const id of jobIds) {
+ await ml.api.waitForDatafeedState(`datafeed-${id}`, DATAFEED_STATE.STOPPED);
+ }
+
+ // ensure jobs are actually closed
+ for (const id of jobIds) {
+ await ml.api.waitForJobState(id, JOB_STATE.CLOSED);
+ }
});
- describe('close jobs succeed', function () {
- for (const testData of testDataList) {
- it(`${testData.testTitle}`, async () => {
- const body = await runCloseJobsRequest(
- testData.user,
- testData.requestBody,
- testData.expected.responseCode
- );
- const expectedResponse = testData.expected.responseBody;
- const expectedRspJobIds = Object.keys(expectedResponse).sort((a, b) =>
- a.localeCompare(b)
- );
- const actualRspJobIds = Object.keys(body).sort((a, b) => a.localeCompare(b));
-
- expect(actualRspJobIds).to.have.length(expectedRspJobIds.length);
- expect(actualRspJobIds).to.eql(expectedRspJobIds);
-
- expectedRspJobIds.forEach((id) => {
- expect(body[id].closed).to.eql(testData.expected.responseBody[id].closed);
- });
-
- // ensure jobs are now closed
- for (const id of testData.requestBody.jobIds) {
- await ml.api.waitForJobState(id, JOB_STATE.CLOSED);
- }
- });
+ it('succeeds for ML Poweruser with job already closed', async () => {
+ const jobIds = [SINGLE_METRIC_JOB_CONFIG.job_id, MULTI_METRIC_JOB_CONFIG.job_id];
+ await runCloseJobsRequest(USER.ML_POWERUSER, { jobIds }, 200);
+
+ const body = await runCloseJobsRequest(USER.ML_POWERUSER, { jobIds }, 200);
+
+ const expectedRspBody = {
+ [SINGLE_METRIC_JOB_CONFIG.job_id]: { closed: true },
+ [MULTI_METRIC_JOB_CONFIG.job_id]: { closed: true },
+ };
+ const expectedRspJobIds = Object.keys(expectedRspBody).sort((a, b) => a.localeCompare(b));
+ const actualRspJobIds = Object.keys(body).sort((a, b) => a.localeCompare(b));
+
+ expect(actualRspJobIds).to.have.length(expectedRspJobIds.length);
+ expect(actualRspJobIds).to.eql(expectedRspJobIds);
+
+ expectedRspJobIds.forEach((id) => {
+ expect(body[id].closed).to.eql(expectedRspBody[id].closed);
+ });
+
+ // datafeeds should still be stopped
+ for (const id of jobIds) {
+ await ml.api.waitForDatafeedState(`datafeed-${id}`, DATAFEED_STATE.STOPPED);
+ }
+
+ // jobs should still be closed
+ for (const id of jobIds) {
+ await ml.api.waitForJobState(id, JOB_STATE.CLOSED);
}
});
});
From 8298b78a62fddda129e91323e2771b1039929671 Mon Sep 17 00:00:00 2001
From: Matthias Wilhelm
Date: Thu, 24 Jun 2021 11:23:41 +0200
Subject: [PATCH 16/86] [Discover] Unskip and improve sidebar filter functional
test (#102986)
---
test/functional/apps/discover/_sidebar.ts | 3 +--
test/functional/page_objects/discover_page.ts | 5 ++++-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/test/functional/apps/discover/_sidebar.ts b/test/functional/apps/discover/_sidebar.ts
index 8179f4e44e8b8..d8701261126c4 100644
--- a/test/functional/apps/discover/_sidebar.ts
+++ b/test/functional/apps/discover/_sidebar.ts
@@ -14,8 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['common', 'discover', 'timePicker']);
const testSubjects = getService('testSubjects');
- // Failing: See https://github.com/elastic/kibana/issues/101449
- describe.skip('discover sidebar', function describeIndexTests() {
+ describe('discover sidebar', function describeIndexTests() {
before(async function () {
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/discover');
diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts
index 65b899d2e2fb0..dc3a04568316e 100644
--- a/test/functional/page_objects/discover_page.ts
+++ b/test/functional/page_objects/discover_page.ts
@@ -448,7 +448,10 @@ export class DiscoverPageObject extends FtrService {
public async closeSidebarFieldFilter() {
await this.testSubjects.click('toggleFieldFilterButton');
- await this.testSubjects.missingOrFail('filterSelectionPanel');
+
+ await this.retry.waitFor('sidebar filter closed', async () => {
+ return !(await this.testSubjects.exists('filterSelectionPanel'));
+ });
}
public async waitForChartLoadingComplete(renderCount: number) {
From 507ab0e8d042d80c7ad56160416bc1abf404b8ab Mon Sep 17 00:00:00 2001
From: Matthias Wilhelm
Date: Thu, 24 Jun 2021 11:29:23 +0200
Subject: [PATCH 17/86] [Discover] Add right permission for unmapped field test
in cloud env (#102853)
---
.../_indexpattern_with_unmapped_fields.ts | 15 ++++++++++-----
test/functional/config.js | 15 +++++++++++++++
2 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts b/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts
index e986429a15d26..264885490cdfc 100644
--- a/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts
+++ b/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts
@@ -12,26 +12,31 @@ import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
- const log = getService('log');
+ const security = getService('security');
const retry = getService('retry');
const PageObjects = getPageObjects(['common', 'timePicker', 'discover']);
describe('index pattern with unmapped fields', () => {
before(async () => {
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/unmapped_fields');
+ await security.testUser.setRoles(['kibana_admin', 'test-index-unmapped-fields']);
+ const fromTime = 'Jan 20, 2021 @ 00:00:00.000';
+ const toTime = 'Jan 25, 2021 @ 00:00:00.000';
+
await kibanaServer.uiSettings.replace({
defaultIndex: 'test-index-unmapped-fields',
'discover:searchFieldsFromSource': false,
+ 'timepicker:timeDefaults': `{ "from": "${fromTime}", "to": "${toTime}"}`,
});
- log.debug('discover');
- const fromTime = 'Jan 20, 2021 @ 00:00:00.000';
- const toTime = 'Jan 25, 2021 @ 00:00:00.000';
+
await PageObjects.common.navigateToApp('discover');
- await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
});
after(async () => {
await esArchiver.unload('test/functional/fixtures/es_archiver/unmapped_fields');
+ await kibanaServer.uiSettings.unset('defaultIndex');
+ await kibanaServer.uiSettings.unset('discover:searchFieldsFromSource');
+ await kibanaServer.uiSettings.unset('timepicker:timeDefaults');
});
it('unmapped fields exist on a new saved search', async () => {
diff --git a/test/functional/config.js b/test/functional/config.js
index bab1148cf372a..b28a9fd36c1c0 100644
--- a/test/functional/config.js
+++ b/test/functional/config.js
@@ -292,6 +292,21 @@ export default async function ({ readConfigFile }) {
kibana: [],
},
+ 'test-index-unmapped-fields': {
+ elasticsearch: {
+ cluster: [],
+ indices: [
+ {
+ names: ['test-index-unmapped-fields'],
+ privileges: ['read', 'view_index_metadata'],
+ field_security: { grant: ['*'], except: [] },
+ },
+ ],
+ run_as: [],
+ },
+ kibana: [],
+ },
+
animals: {
elasticsearch: {
cluster: [],
From 1d2cebafd22017c9e6998c675b6010967fe9505a Mon Sep 17 00:00:00 2001
From: Stratoula Kalafateli
Date: Thu, 24 Jun 2021 12:33:35 +0300
Subject: [PATCH 18/86] [VisTypePie] Use a different advanced setting for pie
charts (#103049)
* Different switch for pie
* Remove unused translations
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
docs/management/advanced-options.asciidoc | 3 +
.../server/collectors/management/schema.ts | 4 ++
.../server/collectors/management/types.ts | 1 +
src/plugins/telemetry/schema/oss_plugins.json | 6 ++
src/plugins/vis_type_pie/common/index.ts | 1 +
src/plugins/vis_type_pie/kibana.json | 2 +
src/plugins/vis_type_pie/public/plugin.ts | 4 +-
src/plugins/vis_type_pie/server/index.ts | 10 ++++
src/plugins/vis_type_pie/server/plugin.ts | 56 +++++++++++++++++++
src/plugins/vis_type_vislib/public/plugin.ts | 24 ++++----
src/plugins/vis_type_xy/common/index.ts | 2 +
src/plugins/vis_type_xy/kibana.json | 2 +
src/plugins/vis_type_xy/public/plugin.ts | 2 +-
src/plugins/vis_type_xy/server/index.ts | 10 ++++
src/plugins/vis_type_xy/server/plugin.ts | 56 +++++++++++++++++++
.../visualizations/common/constants.ts | 1 -
src/plugins/visualizations/server/plugin.ts | 23 +-------
.../apps/dashboard/dashboard_state.ts | 1 +
test/functional/apps/dashboard/index.ts | 2 +
.../apps/getting_started/_shakespeare.ts | 1 +
test/functional/apps/getting_started/index.ts | 2 +
test/functional/apps/visualize/index.ts | 2 +
test/functional/config.js | 1 +
.../page_objects/visualize_chart_page.ts | 3 +-
.../functional/page_objects/visualize_page.ts | 1 +
.../translations/translations/ja-JP.json | 2 -
.../translations/translations/zh-CN.json | 2 -
x-pack/test/functional/config.js | 1 +
28 files changed, 183 insertions(+), 42 deletions(-)
create mode 100644 src/plugins/vis_type_pie/server/index.ts
create mode 100644 src/plugins/vis_type_pie/server/plugin.ts
create mode 100644 src/plugins/vis_type_xy/server/index.ts
create mode 100644 src/plugins/vis_type_xy/server/plugin.ts
diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc
index 853180ec816e9..66a23ee189ae1 100644
--- a/docs/management/advanced-options.asciidoc
+++ b/docs/management/advanced-options.asciidoc
@@ -482,6 +482,9 @@ of buckets to try to represent.
[[visualization-visualize-chartslibrary]]`visualization:visualize:legacyChartsLibrary`::
Enables the legacy charts library for aggregation-based area, line, and bar charts in *Visualize*.
+[[visualization-visualize-pieChartslibrary]]`visualization:visualize:legacyPieChartsLibrary`::
+Enables the legacy charts library for aggregation-based pie charts in *Visualize*.
+
[[visualization-colormapping]]`visualization:colorMapping`::
**This setting is deprecated and will not be supported as of 8.0.**
Maps values to specific colors in charts using the *Compatibility* palette.
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
index a6b79a9e2c009..ff637b6686612 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
@@ -396,6 +396,10 @@ export const stackManagementSchema: MakeSchemaFrom = {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
+ 'visualization:visualize:legacyPieChartsLibrary': {
+ type: 'boolean',
+ _meta: { description: 'Non-default value of setting.' },
+ },
'doc_table:legacy': {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
index 8448b359ce607..b59abc3aa7158 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
@@ -26,6 +26,7 @@ export interface UsageStats {
'autocomplete:useTimeRange': boolean;
'search:timeout': number;
'visualization:visualize:legacyChartsLibrary': boolean;
+ 'visualization:visualize:legacyPieChartsLibrary': boolean;
'doc_table:legacy': boolean;
'discover:modifyColumnsOnSwitch': boolean;
'discover:searchFieldsFromSource': boolean;
diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json
index 99c6dcb40e57d..496335a3b0dc8 100644
--- a/src/plugins/telemetry/schema/oss_plugins.json
+++ b/src/plugins/telemetry/schema/oss_plugins.json
@@ -8594,6 +8594,12 @@
"description": "Non-default value of setting."
}
},
+ "visualization:visualize:legacyPieChartsLibrary": {
+ "type": "boolean",
+ "_meta": {
+ "description": "Non-default value of setting."
+ }
+ },
"doc_table:legacy": {
"type": "boolean",
"_meta": {
diff --git a/src/plugins/vis_type_pie/common/index.ts b/src/plugins/vis_type_pie/common/index.ts
index 1aa1680530b32..a02a2b2ba10f2 100644
--- a/src/plugins/vis_type_pie/common/index.ts
+++ b/src/plugins/vis_type_pie/common/index.ts
@@ -7,3 +7,4 @@
*/
export const DEFAULT_PERCENT_DECIMALS = 2;
+export const LEGACY_PIE_CHARTS_LIBRARY = 'visualization:visualize:legacyPieChartsLibrary';
diff --git a/src/plugins/vis_type_pie/kibana.json b/src/plugins/vis_type_pie/kibana.json
index ee312fd19e8d5..eebefc42681b7 100644
--- a/src/plugins/vis_type_pie/kibana.json
+++ b/src/plugins/vis_type_pie/kibana.json
@@ -2,8 +2,10 @@
"id": "visTypePie",
"version": "kibana",
"ui": true,
+ "server": true,
"requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection"],
"requiredBundles": ["visDefaultEditor"],
+ "extraPublicDirs": ["common/index"],
"owner": {
"name": "Kibana App",
"githubTeam": "kibana-app"
diff --git a/src/plugins/vis_type_pie/public/plugin.ts b/src/plugins/vis_type_pie/public/plugin.ts
index 440a3a75a2eb1..787f49c19aca3 100644
--- a/src/plugins/vis_type_pie/public/plugin.ts
+++ b/src/plugins/vis_type_pie/public/plugin.ts
@@ -12,7 +12,7 @@ import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public';
import { ChartsPluginSetup } from '../../charts/public';
import { UsageCollectionSetup } from '../../usage_collection/public';
import { DataPublicPluginStart } from '../../data/public';
-import { LEGACY_CHARTS_LIBRARY } from '../../visualizations/common/constants';
+import { LEGACY_PIE_CHARTS_LIBRARY } from '../common';
import { pieLabels as pieLabelsExpressionFunction } from './expression_functions/pie_labels';
import { createPieVisFn } from './pie_fn';
import { getPieVisRenderer } from './pie_renderer';
@@ -43,7 +43,7 @@ export class VisTypePiePlugin {
core: CoreSetup,
{ expressions, visualizations, charts, usageCollection }: VisTypePieSetupDependencies
) {
- if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, false)) {
+ if (!core.uiSettings.get(LEGACY_PIE_CHARTS_LIBRARY, false)) {
const getStartDeps = async () => {
const [coreStart, deps] = await core.getStartServices();
return {
diff --git a/src/plugins/vis_type_pie/server/index.ts b/src/plugins/vis_type_pie/server/index.ts
new file mode 100644
index 0000000000000..201071fbb5fca
--- /dev/null
+++ b/src/plugins/vis_type_pie/server/index.ts
@@ -0,0 +1,10 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import { VisTypePieServerPlugin } from './plugin';
+
+export const plugin = () => new VisTypePieServerPlugin();
diff --git a/src/plugins/vis_type_pie/server/plugin.ts b/src/plugins/vis_type_pie/server/plugin.ts
new file mode 100644
index 0000000000000..48576bdff5d33
--- /dev/null
+++ b/src/plugins/vis_type_pie/server/plugin.ts
@@ -0,0 +1,56 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { schema } from '@kbn/config-schema';
+
+import { CoreSetup, Plugin, UiSettingsParams } from 'kibana/server';
+
+import { LEGACY_PIE_CHARTS_LIBRARY } from '../common';
+
+export const getUiSettingsConfig: () => Record> = () => ({
+ // TODO: Remove this when vis_type_vislib is removed
+ // https://github.com/elastic/kibana/issues/56143
+ [LEGACY_PIE_CHARTS_LIBRARY]: {
+ name: i18n.translate('visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.name', {
+ defaultMessage: 'Pie legacy charts library',
+ }),
+ requiresPageReload: true,
+ value: false,
+ description: i18n.translate(
+ 'visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.description',
+ {
+ defaultMessage: 'Enables legacy charts library for pie charts in visualize.',
+ }
+ ),
+ deprecation: {
+ message: i18n.translate(
+ 'visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.deprecation',
+ {
+ defaultMessage:
+ 'The legacy charts library for pie in visualize is deprecated and will not be supported as of 8.0.',
+ }
+ ),
+ docLinksKey: 'visualizationSettings',
+ },
+ category: ['visualization'],
+ schema: schema.boolean(),
+ },
+});
+
+export class VisTypePieServerPlugin implements Plugin {
+ public setup(core: CoreSetup) {
+ core.uiSettings.register(getUiSettingsConfig());
+
+ return {};
+ }
+
+ public start() {
+ return {};
+ }
+}
diff --git a/src/plugins/vis_type_vislib/public/plugin.ts b/src/plugins/vis_type_vislib/public/plugin.ts
index 52faf8a74778c..cdc02aacafa3b 100644
--- a/src/plugins/vis_type_vislib/public/plugin.ts
+++ b/src/plugins/vis_type_vislib/public/plugin.ts
@@ -13,7 +13,8 @@ import { VisualizationsSetup } from '../../visualizations/public';
import { ChartsPluginSetup } from '../../charts/public';
import { DataPublicPluginStart } from '../../data/public';
import { KibanaLegacyStart } from '../../kibana_legacy/public';
-import { LEGACY_CHARTS_LIBRARY } from '../../visualizations/common/constants';
+import { LEGACY_CHARTS_LIBRARY } from '../../vis_type_xy/common/index';
+import { LEGACY_PIE_CHARTS_LIBRARY } from '../../vis_type_pie/common/index';
import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn';
import { createPieVisFn } from './pie_fn';
@@ -50,17 +51,18 @@ export class VisTypeVislibPlugin
core: VisTypeVislibCoreSetup,
{ expressions, visualizations, charts }: VisTypeVislibPluginSetupDependencies
) {
- if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, false)) {
- // Register only non-replaced vis types
- convertedTypeDefinitions.forEach(visualizations.createBaseVisualization);
- expressions.registerRenderer(getVislibVisRenderer(core, charts));
- expressions.registerFunction(createVisTypeVislibVisFn());
- } else {
- // Register all vis types
- visLibVisTypeDefinitions.forEach(visualizations.createBaseVisualization);
+ const typeDefinitions = !core.uiSettings.get(LEGACY_CHARTS_LIBRARY, false)
+ ? convertedTypeDefinitions
+ : visLibVisTypeDefinitions;
+ // register vislib XY axis charts
+ typeDefinitions.forEach(visualizations.createBaseVisualization);
+ expressions.registerRenderer(getVislibVisRenderer(core, charts));
+ expressions.registerFunction(createVisTypeVislibVisFn());
+
+ if (core.uiSettings.get(LEGACY_PIE_CHARTS_LIBRARY, false)) {
+ // register vislib pie chart
visualizations.createBaseVisualization(pieVisTypeDefinition);
- expressions.registerRenderer(getVislibVisRenderer(core, charts));
- [createVisTypeVislibVisFn(), createPieVisFn()].forEach(expressions.registerFunction);
+ expressions.registerFunction(createPieVisFn());
}
}
diff --git a/src/plugins/vis_type_xy/common/index.ts b/src/plugins/vis_type_xy/common/index.ts
index f17bc8476d9a6..a80946f7c62fa 100644
--- a/src/plugins/vis_type_xy/common/index.ts
+++ b/src/plugins/vis_type_xy/common/index.ts
@@ -19,3 +19,5 @@ export enum ChartType {
* Type of xy visualizations
*/
export type XyVisType = ChartType | 'horizontal_bar';
+
+export const LEGACY_CHARTS_LIBRARY = 'visualization:visualize:legacyChartsLibrary';
diff --git a/src/plugins/vis_type_xy/kibana.json b/src/plugins/vis_type_xy/kibana.json
index 1d7fd6a0813b4..c25f035fb6d4b 100644
--- a/src/plugins/vis_type_xy/kibana.json
+++ b/src/plugins/vis_type_xy/kibana.json
@@ -2,8 +2,10 @@
"id": "visTypeXy",
"version": "kibana",
"ui": true,
+ "server": true,
"requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection"],
"requiredBundles": ["kibanaUtils", "visDefaultEditor"],
+ "extraPublicDirs": ["common/index"],
"owner": {
"name": "Kibana App",
"githubTeam": "kibana-app"
diff --git a/src/plugins/vis_type_xy/public/plugin.ts b/src/plugins/vis_type_xy/public/plugin.ts
index e8d53127765b4..b595d3172f143 100644
--- a/src/plugins/vis_type_xy/public/plugin.ts
+++ b/src/plugins/vis_type_xy/public/plugin.ts
@@ -23,7 +23,7 @@ import {
} from './services';
import { visTypesDefinitions } from './vis_types';
-import { LEGACY_CHARTS_LIBRARY } from '../../visualizations/common/constants';
+import { LEGACY_CHARTS_LIBRARY } from '../common/';
import { xyVisRenderer } from './vis_renderer';
import * as expressionFunctions from './expression_functions';
diff --git a/src/plugins/vis_type_xy/server/index.ts b/src/plugins/vis_type_xy/server/index.ts
new file mode 100644
index 0000000000000..a27ac49c0ea49
--- /dev/null
+++ b/src/plugins/vis_type_xy/server/index.ts
@@ -0,0 +1,10 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import { VisTypeXyServerPlugin } from './plugin';
+
+export const plugin = () => new VisTypeXyServerPlugin();
diff --git a/src/plugins/vis_type_xy/server/plugin.ts b/src/plugins/vis_type_xy/server/plugin.ts
new file mode 100644
index 0000000000000..46d6531204c24
--- /dev/null
+++ b/src/plugins/vis_type_xy/server/plugin.ts
@@ -0,0 +1,56 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { schema } from '@kbn/config-schema';
+
+import { CoreSetup, Plugin, UiSettingsParams } from 'kibana/server';
+
+import { LEGACY_CHARTS_LIBRARY } from '../common';
+
+export const getUiSettingsConfig: () => Record> = () => ({
+ // TODO: Remove this when vis_type_vislib is removed
+ // https://github.com/elastic/kibana/issues/56143
+ [LEGACY_CHARTS_LIBRARY]: {
+ name: i18n.translate('visTypeXy.advancedSettings.visualization.legacyChartsLibrary.name', {
+ defaultMessage: 'XY axis legacy charts library',
+ }),
+ requiresPageReload: true,
+ value: false,
+ description: i18n.translate(
+ 'visTypeXy.advancedSettings.visualization.legacyChartsLibrary.description',
+ {
+ defaultMessage: 'Enables legacy charts library for area, line and bar charts in visualize.',
+ }
+ ),
+ deprecation: {
+ message: i18n.translate(
+ 'visTypeXy.advancedSettings.visualization.legacyChartsLibrary.deprecation',
+ {
+ defaultMessage:
+ 'The legacy charts library for area, line and bar charts in visualize is deprecated and will not be supported as of 7.16.',
+ }
+ ),
+ docLinksKey: 'visualizationSettings',
+ },
+ category: ['visualization'],
+ schema: schema.boolean(),
+ },
+});
+
+export class VisTypeXyServerPlugin implements Plugin {
+ public setup(core: CoreSetup) {
+ core.uiSettings.register(getUiSettingsConfig());
+
+ return {};
+ }
+
+ public start() {
+ return {};
+ }
+}
diff --git a/src/plugins/visualizations/common/constants.ts b/src/plugins/visualizations/common/constants.ts
index a33e74b498a2c..a8a0963ac8948 100644
--- a/src/plugins/visualizations/common/constants.ts
+++ b/src/plugins/visualizations/common/constants.ts
@@ -7,4 +7,3 @@
*/
export const VISUALIZE_ENABLE_LABS_SETTING = 'visualize:enableLabs';
-export const LEGACY_CHARTS_LIBRARY = 'visualization:visualize:legacyChartsLibrary';
diff --git a/src/plugins/visualizations/server/plugin.ts b/src/plugins/visualizations/server/plugin.ts
index 1fec63f2bb45a..5a5a80b2689d6 100644
--- a/src/plugins/visualizations/server/plugin.ts
+++ b/src/plugins/visualizations/server/plugin.ts
@@ -18,7 +18,7 @@ import {
Logger,
} from '../../../core/server';
-import { VISUALIZE_ENABLE_LABS_SETTING, LEGACY_CHARTS_LIBRARY } from '../common/constants';
+import { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants';
import { visualizationSavedObjectType } from './saved_objects';
@@ -58,27 +58,6 @@ export class VisualizationsPlugin
category: ['visualization'],
schema: schema.boolean(),
},
- // TODO: Remove this when vis_type_vislib is removed
- // https://github.com/elastic/kibana/issues/56143
- [LEGACY_CHARTS_LIBRARY]: {
- name: i18n.translate(
- 'visualizations.advancedSettings.visualization.legacyChartsLibrary.name',
- {
- defaultMessage: 'Legacy charts library',
- }
- ),
- requiresPageReload: true,
- value: false,
- description: i18n.translate(
- 'visualizations.advancedSettings.visualization.legacyChartsLibrary.description',
- {
- defaultMessage:
- 'Enables legacy charts library for area, line, bar, pie charts in visualize.',
- }
- ),
- category: ['visualization'],
- schema: schema.boolean(),
- },
});
if (plugins.usageCollection) {
diff --git a/test/functional/apps/dashboard/dashboard_state.ts b/test/functional/apps/dashboard/dashboard_state.ts
index 047681e1a8ace..6c259f5a71efa 100644
--- a/test/functional/apps/dashboard/dashboard_state.ts
+++ b/test/functional/apps/dashboard/dashboard_state.ts
@@ -53,6 +53,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
if (isNewChartsLibraryEnabled) {
await kibanaServer.uiSettings.update({
'visualization:visualize:legacyChartsLibrary': false,
+ 'visualization:visualize:legacyPieChartsLibrary': false,
});
await browser.refresh();
}
diff --git a/test/functional/apps/dashboard/index.ts b/test/functional/apps/dashboard/index.ts
index 4b83b2ac92deb..e4dc04282e4ac 100644
--- a/test/functional/apps/dashboard/index.ts
+++ b/test/functional/apps/dashboard/index.ts
@@ -123,6 +123,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
await loadLogstash();
await kibanaServer.uiSettings.update({
'visualization:visualize:legacyChartsLibrary': false,
+ 'visualization:visualize:legacyPieChartsLibrary': false,
});
await browser.refresh();
});
@@ -131,6 +132,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
await unloadLogstash();
await kibanaServer.uiSettings.update({
'visualization:visualize:legacyChartsLibrary': true,
+ 'visualization:visualize:legacyPieChartsLibrary': true,
});
await browser.refresh();
});
diff --git a/test/functional/apps/getting_started/_shakespeare.ts b/test/functional/apps/getting_started/_shakespeare.ts
index 945c1fdcbdcf4..ae6841b85c98d 100644
--- a/test/functional/apps/getting_started/_shakespeare.ts
+++ b/test/functional/apps/getting_started/_shakespeare.ts
@@ -57,6 +57,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
if (isNewChartsLibraryEnabled) {
await kibanaServer.uiSettings.update({
'visualization:visualize:legacyChartsLibrary': false,
+ 'visualization:visualize:legacyPieChartsLibrary': false,
});
await browser.refresh();
}
diff --git a/test/functional/apps/getting_started/index.ts b/test/functional/apps/getting_started/index.ts
index b75a30037d065..4c1c052ef15a2 100644
--- a/test/functional/apps/getting_started/index.ts
+++ b/test/functional/apps/getting_started/index.ts
@@ -24,6 +24,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
before(async () => {
await kibanaServer.uiSettings.update({
'visualization:visualize:legacyChartsLibrary': false,
+ 'visualization:visualize:legacyPieChartsLibrary': false,
});
await browser.refresh();
});
@@ -31,6 +32,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
after(async () => {
await kibanaServer.uiSettings.update({
'visualization:visualize:legacyChartsLibrary': true,
+ 'visualization:visualize:legacyPieChartsLibrary': true,
});
await browser.refresh();
});
diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts
index cecd206abd1db..bc6160eba3846 100644
--- a/test/functional/apps/visualize/index.ts
+++ b/test/functional/apps/visualize/index.ts
@@ -31,6 +31,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
before(async () => {
await kibanaServer.uiSettings.update({
'visualization:visualize:legacyChartsLibrary': false,
+ 'visualization:visualize:legacyPieChartsLibrary': false,
});
await browser.refresh();
});
@@ -38,6 +39,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
after(async () => {
await kibanaServer.uiSettings.update({
'visualization:visualize:legacyChartsLibrary': true,
+ 'visualization:visualize:legacyPieChartsLibrary': true,
});
await browser.refresh();
});
diff --git a/test/functional/config.js b/test/functional/config.js
index b28a9fd36c1c0..670488003e56c 100644
--- a/test/functional/config.js
+++ b/test/functional/config.js
@@ -58,6 +58,7 @@ export default async function ({ readConfigFile }) {
'accessibility:disableAnimations': true,
'dateFormat:tz': 'UTC',
'visualization:visualize:legacyChartsLibrary': true,
+ 'visualization:visualize:legacyPieChartsLibrary': true,
},
},
diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts
index c8587f4ffd346..64b8c363fa6c2 100644
--- a/test/functional/page_objects/visualize_chart_page.ts
+++ b/test/functional/page_objects/visualize_chart_page.ts
@@ -37,7 +37,8 @@ export class VisualizeChartPageObject extends FtrService {
public async isNewChartsLibraryEnabled(): Promise {
const legacyChartsLibrary =
Boolean(
- await this.kibanaServer.uiSettings.get('visualization:visualize:legacyChartsLibrary')
+ (await this.kibanaServer.uiSettings.get('visualization:visualize:legacyChartsLibrary')) &&
+ (await this.kibanaServer.uiSettings.get('visualization:visualize:legacyPieChartsLibrary'))
) ?? true;
const enabled = !legacyChartsLibrary;
this.log.debug(`-- isNewChartsLibraryEnabled = ${enabled}`);
diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts
index a11a254509e7a..e930406cdcce8 100644
--- a/test/functional/page_objects/visualize_page.ts
+++ b/test/functional/page_objects/visualize_page.ts
@@ -57,6 +57,7 @@ export class VisualizePageObject extends FtrService {
defaultIndex: 'logstash-*',
[UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: '0,0.[000]b',
'visualization:visualize:legacyChartsLibrary': !isNewLibrary,
+ 'visualization:visualize:legacyPieChartsLibrary': !isNewLibrary,
});
}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index e73bb19627b8f..87813b64f1f20 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -4941,8 +4941,6 @@
"visTypePie.editors.pie.showLabelsLabel": "ラベルを表示",
"visTypePie.editors.pie.showTopLevelOnlyLabel": "トップレベルのみ表示",
"visTypePie.editors.pie.showValuesLabel": "値を表示",
- "visualizations.advancedSettings.visualization.legacyChartsLibrary.description": "Visualizeでエリア、折れ線、棒グラフのレガシーグラフライブラリを有効にします。",
- "visualizations.advancedSettings.visualization.legacyChartsLibrary.name": "レガシーグラフライブラリ",
"visualizations.advancedSettings.visualizeEnableLabsText": "ユーザーが実験的なビジュアライゼーションを作成、表示、編集できるようになります。無効の場合、\n ユーザーは本番準備が整ったビジュアライゼーションのみを利用できます。",
"visualizations.advancedSettings.visualizeEnableLabsTitle": "実験的なビジュアライゼーションを有効にする",
"visualizations.disabledLabVisualizationLink": "ドキュメンテーションを表示",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 51f6378f481e2..4e98de541ce60 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -4968,8 +4968,6 @@
"visTypePie.editors.pie.showLabelsLabel": "显示标签",
"visTypePie.editors.pie.showTopLevelOnlyLabel": "仅显示顶级",
"visTypePie.editors.pie.showValuesLabel": "显示值",
- "visualizations.advancedSettings.visualization.legacyChartsLibrary.description": "在 Visualize 中启用面积图、折线图和条形图的旧版图表库。",
- "visualizations.advancedSettings.visualization.legacyChartsLibrary.name": "旧版图表库",
"visualizations.advancedSettings.visualizeEnableLabsText": "允许用户创建、查看和编辑实验性可视化。如果禁用,\n 仅被视为生产就绪的可视化可供用户使用。",
"visualizations.advancedSettings.visualizeEnableLabsTitle": "启用实验性可视化",
"visualizations.disabledLabVisualizationLink": "阅读文档",
diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js
index cf05bd6e15898..2c3a3c93e2a0a 100644
--- a/x-pack/test/functional/config.js
+++ b/x-pack/test/functional/config.js
@@ -103,6 +103,7 @@ export default async function ({ readConfigFile }) {
'accessibility:disableAnimations': true,
'dateFormat:tz': 'UTC',
'visualization:visualize:legacyChartsLibrary': true,
+ 'visualization:visualize:legacyPieChartsLibrary': true,
},
},
// the apps section defines the urls that
From 59d422394aad7124cf07efbd651fed944e73f0e5 Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Thu, 24 Jun 2021 11:43:33 +0200
Subject: [PATCH 19/86] [Lens] Move empty string handling into field formatter
(#102877)
---
.../common/field_formats/converters/string.ts | 7 ++
.../vis_type_pie/public/utils/get_layers.ts | 6 -
.../public/components/detailed_tooltip.tsx | 4 +-
.../public/components/xy_settings.tsx | 3 +-
.../vis_type_xy/public/config/get_axis.ts | 4 +-
.../public/utils/get_series_name_fn.ts | 13 +-
.../components/table_actions.ts | 6 +-
.../heatmap_visualization/chart_component.tsx | 5 +-
.../rename_columns.test.ts | 23 ----
.../indexpattern_datasource/rename_columns.ts | 14 +--
.../pie_visualization/render_function.tsx | 3 +-
.../legend_action_popover.tsx | 5 +-
x-pack/plugins/lens/public/utils.test.ts | 118 ------------------
x-pack/plugins/lens/public/utils.ts | 36 ------
.../public/xy_visualization/expression.tsx | 3 +-
.../translations/translations/ja-JP.json | 2 -
.../translations/translations/zh-CN.json | 2 -
.../discover/__snapshots__/reporting.snap | 4 +-
18 files changed, 23 insertions(+), 235 deletions(-)
delete mode 100644 x-pack/plugins/lens/public/utils.test.ts
diff --git a/src/plugins/data/common/field_formats/converters/string.ts b/src/plugins/data/common/field_formats/converters/string.ts
index ec92d75910522..64367df5d90dd 100644
--- a/src/plugins/data/common/field_formats/converters/string.ts
+++ b/src/plugins/data/common/field_formats/converters/string.ts
@@ -13,6 +13,10 @@ import { FieldFormat } from '../field_format';
import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types';
import { shortenDottedString } from '../../utils';
+export const emptyLabel = i18n.translate('data.fieldFormats.string.emptyLabel', {
+ defaultMessage: '(empty)',
+});
+
const TRANSFORM_OPTIONS = [
{
kind: false,
@@ -103,6 +107,9 @@ export class StringFormat extends FieldFormat {
}
textConvert: TextContextTypeConvert = (val) => {
+ if (val === '') {
+ return emptyLabel;
+ }
switch (this.param('transform')) {
case 'lower':
return String(val).toLowerCase();
diff --git a/src/plugins/vis_type_pie/public/utils/get_layers.ts b/src/plugins/vis_type_pie/public/utils/get_layers.ts
index 27dcf2d379811..5a82871bf3688 100644
--- a/src/plugins/vis_type_pie/public/utils/get_layers.ts
+++ b/src/plugins/vis_type_pie/public/utils/get_layers.ts
@@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
-import { i18n } from '@kbn/i18n';
import {
Datum,
PartitionFillLabel,
@@ -125,11 +124,6 @@ export const getLayers = (
},
showAccessor: (d: Datum) => d !== EMPTY_SLICE,
nodeLabel: (d: unknown) => {
- if (d === '') {
- return i18n.translate('visTypePie.emptyLabelValue', {
- defaultMessage: '(empty)',
- });
- }
if (col.format) {
const formattedLabel = formatter.deserialize(col.format).convert(d) ?? '';
if (visParams.labels.truncate && formattedLabel.length <= visParams.labels.truncate) {
diff --git a/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx b/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx
index c9ed82fcf58e5..fb6b4bb41d9ba 100644
--- a/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx
+++ b/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx
@@ -19,7 +19,6 @@ import {
import { Aspects } from '../types';
import './_detailed_tooltip.scss';
-import { fillEmptyValue } from '../utils/get_series_name_fn';
import { COMPLEX_SPLIT_ACCESSOR, isRangeAggType } from '../utils/accessors';
interface TooltipData {
@@ -100,8 +99,7 @@ export const getTooltipData = (
return data;
};
-const renderData = ({ label, value: rawValue }: TooltipData, index: number) => {
- const value = fillEmptyValue(rawValue);
+const renderData = ({ label, value }: TooltipData, index: number) => {
return label && value ? (
diff --git a/src/plugins/vis_type_xy/public/components/xy_settings.tsx b/src/plugins/vis_type_xy/public/components/xy_settings.tsx
index 8922f512522a0..8d6a7eecdfe52 100644
--- a/src/plugins/vis_type_xy/public/components/xy_settings.tsx
+++ b/src/plugins/vis_type_xy/public/components/xy_settings.tsx
@@ -29,7 +29,6 @@ import { renderEndzoneTooltip } from '../../../charts/public';
import { getThemeService, getUISettings } from '../services';
import { VisConfig } from '../types';
-import { fillEmptyValue } from '../utils/get_series_name_fn';
declare global {
interface Window {
@@ -134,7 +133,7 @@ export const XYSettings: FC = ({
};
const headerValueFormatter: TickFormatter | undefined = xAxis.ticks?.formatter
- ? (value) => fillEmptyValue(xAxis.ticks?.formatter?.(value)) ?? ''
+ ? (value) => xAxis.ticks?.formatter?.(value) ?? ''
: undefined;
const headerFormatter =
isTimeChart && xDomain && adjustedXDomain
diff --git a/src/plugins/vis_type_xy/public/config/get_axis.ts b/src/plugins/vis_type_xy/public/config/get_axis.ts
index 08b17c882eea6..71d33cc20d057 100644
--- a/src/plugins/vis_type_xy/public/config/get_axis.ts
+++ b/src/plugins/vis_type_xy/public/config/get_axis.ts
@@ -27,7 +27,6 @@ import {
YScaleType,
SeriesParam,
} from '../types';
-import { fillEmptyValue } from '../utils/get_series_name_fn';
export function getAxis(
{ type, title: axisTitle, labels, scale: axisScale, ...axis }: CategoryAxis,
@@ -90,8 +89,7 @@ function getLabelFormatter(
}
return (value: any) => {
- const formattedStringValue = `${formatter ? formatter(value) : value}`;
- const finalValue = fillEmptyValue(formattedStringValue);
+ const finalValue = `${formatter ? formatter(value) : value}`;
if (finalValue.length > truncate) {
return `${finalValue.slice(0, truncate)}...`;
diff --git a/src/plugins/vis_type_xy/public/utils/get_series_name_fn.ts b/src/plugins/vis_type_xy/public/utils/get_series_name_fn.ts
index 0e54650e22f75..137f8a5558010 100644
--- a/src/plugins/vis_type_xy/public/utils/get_series_name_fn.ts
+++ b/src/plugins/vis_type_xy/public/utils/get_series_name_fn.ts
@@ -8,21 +8,10 @@
import { memoize } from 'lodash';
-import { i18n } from '@kbn/i18n';
import { XYChartSeriesIdentifier, SeriesName } from '@elastic/charts';
import { VisConfig } from '../types';
-const emptyTextLabel = i18n.translate('visTypeXy.emptyTextColumnValue', {
- defaultMessage: '(empty)',
-});
-
-/**
- * Returns empty values
- */
-export const fillEmptyValue = (value: T) =>
- value === '' ? emptyTextLabel : value;
-
function getSplitValues(
splitAccessors: XYChartSeriesIdentifier['splitAccessors'],
seriesAspects?: VisConfig['aspects']['series']
@@ -36,7 +25,7 @@ function getSplitValues(
const split = (seriesAspects ?? []).find(({ accessor }) => accessor === key);
splitValues.push(split?.formatter ? split?.formatter(value) : value);
});
- return splitValues.map(fillEmptyValue);
+ return splitValues;
}
export const getSeriesNameFn = (aspects: VisConfig['aspects'], multipleY = false) =>
diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts
index 0d44ae3aa6dec..8615ed6536316 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts
+++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts
@@ -15,8 +15,6 @@ import type {
LensToggleAction,
} from './types';
import { ColumnConfig } from './table_basic';
-
-import { desanitizeFilterContext } from '../../utils';
import { getOriginalId } from '../transpose_helpers';
export const createGridResizeHandler = (
@@ -92,7 +90,7 @@ export const createGridFilterHandler = (
timeFieldName,
};
- onClickValue(desanitizeFilterContext(data));
+ onClickValue(data);
};
export const createTransposeColumnFilterHandler = (
@@ -125,7 +123,7 @@ export const createTransposeColumnFilterHandler = (
timeFieldName,
};
- onClickValue(desanitizeFilterContext(data));
+ onClickValue(data);
};
export const createGridSortingConfig = (
diff --git a/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx b/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx
index 3048f3b3db580..8214d5ba129d4 100644
--- a/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx
+++ b/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx
@@ -21,7 +21,6 @@ import { VisualizationContainer } from '../visualization_container';
import { HeatmapRenderProps } from './types';
import './index.scss';
import { LensBrushEvent, LensFilterEvent } from '../types';
-import { desanitizeFilterContext } from '../utils';
import { EmptyPlaceholder } from '../shared_components';
import { LensIconChartHeatmap } from '../assets/chart_heatmap';
@@ -117,7 +116,7 @@ export const HeatmapComponent: FC = ({
})),
timeFieldName,
};
- onClickValue(desanitizeFilterContext(context));
+ onClickValue(context);
}) as ElementClickListener;
const onBrushEnd = (e: HeatmapBrushEvent) => {
@@ -164,7 +163,7 @@ export const HeatmapComponent: FC = ({
})),
timeFieldName,
};
- onClickValue(desanitizeFilterContext(context));
+ onClickValue(context);
}
};
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts
index 0750b99db5f67..5654a599c5e27 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts
@@ -83,29 +83,6 @@ describe('rename_columns', () => {
`);
});
- it('should replace "" with a visible value', () => {
- const input: Datatable = {
- type: 'datatable',
- columns: [{ id: 'a', name: 'A', meta: { type: 'string' } }],
- rows: [{ a: '' }],
- };
-
- const idMap = {
- a: {
- id: 'a',
- label: 'Austrailia',
- },
- };
-
- const result = renameColumns.fn(
- input,
- { idMap: JSON.stringify(idMap) },
- createMockExecutionContext()
- );
-
- expect(result.rows[0].a).toEqual('(empty)');
- });
-
it('should keep columns which are not mapped', () => {
const input: Datatable = {
type: 'datatable',
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.ts b/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.ts
index 89c63880248d0..a16756126c030 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.ts
@@ -49,9 +49,9 @@ export const renameColumns: ExpressionFunctionDefinition<
Object.entries(row).forEach(([id, value]) => {
if (id in idMap) {
- mappedRow[idMap[id].id] = sanitizeValue(value);
+ mappedRow[idMap[id].id] = value;
} else {
- mappedRow[id] = sanitizeValue(value);
+ mappedRow[id] = value;
}
});
@@ -86,13 +86,3 @@ function getColumnName(originalColumn: OriginalColumn, newColumn: DatatableColum
return originalColumn.label;
}
-
-function sanitizeValue(value: unknown) {
- if (value === '') {
- return i18n.translate('xpack.lens.indexpattern.emptyTextColumnValue', {
- defaultMessage: '(empty)',
- });
- }
-
- return value;
-}
diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx
index f329cfe1bb8b9..2e5a06b4f705f 100644
--- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx
+++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx
@@ -31,7 +31,6 @@ import { PieExpressionProps } from './types';
import { getSliceValue, getFilterContext } from './render_helpers';
import { EmptyPlaceholder } from '../shared_components';
import './visualization.scss';
-import { desanitizeFilterContext } from '../utils';
import {
ChartsPluginSetup,
PaletteRegistry,
@@ -254,7 +253,7 @@ export function PieComponent(
const onElementClickHandler: ElementClickListener = (args) => {
const context = getFilterContext(args[0][0] as LayerValue[], groups, firstTable);
- onClickValue(desanitizeFilterContext(context));
+ onClickValue(context);
};
return (
diff --git a/x-pack/plugins/lens/public/shared_components/legend_action_popover.tsx b/x-pack/plugins/lens/public/shared_components/legend_action_popover.tsx
index e344cb5289f51..5027629ef6ae5 100644
--- a/x-pack/plugins/lens/public/shared_components/legend_action_popover.tsx
+++ b/x-pack/plugins/lens/public/shared_components/legend_action_popover.tsx
@@ -9,7 +9,6 @@ import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiContextMenuPanelDescriptor, EuiIcon, EuiPopover, EuiContextMenu } from '@elastic/eui';
import type { LensFilterEvent } from '../types';
-import { desanitizeFilterContext } from '../utils';
export interface LegendActionPopoverProps {
/**
@@ -45,7 +44,7 @@ export const LegendActionPopover: React.FunctionComponent ,
onClick: () => {
setPopoverOpen(false);
- onFilter(desanitizeFilterContext(context));
+ onFilter(context);
},
},
{
@@ -56,7 +55,7 @@ export const LegendActionPopover: React.FunctionComponent ,
onClick: () => {
setPopoverOpen(false);
- onFilter(desanitizeFilterContext({ ...context, negate: true }));
+ onFilter({ ...context, negate: true });
},
},
],
diff --git a/x-pack/plugins/lens/public/utils.test.ts b/x-pack/plugins/lens/public/utils.test.ts
deleted file mode 100644
index 76597870b3beb..0000000000000
--- a/x-pack/plugins/lens/public/utils.test.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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 { LensFilterEvent } from './types';
-import { desanitizeFilterContext } from './utils';
-import { Datatable } from '../../../../src/plugins/expressions/common';
-
-describe('desanitizeFilterContext', () => {
- it(`When filtered value equals '(empty)' replaces it with '' in table and in value.`, () => {
- const table: Datatable = {
- type: 'datatable',
- rows: [
- {
- 'f903668f-1175-4705-a5bd-713259d10326': 1589414640000,
- '5d5446b2-72e8-4f86-91e0-88380f0fa14c': '(empty)',
- 'col-1-9f0b6f88-c399-43a0-a993-0ad943c9af25': 1,
- },
- {
- 'f903668f-1175-4705-a5bd-713259d10326': 1589414670000,
- 'col-1-9f0b6f88-c399-43a0-a993-0ad943c9af25': 0,
- },
- {
- 'f903668f-1175-4705-a5bd-713259d10326': 1589414880000,
- '5d5446b2-72e8-4f86-91e0-88380f0fa14c': '123123123',
- 'col-1-9f0b6f88-c399-43a0-a993-0ad943c9af25': 1,
- },
- {
- 'f903668f-1175-4705-a5bd-713259d10326': 1589414910000,
- '5d5446b2-72e8-4f86-91e0-88380f0fa14c': '(empty)',
- 'col-1-9f0b6f88-c399-43a0-a993-0ad943c9af25': 1,
- },
- ],
- columns: [
- {
- id: 'f903668f-1175-4705-a5bd-713259d10326',
- name: 'order_date per 30 seconds',
- meta: { type: 'date' },
- },
- {
- id: '5d5446b2-72e8-4f86-91e0-88380f0fa14c',
- name: 'Top values of customer_phone',
- meta: { type: 'string' },
- },
- {
- id: '9f0b6f88-c399-43a0-a993-0ad943c9af25',
- name: 'Count of records',
- meta: { type: 'number' },
- },
- ],
- };
-
- const contextWithEmptyValue: LensFilterEvent['data'] = {
- data: [
- {
- row: 3,
- column: 0,
- value: 1589414910000,
- table,
- },
- {
- row: 0,
- column: 1,
- value: '(empty)',
- table,
- },
- ],
- timeFieldName: 'order_date',
- };
-
- const desanitizedFilterContext = desanitizeFilterContext(contextWithEmptyValue);
-
- expect(desanitizedFilterContext).toEqual({
- data: [
- {
- row: 3,
- column: 0,
- value: 1589414910000,
- table,
- },
- {
- value: '',
- row: 0,
- column: 1,
- table: {
- rows: [
- {
- 'f903668f-1175-4705-a5bd-713259d10326': 1589414640000,
- '5d5446b2-72e8-4f86-91e0-88380f0fa14c': '',
- 'col-1-9f0b6f88-c399-43a0-a993-0ad943c9af25': 1,
- },
- {
- 'f903668f-1175-4705-a5bd-713259d10326': 1589414670000,
- 'col-1-9f0b6f88-c399-43a0-a993-0ad943c9af25': 0,
- },
- {
- 'f903668f-1175-4705-a5bd-713259d10326': 1589414880000,
- '5d5446b2-72e8-4f86-91e0-88380f0fa14c': '123123123',
- 'col-1-9f0b6f88-c399-43a0-a993-0ad943c9af25': 1,
- },
- {
- 'f903668f-1175-4705-a5bd-713259d10326': 1589414910000,
- '5d5446b2-72e8-4f86-91e0-88380f0fa14c': '(empty)',
- 'col-1-9f0b6f88-c399-43a0-a993-0ad943c9af25': 1,
- },
- ],
- columns: table.columns,
- type: 'datatable',
- },
- },
- ],
- timeFieldName: 'order_date',
- });
- });
-});
diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts
index 2706fe977c68e..1c4b2c67f96fc 100644
--- a/x-pack/plugins/lens/public/utils.ts
+++ b/x-pack/plugins/lens/public/utils.ts
@@ -9,42 +9,6 @@ import { i18n } from '@kbn/i18n';
import { IndexPattern, IndexPatternsContract, TimefilterContract } from 'src/plugins/data/public';
import { IUiSettingsClient } from 'kibana/public';
import moment from 'moment-timezone';
-import { LensFilterEvent } from './types';
-
-/** replaces the value `(empty) to empty string for proper filtering` */
-export const desanitizeFilterContext = (
- context: LensFilterEvent['data']
-): LensFilterEvent['data'] => {
- const emptyTextValue = i18n.translate('xpack.lens.indexpattern.emptyTextColumnValue', {
- defaultMessage: '(empty)',
- });
- const result: LensFilterEvent['data'] = {
- ...context,
- data: context.data.map((point) =>
- point.value === emptyTextValue
- ? {
- ...point,
- value: '',
- table: {
- ...point.table,
- rows: point.table.rows.map((row, index) =>
- index === point.row
- ? {
- ...row,
- [point.table.columns[point.column].id]: '',
- }
- : row
- ),
- },
- }
- : point
- ),
- };
- if (context.timeFieldName) {
- result.timeFieldName = context.timeFieldName;
- }
- return result;
-};
export function getVisualizeGeoFieldMessage(fieldType: string) {
return i18n.translate('xpack.lens.visualizeGeoFieldMessage', {
diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx
index 1de5cf6b30533..3fe98282a18b0 100644
--- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx
@@ -53,7 +53,6 @@ import {
SeriesLayer,
} from '../../../../../src/plugins/charts/public';
import { EmptyPlaceholder } from '../shared_components';
-import { desanitizeFilterContext } from '../utils';
import { fittingFunctionDefinitions, getFitOptions } from './fitting_functions';
import { getAxesConfiguration, GroupsConfiguration, validateExtent } from './axes_configuration';
import { getColorAssignments } from './color_assignment';
@@ -575,7 +574,7 @@ export function XYChart({
})),
timeFieldName: xDomain && isDateField ? xAxisFieldName : undefined,
};
- onClickValue(desanitizeFilterContext(context));
+ onClickValue(context);
};
const brushHandler: BrushEndListener = ({ x }) => {
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 87813b64f1f20..837716ec9dd5a 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -5055,7 +5055,6 @@
"visTypeXy.editors.pointSeries.thresholdLine.valueLabel": "しきい値",
"visTypeXy.editors.pointSeries.thresholdLine.widthLabel": "線の幅",
"visTypeXy.editors.pointSeries.thresholdLineSettingsTitle": "しきい線",
- "visTypeXy.emptyTextColumnValue": " (空) ",
"visTypeXy.fittingFunctionsTitle.carry": "最後 (ギャップを最後の値で埋める) ",
"visTypeXy.fittingFunctionsTitle.linear": "線形 (ギャップを線で埋める) ",
"visTypeXy.fittingFunctionsTitle.lookahead": "次 (ギャップを次の値で埋める) ",
@@ -12574,7 +12573,6 @@
"xpack.lens.indexPattern.emptyDimensionButton": "空のディメンション",
"xpack.lens.indexPattern.emptyFieldsLabel": "空のフィールド",
"xpack.lens.indexPattern.emptyFieldsLabelHelp": "空のフィールドには、フィルターに基づく最初の 500 件のドキュメントの値が含まれていませんでした。",
- "xpack.lens.indexpattern.emptyTextColumnValue": " (空) ",
"xpack.lens.indexPattern.existenceErrorAriaLabel": "存在の取り込みに失敗しました",
"xpack.lens.indexPattern.existenceErrorLabel": "フィールド情報を読み込めません",
"xpack.lens.indexPattern.existenceTimeoutAriaLabel": "存在の取り込みがタイムアウトしました",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 4e98de541ce60..0192566db0731 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -5083,7 +5083,6 @@
"visTypeXy.editors.pointSeries.thresholdLine.valueLabel": "阈值",
"visTypeXy.editors.pointSeries.thresholdLine.widthLabel": "线条宽度",
"visTypeXy.editors.pointSeries.thresholdLineSettingsTitle": "阈值线条",
- "visTypeXy.emptyTextColumnValue": " (空) ",
"visTypeXy.fittingFunctionsTitle.carry": "最后一个 (使用最后一个值填充缺口) ",
"visTypeXy.fittingFunctionsTitle.linear": "线 (使用线填充缺口) ",
"visTypeXy.fittingFunctionsTitle.lookahead": "下一个 (使用下一个值填充缺口) ",
@@ -12744,7 +12743,6 @@
"xpack.lens.indexPattern.emptyDimensionButton": "空维度",
"xpack.lens.indexPattern.emptyFieldsLabel": "空字段",
"xpack.lens.indexPattern.emptyFieldsLabelHelp": "空字段在基于您的筛选的前 500 个文档中不包含任何值。",
- "xpack.lens.indexpattern.emptyTextColumnValue": " (空) ",
"xpack.lens.indexPattern.existenceErrorAriaLabel": "现有内容提取失败",
"xpack.lens.indexPattern.existenceErrorLabel": "无法加载字段信息",
"xpack.lens.indexPattern.existenceTimeoutAriaLabel": "现有内容提取超时",
diff --git a/x-pack/test/functional/apps/discover/__snapshots__/reporting.snap b/x-pack/test/functional/apps/discover/__snapshots__/reporting.snap
index baa49cb6f9d81..c7666bf00dd53 100644
--- a/x-pack/test/functional/apps/discover/__snapshots__/reporting.snap
+++ b/x-pack/test/functional/apps/discover/__snapshots__/reporting.snap
@@ -65,7 +65,7 @@ exports[`discover Discover CSV Export Generate CSV: archived search generates a
exports[`discover Discover CSV Export Generate CSV: new search generates a report from a new search with data: default 1`] = `
"\\"_id\\",\\"_index\\",\\"_score\\",\\"_type\\",category,\\"category.keyword\\",currency,\\"customer_first_name\\",\\"customer_first_name.keyword\\",\\"customer_full_name\\",\\"customer_full_name.keyword\\",\\"customer_gender\\",\\"customer_id\\",\\"customer_last_name\\",\\"customer_last_name.keyword\\",\\"customer_phone\\",\\"day_of_week\\",\\"day_of_week_i\\",email,\\"geoip.city_name\\",\\"geoip.continent_name\\",\\"geoip.country_iso_code\\",\\"geoip.location\\",\\"geoip.region_name\\",manufacturer,\\"manufacturer.keyword\\",\\"order_date\\",\\"order_id\\",\\"products._id\\",\\"products._id.keyword\\",\\"products.base_price\\",\\"products.base_unit_price\\",\\"products.category\\",\\"products.category.keyword\\",\\"products.created_on\\",\\"products.discount_amount\\",\\"products.discount_percentage\\",\\"products.manufacturer\\",\\"products.manufacturer.keyword\\",\\"products.min_price\\",\\"products.price\\",\\"products.product_id\\",\\"products.product_name\\",\\"products.product_name.keyword\\",\\"products.quantity\\",\\"products.sku\\",\\"products.tax_amount\\",\\"products.taxful_price\\",\\"products.taxless_price\\",\\"products.unit_discount_amount\\",sku,\\"taxful_total_price\\",\\"taxless_total_price\\",\\"total_quantity\\",\\"total_unique_products\\",type,user
-3AMtOW0BH63Xcmy432DJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,\\"Sultan Al\\",\\"Sultan Al\\",\\"Sultan Al Boone\\",\\"Sultan Al Boone\\",MALE,19,Boone,Boone,,Saturday,5,\\"sultan al@boone-family.zzz\\",\\"Abu Dhabi\\",Asia,AE,\\"{
+3AMtOW0BH63Xcmy432DJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,\\"Sultan Al\\",\\"Sultan Al\\",\\"Sultan Al Boone\\",\\"Sultan Al Boone\\",MALE,19,Boone,Boone,\\"(empty)\\",Saturday,5,\\"sultan al@boone-family.zzz\\",\\"Abu Dhabi\\",Asia,AE,\\"{
\\"\\"coordinates\\"\\": [
54.4,
24.5
@@ -77,7 +77,7 @@ exports[`discover Discover CSV Export Generate CSV: new search generates a repor
exports[`discover Discover CSV Export Generate CSV: new search generates a report from a new search with data: discover:searchFieldsFromSource 1`] = `
"\\"_id\\",\\"_index\\",\\"_score\\",\\"_type\\",category,\\"category.keyword\\",currency,\\"customer_first_name\\",\\"customer_first_name.keyword\\",\\"customer_full_name\\",\\"customer_full_name.keyword\\",\\"customer_gender\\",\\"customer_id\\",\\"customer_last_name\\",\\"customer_last_name.keyword\\",\\"customer_phone\\",\\"day_of_week\\",\\"day_of_week_i\\",email,\\"geoip.city_name\\",\\"geoip.continent_name\\",\\"geoip.country_iso_code\\",\\"geoip.location\\",\\"geoip.region_name\\",manufacturer,\\"manufacturer.keyword\\",\\"order_date\\",\\"order_id\\",\\"products._id\\",\\"products._id.keyword\\",\\"products.base_price\\",\\"products.base_unit_price\\",\\"products.category\\",\\"products.category.keyword\\",\\"products.created_on\\",\\"products.discount_amount\\",\\"products.discount_percentage\\",\\"products.manufacturer\\",\\"products.manufacturer.keyword\\",\\"products.min_price\\",\\"products.price\\",\\"products.product_id\\",\\"products.product_name\\",\\"products.product_name.keyword\\",\\"products.quantity\\",\\"products.sku\\",\\"products.tax_amount\\",\\"products.taxful_price\\",\\"products.taxless_price\\",\\"products.unit_discount_amount\\",sku,\\"taxful_total_price\\",\\"taxless_total_price\\",\\"total_quantity\\",\\"total_unique_products\\",type,user
-3AMtOW0BH63Xcmy432DJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,\\"Sultan Al\\",\\"Sultan Al\\",\\"Sultan Al Boone\\",\\"Sultan Al Boone\\",MALE,19,Boone,Boone,,Saturday,5,\\"sultan al@boone-family.zzz\\",\\"Abu Dhabi\\",Asia,AE,\\"{
+3AMtOW0BH63Xcmy432DJ,ecommerce,\\"-\\",\\"-\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",EUR,\\"Sultan Al\\",\\"Sultan Al\\",\\"Sultan Al Boone\\",\\"Sultan Al Boone\\",MALE,19,Boone,Boone,\\"(empty)\\",Saturday,5,\\"sultan al@boone-family.zzz\\",\\"Abu Dhabi\\",Asia,AE,\\"{
\\"\\"coordinates\\"\\": [
54.4,
24.5
From 4c2449fd2860ad7757fd210788563f2bede86354 Mon Sep 17 00:00:00 2001
From: Ignacio Rivas
Date: Thu, 24 Jun 2021 11:55:28 +0200
Subject: [PATCH 20/86] [Ingest pipelines] add extract_device_type to user
agent processor (#100986)
* testing layouts
* fix copy for beta badge
* replace hardcoded text with i18n strings
* avoid updating types and just replace label
* Small cr changes
* get rid of style prop and just use a smaller badge
---
.../__jest__/processors/processor.helpers.tsx | 3 +
.../__jest__/processors/user_agent.test.tsx | 125 ++++++++++++++++++
.../common_fields/properties_field.tsx | 13 +-
.../processor_form/processors/user_agent.tsx | 62 ++++++++-
4 files changed, 188 insertions(+), 15 deletions(-)
create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx
index 15e8c323b1308..5f6ace2069410 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx
@@ -156,6 +156,9 @@ type TestSubject =
| 'separatorValueField.input'
| 'quoteValueField.input'
| 'emptyValueField.input'
+ | 'extractDeviceTypeSwitch.input'
+ | 'propertiesValueField'
+ | 'regexFileField.input'
| 'valueFieldInput'
| 'mediaTypeSelectorField'
| 'ignoreEmptyField.input'
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx
new file mode 100644
index 0000000000000..fa1c24c9dfb39
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx
@@ -0,0 +1,125 @@
+/*
+ * 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 { act } from 'react-dom/test-utils';
+import { setup, SetupResult, getProcessorValue } from './processor.helpers';
+
+// Default parameter values automatically added to the user agent processor when saved
+const defaultUserAgentParameters = {
+ if: undefined,
+ regex_file: undefined,
+ properties: undefined,
+ description: undefined,
+ ignore_missing: undefined,
+ ignore_failure: undefined,
+ extract_device_type: undefined,
+};
+
+const USER_AGENT_TYPE = 'user_agent';
+
+describe('Processor: User Agent', () => {
+ let onUpdate: jest.Mock;
+ let testBed: SetupResult;
+
+ beforeAll(() => {
+ jest.useFakeTimers();
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
+
+ beforeEach(async () => {
+ onUpdate = jest.fn();
+
+ await act(async () => {
+ testBed = await setup({
+ value: {
+ processors: [],
+ },
+ onFlyoutOpen: jest.fn(),
+ onUpdate,
+ });
+ });
+
+ testBed.component.update();
+
+ // Open flyout to add new processor
+ testBed.actions.addProcessor();
+ // Add type (the other fields are not visible until a type is selected)
+ await testBed.actions.addProcessorType(USER_AGENT_TYPE);
+ });
+
+ test('prevents form submission if required fields are not provided', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ } = testBed;
+
+ // Click submit button with only the processor type defined
+ await saveNewProcessor();
+
+ // Expect form error as "field" is required parameter
+ expect(form.getErrorsMessages()).toEqual(['A field value is required.']);
+ });
+
+ test('saves with just the default parameter value', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ } = testBed;
+
+ // Add "field" value (required)
+ form.setInputValue('fieldNameField.input', 'field_1');
+ // Save the field
+ await saveNewProcessor();
+
+ const processors = getProcessorValue(onUpdate, USER_AGENT_TYPE);
+ expect(processors[0][USER_AGENT_TYPE]).toEqual({
+ ...defaultUserAgentParameters,
+ field: 'field_1',
+ });
+ });
+
+ test('allows optional parameters to be set', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ find,
+ component,
+ } = testBed;
+
+ // Add "field" value (required)
+ form.setInputValue('fieldNameField.input', 'field_1');
+
+ // Set optional parameteres
+ form.setInputValue('targetField.input', 'target_field');
+ form.setInputValue('regexFileField.input', 'hello*');
+ form.toggleEuiSwitch('ignoreMissingSwitch.input');
+ form.toggleEuiSwitch('ignoreFailureSwitch.input');
+ form.toggleEuiSwitch('extractDeviceTypeSwitch.input');
+ await act(async () => {
+ find('propertiesValueField').simulate('change', [{ label: 'os' }]);
+ });
+ component.update();
+
+ // Save the field with new changes
+ await saveNewProcessor();
+
+ const processors = getProcessorValue(onUpdate, USER_AGENT_TYPE);
+ expect(processors[0][USER_AGENT_TYPE]).toEqual({
+ ...defaultUserAgentParameters,
+ field: 'field_1',
+ target_field: 'target_field',
+ properties: ['os'],
+ regex_file: 'hello*',
+ extract_device_type: true,
+ ignore_missing: true,
+ ignore_failure: true,
+ });
+ });
+});
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/common_fields/properties_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/common_fields/properties_field.tsx
index dd52375a19436..c8a50cf64484e 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/common_fields/properties_field.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/common_fields/properties_field.tsx
@@ -6,9 +6,9 @@
*/
import React, { FunctionComponent } from 'react';
+import { EuiComboBoxProps } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { EuiComboBoxOptionOption } from '@elastic/eui';
import { ComboBoxField, FIELD_TYPES, UseField } from '../../../../../../../shared_imports';
import { FieldsConfig, to } from '../shared';
@@ -29,10 +29,10 @@ const fieldsConfig: FieldsConfig = {
interface Props {
helpText?: React.ReactNode;
- propertyOptions?: EuiComboBoxOptionOption[];
+ euiFieldProps?: EuiComboBoxProps;
}
-export const PropertiesField: FunctionComponent = ({ helpText, propertyOptions }) => {
+export const PropertiesField: FunctionComponent = ({ helpText, euiFieldProps }) => {
return (
= ({ helpText, propertyOp
}}
component={ComboBoxField}
path="fields.properties"
- componentProps={{
- euiFieldProps: {
- options: propertyOptions || [],
- noSuggestions: !propertyOptions,
- },
- }}
+ componentProps={{ euiFieldProps }}
/>
);
};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/user_agent.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/user_agent.tsx
index 893e52bcc0073..2b5a68f799b7e 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/user_agent.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/user_agent.tsx
@@ -6,20 +6,20 @@
*/
import React, { FunctionComponent } from 'react';
-import { EuiCode } from '@elastic/eui';
+import { EuiCode, EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiComboBoxOptionOption } from '@elastic/eui';
-import { FIELD_TYPES, UseField, Field } from '../../../../../../shared_imports';
+import { FIELD_TYPES, ToggleField, UseField, Field } from '../../../../../../shared_imports';
-import { FieldsConfig, from } from './shared';
+import { FieldsConfig, from, to } from './shared';
import { IgnoreMissingField } from './common_fields/ignore_missing_field';
import { FieldNameField } from './common_fields/field_name_field';
import { TargetField } from './common_fields/target_field';
import { PropertiesField } from './common_fields/properties_field';
-const propertyOptions: EuiComboBoxOptionOption[] = [
+const propertyOptions: Array> = [
{ label: 'name' },
{ label: 'os' },
{ label: 'device' },
@@ -47,6 +47,18 @@ const fieldsConfig: FieldsConfig = {
}
),
},
+ extract_device_type: {
+ type: FIELD_TYPES.TOGGLE,
+ defaultValue: false,
+ deserializer: to.booleanOrUndef,
+ serializer: from.undefinedIfValue(false),
+ helpText: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.userAgentForm.extractDeviceTypeFieldHelpText',
+ {
+ defaultMessage: 'Extracts device type from the user agent string.',
+ }
+ ),
+ },
};
export const UserAgent: FunctionComponent = () => {
@@ -59,7 +71,12 @@ export const UserAgent: FunctionComponent = () => {
)}
/>
-
+
{
'xpack.ingestPipelines.pipelineEditor.userAgentForm.propertiesFieldHelpText',
{ defaultMessage: 'Properties added to the target field.' }
)}
- propertyOptions={propertyOptions}
+ euiFieldProps={{
+ options: propertyOptions,
+ noSuggestions: false,
+ 'data-test-subj': 'propertiesValueField',
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ ),
+ }}
/>
From 9a1f5a4a7a81603f1b11ff722c3c1c3707e00ca9 Mon Sep 17 00:00:00 2001
From: Peter Pisljar
Date: Thu, 24 Jun 2021 12:23:41 +0200
Subject: [PATCH 21/86] switching to peggy (#103169)
---
packages/kbn-interpreter/BUILD.bazel | 8 ++++----
.../grammar/{grammar.pegjs => grammar.peggy} | 0
2 files changed, 4 insertions(+), 4 deletions(-)
rename packages/kbn-interpreter/grammar/{grammar.pegjs => grammar.peggy} (100%)
diff --git a/packages/kbn-interpreter/BUILD.bazel b/packages/kbn-interpreter/BUILD.bazel
index 4492faabfdf81..c29faf65638ca 100644
--- a/packages/kbn-interpreter/BUILD.bazel
+++ b/packages/kbn-interpreter/BUILD.bazel
@@ -1,5 +1,5 @@
load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
-load("@npm//pegjs:index.bzl", "pegjs")
+load("@npm//peggy:index.bzl", "peggy")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm")
PKG_BASE_NAME = "kbn-interpreter"
@@ -37,10 +37,10 @@ TYPES_DEPS = [
DEPS = SRC_DEPS + TYPES_DEPS
-pegjs(
+peggy(
name = "grammar",
data = [
- ":grammar/grammar.pegjs"
+ ":grammar/grammar.peggy"
],
output_dir = True,
args = [
@@ -48,7 +48,7 @@ pegjs(
"expression,argument",
"-o",
"$(@D)/index.js",
- "./%s/grammar/grammar.pegjs" % package_name()
+ "./%s/grammar/grammar.peggy" % package_name()
],
)
diff --git a/packages/kbn-interpreter/grammar/grammar.pegjs b/packages/kbn-interpreter/grammar/grammar.peggy
similarity index 100%
rename from packages/kbn-interpreter/grammar/grammar.pegjs
rename to packages/kbn-interpreter/grammar/grammar.peggy
From fa71c6d7ac5abfcf79428fc120561d7cc9beb22f Mon Sep 17 00:00:00 2001
From: Pete Harverson
Date: Thu, 24 Jun 2021 11:31:30 +0100
Subject: [PATCH 22/86] [ML] Transforms: Converts management pages to new
layout (#102648)
* [ML] Transforms: Converts management pages to new layout
* [ML] Fix vertical centering of error state in app
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/transform/public/app/app.tsx | 36 ++--
.../public/app/components/section_error.tsx | 18 +-
.../components/with_privileges.tsx | 44 ++---
.../clone_transform_section.tsx | 77 +++++----
.../create_transform_section.tsx | 73 ++++----
.../transform_list.test.tsx.snap | 57 ++++---
.../transform_list/transform_list.tsx | 54 +++---
.../transform_management_section.tsx | 156 ++++++++++--------
8 files changed, 287 insertions(+), 228 deletions(-)
diff --git a/x-pack/plugins/transform/public/app/app.tsx b/x-pack/plugins/transform/public/app/app.tsx
index d4936783a0297..9219f29e4d9f0 100644
--- a/x-pack/plugins/transform/public/app/app.tsx
+++ b/x-pack/plugins/transform/public/app/app.tsx
@@ -10,7 +10,7 @@ import { render, unmountComponentAtNode } from 'react-dom';
import { Router, Route, Switch } from 'react-router-dom';
import { ScopedHistory } from 'kibana/public';
-import { EuiErrorBoundary } from '@elastic/eui';
+import { EuiErrorBoundary, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -35,7 +35,7 @@ export const App: FC<{ history: ScopedHistory }> = ({ history }) => {
title={
}
error={apiError}
@@ -44,21 +44,23 @@ export const App: FC<{ history: ScopedHistory }> = ({ history }) => {
}
return (
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/transform/public/app/components/section_error.tsx b/x-pack/plugins/transform/public/app/components/section_error.tsx
index 2af0c19fb8817..964c13d775d4b 100644
--- a/x-pack/plugins/transform/public/app/components/section_error.tsx
+++ b/x-pack/plugins/transform/public/app/components/section_error.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiCallOut } from '@elastic/eui';
+import { EuiEmptyPrompt, EuiPageContent } from '@elastic/eui';
import React from 'react';
interface Props {
@@ -23,9 +23,17 @@ export const SectionError: React.FunctionComponent = ({
const errorMessage = error?.message ?? JSON.stringify(error, null, 2);
return (
-
- {errorMessage}
- {actions ? actions : null}
-
+
+ {title}}
+ body={
+
+
{errorMessage}
+ {actions ? actions : null}
+
+ }
+ />
+
);
};
diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/with_privileges.tsx b/x-pack/plugins/transform/public/app/lib/authorization/components/with_privileges.tsx
index ef009e6a125e7..cdf4407b4233f 100644
--- a/x-pack/plugins/transform/public/app/lib/authorization/components/with_privileges.tsx
+++ b/x-pack/plugins/transform/public/app/lib/authorization/components/with_privileges.tsx
@@ -7,7 +7,7 @@
import React, { useContext, FC } from 'react';
-import { EuiPageContent } from '@elastic/eui';
+import { EuiFlexItem, EuiFlexGroup, EuiPageContent } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -74,27 +74,31 @@ const MissingClusterPrivileges: FC = ({
missingPrivileges,
privilegesCount,
}) => (
-
-
- }
- message={
-
+
+
+
+ }
+ message={
+
+ }
/>
- }
- />
-
+
+
+
);
export const PrivilegesWrapper: FC<{ privileges: string | string[] }> = ({
diff --git a/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx b/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx
index e4ecc0418d782..8aecf403186c5 100644
--- a/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx
+++ b/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx
@@ -15,12 +15,9 @@ import { i18n } from '@kbn/i18n';
import {
EuiButtonEmpty,
EuiCallOut,
- EuiFlexGroup,
- EuiFlexItem,
- EuiPageContent,
EuiPageContentBody,
+ EuiPageHeader,
EuiSpacer,
- EuiTitle,
} from '@elastic/eui';
import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants';
@@ -105,37 +102,38 @@ export const CloneTransformSection: FC = ({ match, location }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ const docsLink = (
+
+
+
+ );
+
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {typeof errorMessage !== 'undefined' && (
+
+ }
+ rightSideItems={[docsLink]}
+ bottomBorder
+ />
+
+
+
+
+ {typeof errorMessage !== 'undefined' && (
+ <>
= ({ match, location }) => {
>
{JSON.stringify(errorMessage)}
- )}
- {searchItems !== undefined && isInitialized === true && transformConfig !== undefined && (
-
- )}
-
-
+
+ >
+ )}
+ {searchItems !== undefined && isInitialized === true && transformConfig !== undefined && (
+
+ )}
+
);
};
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx
index b88eb8ce48601..d736bd60f2df6 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx
@@ -13,12 +13,9 @@ import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButtonEmpty,
EuiCallOut,
- EuiFlexGroup,
- EuiFlexItem,
- EuiPageContent,
EuiPageContentBody,
+ EuiPageHeader,
EuiSpacer,
- EuiTitle,
} from '@elastic/eui';
import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants';
@@ -42,42 +39,44 @@ export const CreateTransformSection: FC = ({ match }) => {
const { error: searchItemsError, searchItems } = useSearchItems(match.params.savedObjectId);
+ const docsLink = (
+
+
+
+ );
+
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {searchItemsError !== undefined && (
+
+ }
+ rightSideItems={[docsLink]}
+ bottomBorder
+ />
+
+
+
+
+ {searchItemsError !== undefined && (
+ <>
- )}
- {searchItems !== undefined && }
-
-
+
+ >
+ )}
+ {searchItems !== undefined && }
+
);
};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap
index e2de4c0ea1f6c..cf80421711355 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap
@@ -1,23 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Transform: Transform List Minimal initialization 1`] = `
-
- Create your first transform
- ,
- ]
- }
- data-test-subj="transformNoTransformsFound"
- title={
-
- No transforms found
-
- }
-/>
+
+
+
+
+
+ Create your first transform
+ ,
+ ]
+ }
+ data-test-subj="transformNoTransformsFound"
+ title={
+
+ No transforms found
+
+ }
+ />
+
+
+
`;
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx
index bacf8f9deccae..ab30f4793a315 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx
@@ -10,12 +10,15 @@ import React, { MouseEventHandler, FC, useContext, useState } from 'react';
import { i18n } from '@kbn/i18n';
import {
+ EuiButton,
EuiButtonEmpty,
EuiButtonIcon,
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
+ EuiPageContent,
EuiPopover,
+ EuiSpacer,
EuiTitle,
EuiInMemoryTable,
EuiSearchBarProps,
@@ -135,27 +138,36 @@ export const TransformList: FC = ({
if (transforms.length === 0) {
return (
-
- {i18n.translate('xpack.transform.list.emptyPromptTitle', {
- defaultMessage: 'No transforms found',
- })}
-
- }
- actions={[
-
- {i18n.translate('xpack.transform.list.emptyPromptButtonText', {
- defaultMessage: 'Create your first transform',
- })}
- ,
- ]}
- data-test-subj="transformNoTransformsFound"
- />
+
+
+
+
+
+ {i18n.translate('xpack.transform.list.emptyPromptTitle', {
+ defaultMessage: 'No transforms found',
+ })}
+
+ }
+ actions={[
+
+ {i18n.translate('xpack.transform.list.emptyPromptButtonText', {
+ defaultMessage: 'Create your first transform',
+ })}
+ ,
+ ]}
+ data-test-subj="transformNoTransformsFound"
+ />
+
+
+
);
}
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx
index cc4c502f21eb5..2479d34f1579a 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx
@@ -5,23 +5,21 @@
* 2.0.
*/
-import React, { FC, Fragment, useEffect, useState } from 'react';
+import React, { FC, useEffect, useState } from 'react';
-import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButtonEmpty,
- EuiCallOut,
+ EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingContent,
EuiModal,
EuiPageContent,
EuiPageContentBody,
+ EuiPageHeader,
EuiSpacer,
- EuiText,
- EuiTitle,
} from '@elastic/eui';
import { APP_GET_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants';
@@ -77,73 +75,91 @@ export const TransformManagement: FC = () => {
setSavedObjectId(id);
};
+ const docsLink = (
+
+
+
+ );
+
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ <>
+
-
-
-
-
- {!isInitialized && }
- {isInitialized && (
- <>
-
-
- {typeof errorMessage !== 'undefined' && (
-
- {JSON.stringify(errorMessage)}
-
- )}
- {typeof errorMessage === 'undefined' && (
-
- )}
- >
- )}
-
-
+
+ }
+ description={
+
+ }
+ rightSideItems={[docsLink]}
+ bottomBorder
+ />
+
+
+
+
+ {!isInitialized && }
+ {isInitialized && (
+ <>
+
+
+ {typeof errorMessage !== 'undefined' && (
+
+
+
+
+
+
+
+ }
+ body={
+
+
{JSON.stringify(errorMessage)}
+
+ }
+ actions={[]}
+ />
+
+
+
+ )}
+ {typeof errorMessage === 'undefined' && (
+
+ )}
+ >
+ )}
+
+
{isSearchSelectionVisible && (
{
)}
-
+ >
);
};
From d44f9fe6e6fa85ca411ac9c9e89c7e6711d09753 Mon Sep 17 00:00:00 2001
From: Nathan L Smith
Date: Thu, 24 Jun 2021 06:29:53 -0500
Subject: [PATCH 23/86] Use observability plugin breadcrumbs in APM (#103168)
Both APM and Observability plugins have a `useBreadcrumbs` hook.
APM's takes the whole list of route definitions, creates the whole path of breadcrumbs, and has an effect to set the breadcrumbs and the page title.
The Observability plugin's `useBreadcrumbs` just takes an array of breadcrumb objects, adds onclick handlers for them, and has an effect to set the breadcrumbs and the page title.
Rename APM's `useBreadcrumbs` to `useApmBreadcrumbs`. It still constructs the path based on the routes and the current route, but then just calls out to the Observability plugin's `useBreadcrumbs` to do the breadcrumb and title setting.
Now all APM breadcrumbs begin with "Observability" which links to the Observability overview, but the rest of them remain the same.
---
.../public/components/routing/app_root.tsx | 4 +-
.../apm_plugin/mock_apm_plugin_context.tsx | 1 +
....test.tsx => use_apm_breadcrumbs.test.tsx} | 58 ++++---------------
..._breadcrumbs.ts => use_apm_breadcrumbs.ts} | 29 ++--------
4 files changed, 18 insertions(+), 74 deletions(-)
rename x-pack/plugins/apm/public/hooks/{use_breadcrumbs.test.tsx => use_apm_breadcrumbs.test.tsx} (79%)
rename x-pack/plugins/apm/public/hooks/{use_breadcrumbs.ts => use_apm_breadcrumbs.ts} (85%)
diff --git a/x-pack/plugins/apm/public/components/routing/app_root.tsx b/x-pack/plugins/apm/public/components/routing/app_root.tsx
index 2bb387ae315ff..8fc59a01eeca0 100644
--- a/x-pack/plugins/apm/public/components/routing/app_root.tsx
+++ b/x-pack/plugins/apm/public/components/routing/app_root.tsx
@@ -24,7 +24,7 @@ import {
} from '../../context/apm_plugin/apm_plugin_context';
import { LicenseProvider } from '../../context/license/license_context';
import { UrlParamsProvider } from '../../context/url_params_context/url_params_context';
-import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
+import { useApmBreadcrumbs } from '../../hooks/use_apm_breadcrumbs';
import { ApmPluginStartDeps } from '../../plugin';
import { HeaderMenuPortal } from '../../../../observability/public';
import { ApmHeaderActionMenu } from '../shared/apm_header_action_menu';
@@ -79,7 +79,7 @@ export function ApmAppRoot({
}
function MountApmHeaderActionMenu() {
- useBreadcrumbs(apmRouteConfig);
+ useApmBreadcrumbs(apmRouteConfig);
const { setHeaderActionMenu } = useApmPluginContext().appMountParameters;
return (
diff --git a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
index a16f81826636b..bcc1932dde7cb 100644
--- a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
+++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
@@ -44,6 +44,7 @@ const mockCore = {
ml: {},
},
currentAppId$: new Observable(),
+ getUrlForApp: (appId: string) => '',
navigateToUrl: (url: string) => {},
},
chrome: {
diff --git a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx b/x-pack/plugins/apm/public/hooks/use_apm_breadcrumbs.test.tsx
similarity index 79%
rename from x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx
rename to x-pack/plugins/apm/public/hooks/use_apm_breadcrumbs.test.tsx
index 64990651b52bb..1cdb84c324750 100644
--- a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx
+++ b/x-pack/plugins/apm/public/hooks/use_apm_breadcrumbs.test.tsx
@@ -15,14 +15,15 @@ import {
mockApmPluginContextValue,
MockApmPluginContextWrapper,
} from '../context/apm_plugin/mock_apm_plugin_context';
-import { useBreadcrumbs } from './use_breadcrumbs';
+import { useApmBreadcrumbs } from './use_apm_breadcrumbs';
+import { useBreadcrumbs } from '../../../observability/public';
+
+jest.mock('../../../observability/public');
function createWrapper(path: string) {
return ({ children }: { children?: ReactNode }) => {
const value = (produce(mockApmPluginContextValue, (draft) => {
draft.core.application.navigateToUrl = (url: string) => Promise.resolve();
- draft.core.chrome.docTitle.change = changeTitle;
- draft.core.chrome.setBreadcrumbs = setBreadcrumbs;
}) as unknown) as ApmPluginContextValue;
return (
@@ -36,27 +37,18 @@ function createWrapper(path: string) {
}
function mountBreadcrumb(path: string) {
- renderHook(() => useBreadcrumbs(apmRouteConfig), {
+ renderHook(() => useApmBreadcrumbs(apmRouteConfig), {
wrapper: createWrapper(path),
});
}
-const changeTitle = jest.fn();
-const setBreadcrumbs = jest.fn();
-
-describe('useBreadcrumbs', () => {
- it('changes the page title', () => {
- mountBreadcrumb('/');
-
- expect(changeTitle).toHaveBeenCalledWith(['APM']);
- });
-
+describe('useApmBreadcrumbs', () => {
test('/services/:serviceName/errors/:groupId', () => {
mountBreadcrumb(
'/services/opbeans-node/errors/myGroupId?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0'
);
- expect(setBreadcrumbs).toHaveBeenCalledWith(
+ expect(useBreadcrumbs).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
text: 'APM',
@@ -81,20 +73,12 @@ describe('useBreadcrumbs', () => {
expect.objectContaining({ text: 'myGroupId', href: undefined }),
])
);
-
- expect(changeTitle).toHaveBeenCalledWith([
- 'myGroupId',
- 'Errors',
- 'opbeans-node',
- 'Services',
- 'APM',
- ]);
});
test('/services/:serviceName/errors', () => {
mountBreadcrumb('/services/opbeans-node/errors?kuery=myKuery');
- expect(setBreadcrumbs).toHaveBeenCalledWith(
+ expect(useBreadcrumbs).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
text: 'APM',
@@ -111,19 +95,12 @@ describe('useBreadcrumbs', () => {
expect.objectContaining({ text: 'Errors', href: undefined }),
])
);
-
- expect(changeTitle).toHaveBeenCalledWith([
- 'Errors',
- 'opbeans-node',
- 'Services',
- 'APM',
- ]);
});
test('/services/:serviceName/transactions', () => {
mountBreadcrumb('/services/opbeans-node/transactions?kuery=myKuery');
- expect(setBreadcrumbs).toHaveBeenCalledWith(
+ expect(useBreadcrumbs).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
text: 'APM',
@@ -140,13 +117,6 @@ describe('useBreadcrumbs', () => {
expect.objectContaining({ text: 'Transactions', href: undefined }),
])
);
-
- expect(changeTitle).toHaveBeenCalledWith([
- 'Transactions',
- 'opbeans-node',
- 'Services',
- 'APM',
- ]);
});
test('/services/:serviceName/transactions/view?transactionName=my-transaction-name', () => {
@@ -154,7 +124,7 @@ describe('useBreadcrumbs', () => {
'/services/opbeans-node/transactions/view?kuery=myKuery&transactionName=my-transaction-name'
);
- expect(setBreadcrumbs).toHaveBeenCalledWith(
+ expect(useBreadcrumbs).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
text: 'APM',
@@ -179,13 +149,5 @@ describe('useBreadcrumbs', () => {
}),
])
);
-
- expect(changeTitle).toHaveBeenCalledWith([
- 'my-transaction-name',
- 'Transactions',
- 'opbeans-node',
- 'Services',
- 'APM',
- ]);
});
});
diff --git a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/apm/public/hooks/use_apm_breadcrumbs.ts
similarity index 85%
rename from x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts
rename to x-pack/plugins/apm/public/hooks/use_apm_breadcrumbs.ts
index d907c27319d26..d64bcadf79577 100644
--- a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts
+++ b/x-pack/plugins/apm/public/hooks/use_apm_breadcrumbs.ts
@@ -7,14 +7,15 @@
import { History, Location } from 'history';
import { ChromeBreadcrumb } from 'kibana/public';
-import { MouseEvent, ReactNode, useEffect } from 'react';
+import { MouseEvent } from 'react';
import {
+ match as Match,
matchPath,
RouteComponentProps,
useHistory,
- match as Match,
useLocation,
} from 'react-router-dom';
+import { useBreadcrumbs } from '../../../observability/public';
import { APMRouteDefinition, BreadcrumbTitle } from '../application/routes';
import { getAPMHref } from '../components/shared/Links/apm/APMLink';
import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context';
@@ -164,33 +165,17 @@ function routeDefinitionsToBreadcrumbs({
return breadcrumbs;
}
-/**
- * Get an array for a page title from a list of breadcrumbs
- */
-function getTitleFromBreadcrumbs(breadcrumbs: ChromeBreadcrumb[]): string[] {
- function removeNonStrings(item: ReactNode): item is string {
- return typeof item === 'string';
- }
-
- return breadcrumbs
- .map(({ text }) => text)
- .reverse()
- .filter(removeNonStrings);
-}
-
/**
* Determine the breadcrumbs from the routes, set them, and update the page
* title when the route changes.
*/
-export function useBreadcrumbs(routes: APMRouteDefinition[]) {
+export function useApmBreadcrumbs(routes: APMRouteDefinition[]) {
const history = useHistory();
const location = useLocation();
const { search } = location;
const { core } = useApmPluginContext();
const { basePath } = core.http;
const { navigateToUrl } = core.application;
- const { docTitle, setBreadcrumbs } = core.chrome;
- const changeTitle = docTitle.change;
function wrappedGetAPMHref(path: string) {
return getAPMHref({ basePath, path, search });
@@ -206,10 +191,6 @@ export function useBreadcrumbs(routes: APMRouteDefinition[]) {
wrappedGetAPMHref,
navigateToUrl,
});
- const title = getTitleFromBreadcrumbs(breadcrumbs);
- useEffect(() => {
- changeTitle(title);
- setBreadcrumbs(breadcrumbs);
- }, [breadcrumbs, changeTitle, location, title, setBreadcrumbs]);
+ useBreadcrumbs(breadcrumbs);
}
From 7a3d61fb671fe549444dcb266b46d4664d8f3dc5 Mon Sep 17 00:00:00 2001
From: Dzmitry Lemechko
Date: Thu, 24 Jun 2021 13:45:39 +0200
Subject: [PATCH 24/86] [load testing] adjust ES heap size (#101906)
* [load testing] increase es heap to 2g
* update default simulation
* [heap size] 4g
* [heap size] 6g
* [load testing] es heap size 8g
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/test/load/config.ts | 1 +
x-pack/test/load/runner.ts | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/load/config.ts b/x-pack/test/load/config.ts
index 514440fd73f46..8f8708d155fb1 100644
--- a/x-pack/test/load/config.ts
+++ b/x-pack/test/load/config.ts
@@ -30,6 +30,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
esTestCluster: {
...xpackFunctionalTestsConfig.get('esTestCluster'),
serverArgs: [...xpackFunctionalTestsConfig.get('esTestCluster.serverArgs')],
+ esJavaOpts: '-Xms8g -Xmx8g',
},
kbnTestServer: {
diff --git a/x-pack/test/load/runner.ts b/x-pack/test/load/runner.ts
index 3e7a4817eeef1..2d379391b2089 100644
--- a/x-pack/test/load/runner.ts
+++ b/x-pack/test/load/runner.ts
@@ -18,7 +18,7 @@ const simulationPackage = 'org.kibanaLoadTest.simulation';
const simulationFIleExtension = '.scala';
const gatlingProjectRootPath: string =
process.env.GATLING_PROJECT_PATH || resolve(REPO_ROOT, '../kibana-load-testing');
-const simulationEntry: string = process.env.GATLING_SIMULATIONS || 'DemoJourney';
+const simulationEntry: string = process.env.GATLING_SIMULATIONS || 'branch.DemoJourney';
if (!Fs.existsSync(gatlingProjectRootPath)) {
throw createFlagError(
From 9b9c47b269a437dad69ddb384b2ea6b124a60e5b Mon Sep 17 00:00:00 2001
From: Jean-Louis Leysens
Date: Thu, 24 Jun 2021 14:45:15 +0200
Subject: [PATCH 25/86] [Fleet] Fix double policy header layout (#103076)
* Fix double policy header layout
- Use the default page title without tabs while loading the
add integration view
* remove unused import
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../fleet/public/applications/fleet/app.tsx | 30 +++++++++-----
.../fleet/layouts/{ => default}/default.tsx | 35 +++-------------
.../layouts/default/default_page_title.tsx | 40 +++++++++++++++++++
.../fleet/layouts/default/index.ts | 9 +++++
.../applications/fleet/layouts/index.tsx | 2 +-
5 files changed, 75 insertions(+), 41 deletions(-)
rename x-pack/plugins/fleet/public/applications/fleet/layouts/{ => default}/default.tsx (65%)
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/layouts/default/default_page_title.tsx
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/layouts/default/index.ts
diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx
index c4cc4d92f5d95..8be6232733def 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx
@@ -5,12 +5,13 @@
* 2.0.
*/
+import type { FunctionComponent } from 'react';
import React, { memo, useEffect, useState } from 'react';
import type { AppMountParameters } from 'kibana/public';
import { EuiCode, EuiEmptyPrompt, EuiErrorBoundary, EuiPanel, EuiPortal } from '@elastic/eui';
import type { History } from 'history';
import { createHashHistory } from 'history';
-import { Router, Redirect, Route, Switch } from 'react-router-dom';
+import { Router, Redirect, Route, Switch, useRouteMatch } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
@@ -39,7 +40,7 @@ import { Error, Loading, SettingFlyout, FleetSetupLoading } from './components';
import type { UIExtensionsStorage } from './types';
import { FLEET_ROUTING_PATHS } from './constants';
-import { DefaultLayout, WithoutHeaderLayout } from './layouts';
+import { DefaultLayout, DefaultPageTitle, WithoutHeaderLayout, WithHeaderLayout } from './layouts';
import { AgentPolicyApp } from './sections/agent_policy';
import { DataStreamApp } from './sections/data_stream';
import { AgentsApp } from './sections/agents';
@@ -48,11 +49,18 @@ import { EnrollmentTokenListPage } from './sections/agents/enrollment_token_list
const FEEDBACK_URL = 'https://ela.st/fleet-feedback';
-const ErrorLayout = ({ children }: { children: JSX.Element }) => (
+const ErrorLayout: FunctionComponent<{ isAddIntegrationsPath: boolean }> = ({
+ isAddIntegrationsPath,
+ children,
+}) => (
-
- {children}
-
+ {isAddIntegrationsPath ? (
+ }>{children}
+ ) : (
+
+ {children}
+
+ )}
);
@@ -71,6 +79,8 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
const [isInitialized, setIsInitialized] = useState(false);
const [initializationError, setInitializationError] = useState(null);
+ const isAddIntegrationsPath = !!useRouteMatch(FLEET_ROUTING_PATHS.add_integration_to_policy);
+
useEffect(() => {
(async () => {
setIsPermissionsLoading(false);
@@ -109,7 +119,7 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
if (isPermissionsLoading || permissionsError) {
return (
-
+
{isPermissionsLoading ? (
) : permissionsError === 'REQUEST_ERROR' ? (
@@ -168,7 +178,7 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
if (!isInitialized || initializationError) {
return (
-
+
{initializationError ? (
-
-
-
+
diff --git a/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/default/default.tsx
similarity index 65%
rename from x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx
rename to x-pack/plugins/fleet/public/applications/fleet/layouts/default/default.tsx
index f312ff374d792..c6ef212b3995e 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/layouts/default/default.tsx
@@ -6,12 +6,13 @@
*/
import React from 'react';
-import { EuiText, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import type { Section } from '../sections';
-import { useLink, useConfig } from '../hooks';
-import { WithHeaderLayout } from '../../../layouts';
+import type { Section } from '../../sections';
+import { useLink, useConfig } from '../../hooks';
+import { WithHeaderLayout } from '../../../../layouts';
+
+import { DefaultPageTitle } from './default_page_title';
interface Props {
section?: Section;
@@ -24,31 +25,7 @@ export const DefaultLayout: React.FunctionComponent = ({ section, childre
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
+ leftColumn={ }
tabs={[
{
name: (
diff --git a/x-pack/plugins/fleet/public/applications/fleet/layouts/default/default_page_title.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/default/default_page_title.tsx
new file mode 100644
index 0000000000000..e525a059b7837
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/layouts/default/default_page_title.tsx
@@ -0,0 +1,40 @@
+/*
+ * 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 type { FunctionComponent } from 'react';
+import React from 'react';
+
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiText } from '@elastic/eui';
+
+export const DefaultPageTitle: FunctionComponent = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/layouts/default/index.ts b/x-pack/plugins/fleet/public/applications/fleet/layouts/default/index.ts
new file mode 100644
index 0000000000000..9b0d3ee06138f
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/layouts/default/index.ts
@@ -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 { DefaultLayout } from './default';
+export { DefaultPageTitle } from './default_page_title';
diff --git a/x-pack/plugins/fleet/public/applications/fleet/layouts/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/index.tsx
index 71cb8d3aeeb36..0c07f1ffecb79 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/layouts/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/layouts/index.tsx
@@ -7,4 +7,4 @@
export * from '../../../layouts';
-export { DefaultLayout } from './default';
+export { DefaultLayout, DefaultPageTitle } from './default';
From 2a8f3eb2f921cfb1d6a5faaac59cbac5aac24910 Mon Sep 17 00:00:00 2001
From: Jean-Louis Leysens
Date: Thu, 24 Jun 2021 14:45:35 +0200
Subject: [PATCH 26/86] [Fleet] Fix staleness bug in "Add agent" flyout
(#103095)
* * Fix stale enrollment api token bug
* Refactored naming
* raise the state of the selected enrollment api key to parent to avoid state sync issues
* removed consts for onKeyChange and selectedApiKeyId
* fix typo
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
...advanced_agent_authentication_settings.tsx | 38 ++++++++----------
.../agent_policy_selection.tsx | 40 ++++++++++---------
.../managed_instructions.tsx | 11 ++---
.../agent_enrollment_flyout/steps.tsx | 12 ++++--
4 files changed, 52 insertions(+), 49 deletions(-)
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/advanced_agent_authentication_settings.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/advanced_agent_authentication_settings.tsx
index 25602b7e108fd..96fab27a55050 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/advanced_agent_authentication_settings.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/advanced_agent_authentication_settings.tsx
@@ -21,20 +21,19 @@ import {
interface Props {
agentPolicyId?: string;
+ selectedApiKeyId?: string;
onKeyChange: (key?: string) => void;
}
export const AdvancedAgentAuthenticationSettings: FunctionComponent = ({
agentPolicyId,
+ selectedApiKeyId,
onKeyChange,
}) => {
const { notifications } = useStartServices();
const [enrollmentAPIKeys, setEnrollmentAPIKeys] = useState(
[]
);
- // TODO: Remove this piece of state since we don't need it here. The currently selected enrollment API key only
- // needs to live on the form
- const [selectedEnrollmentApiKey, setSelectedEnrollmentApiKey] = useState();
const [isLoadingEnrollmentKey, setIsLoadingEnrollmentKey] = useState(false);
const [isAuthenticationSettingsOpen, setIsAuthenticationSettingsOpen] = useState(false);
@@ -51,7 +50,7 @@ export const AdvancedAgentAuthenticationSettings: FunctionComponent = ({
return;
}
setEnrollmentAPIKeys([res.data.item]);
- setSelectedEnrollmentApiKey(res.data.item.id);
+ onKeyChange(res.data.item.id);
notifications.toasts.addSuccess(
i18n.translate('xpack.fleet.newEnrollmentKey.keyCreatedToasts', {
defaultMessage: 'Enrollment token created',
@@ -66,15 +65,6 @@ export const AdvancedAgentAuthenticationSettings: FunctionComponent = ({
}
};
- useEffect(
- function triggerOnKeyChangeEffect() {
- if (onKeyChange) {
- onKeyChange(selectedEnrollmentApiKey);
- }
- },
- [onKeyChange, selectedEnrollmentApiKey]
- );
-
useEffect(
function useEnrollmentKeysForAgentPolicyEffect() {
if (!agentPolicyId) {
@@ -97,9 +87,13 @@ export const AdvancedAgentAuthenticationSettings: FunctionComponent = ({
throw new Error('No data while fetching enrollment API keys');
}
- setEnrollmentAPIKeys(
- res.data.list.filter((key) => key.policy_id === agentPolicyId && key.active === true)
+ const enrollmentAPIKeysResponse = res.data.list.filter(
+ (key) => key.policy_id === agentPolicyId && key.active === true
);
+
+ setEnrollmentAPIKeys(enrollmentAPIKeysResponse);
+ // Default to the first enrollment key if there is one.
+ onKeyChange(enrollmentAPIKeysResponse[0]?.id);
} catch (error) {
notifications.toasts.addError(error, {
title: 'Error',
@@ -108,21 +102,21 @@ export const AdvancedAgentAuthenticationSettings: FunctionComponent = ({
}
fetchEnrollmentAPIKeys();
},
- [agentPolicyId, notifications.toasts]
+ [onKeyChange, agentPolicyId, notifications.toasts]
);
useEffect(
function useDefaultEnrollmentKeyForAgentPolicyEffect() {
if (
- !selectedEnrollmentApiKey &&
+ !selectedApiKeyId &&
enrollmentAPIKeys.length > 0 &&
enrollmentAPIKeys[0].policy_id === agentPolicyId
) {
const enrollmentAPIKeyId = enrollmentAPIKeys[0].id;
- setSelectedEnrollmentApiKey(enrollmentAPIKeyId);
+ onKeyChange(enrollmentAPIKeyId);
}
},
- [enrollmentAPIKeys, selectedEnrollmentApiKey, agentPolicyId]
+ [enrollmentAPIKeys, selectedApiKeyId, agentPolicyId, onKeyChange]
);
return (
<>
@@ -139,14 +133,14 @@ export const AdvancedAgentAuthenticationSettings: FunctionComponent = ({
{isAuthenticationSettingsOpen && (
<>
- {enrollmentAPIKeys.length && selectedEnrollmentApiKey ? (
+ {enrollmentAPIKeys.length && selectedApiKeyId ? (
({
value: key.id,
text: key.name,
}))}
- value={selectedEnrollmentApiKey || undefined}
+ value={selectedApiKeyId || undefined}
prepend={
= ({
}
onChange={(e) => {
- setSelectedEnrollmentApiKey(e.target.value);
+ onKeyChange(e.target.value);
}}
/>
) : (
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.tsx
index f92b2d4825935..d9d1aa2e77f86 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.tsx
@@ -22,6 +22,7 @@ type Props = {
} & (
| {
withKeySelection: true;
+ selectedApiKeyId?: string;
onKeyChange?: (key?: string) => void;
}
| {
@@ -31,9 +32,9 @@ type Props = {
const resolveAgentId = (
agentPolicies?: AgentPolicy[],
- selectedAgentId?: string
+ selectedAgentPolicyId?: string
): undefined | string => {
- if (agentPolicies && agentPolicies.length && !selectedAgentId) {
+ if (agentPolicies && agentPolicies.length && !selectedAgentPolicyId) {
if (agentPolicies.length === 1) {
return agentPolicies[0].id;
}
@@ -44,33 +45,33 @@ const resolveAgentId = (
}
}
- return selectedAgentId;
+ return selectedAgentPolicyId;
};
export const EnrollmentStepAgentPolicy: React.FC = (props) => {
- const { withKeySelection, agentPolicies, onAgentPolicyChange, excludeFleetServer } = props;
- const onKeyChange = props.withKeySelection && props.onKeyChange;
- const [selectedAgentId, setSelectedAgentId] = useState(
+ const { agentPolicies, onAgentPolicyChange, excludeFleetServer } = props;
+
+ const [selectedAgentPolicyId, setSelectedAgentPolicyId] = useState(
() => resolveAgentId(agentPolicies, undefined) // no agent id selected yet
);
useEffect(
function triggerOnAgentPolicyChangeEffect() {
if (onAgentPolicyChange) {
- onAgentPolicyChange(selectedAgentId);
+ onAgentPolicyChange(selectedAgentPolicyId);
}
},
- [selectedAgentId, onAgentPolicyChange]
+ [selectedAgentPolicyId, onAgentPolicyChange]
);
useEffect(
function useDefaultAgentPolicyEffect() {
- const resolvedId = resolveAgentId(agentPolicies, selectedAgentId);
- if (resolvedId !== selectedAgentId) {
- setSelectedAgentId(resolvedId);
+ const resolvedId = resolveAgentId(agentPolicies, selectedAgentPolicyId);
+ if (resolvedId !== selectedAgentPolicyId) {
+ setSelectedAgentPolicyId(resolvedId);
}
},
- [agentPolicies, selectedAgentId]
+ [agentPolicies, selectedAgentPolicyId]
);
return (
@@ -90,25 +91,26 @@ export const EnrollmentStepAgentPolicy: React.FC = (props) => {
value: agentPolicy.id,
text: agentPolicy.name,
}))}
- value={selectedAgentId || undefined}
- onChange={(e) => setSelectedAgentId(e.target.value)}
+ value={selectedAgentPolicyId || undefined}
+ onChange={(e) => setSelectedAgentPolicyId(e.target.value)}
aria-label={i18n.translate('xpack.fleet.enrollmentStepAgentPolicy.policySelectAriaLabel', {
defaultMessage: 'Agent policy',
})}
/>
- {selectedAgentId && (
+ {selectedAgentPolicyId && (
)}
- {withKeySelection && onKeyChange && (
+ {props.withKeySelection && props.onKeyChange && (
<>
>
)}
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx
index 919f0c3052db9..efae8db377f7f 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx
@@ -62,10 +62,10 @@ export const ManagedInstructions = React.memo(
({ agentPolicy, agentPolicies, viewDataStepContent }) => {
const fleetStatus = useFleetStatus();
- const [selectedAPIKeyId, setSelectedAPIKeyId] = useState();
+ const [selectedApiKeyId, setSelectedAPIKeyId] = useState();
const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState(false);
- const apiKey = useGetOneEnrollmentAPIKey(selectedAPIKeyId);
+ const apiKey = useGetOneEnrollmentAPIKey(selectedApiKeyId);
const settings = useGetSettings();
const fleetServerInstructions = useFleetServerInstructions(apiKey?.data?.item?.policy_id);
@@ -84,10 +84,11 @@ export const ManagedInstructions = React.memo(
!agentPolicy
? AgentPolicySelectionStep({
agentPolicies,
+ selectedApiKeyId,
setSelectedAPIKeyId,
setIsFleetServerPolicySelected,
})
- : AgentEnrollmentKeySelectionStep({ agentPolicy, setSelectedAPIKeyId }),
+ : AgentEnrollmentKeySelectionStep({ agentPolicy, selectedApiKeyId, setSelectedAPIKeyId }),
];
if (isFleetServerPolicySelected) {
baseSteps.push(
@@ -101,7 +102,7 @@ export const ManagedInstructions = React.memo(
title: i18n.translate('xpack.fleet.agentEnrollment.stepEnrollAndRunAgentTitle', {
defaultMessage: 'Enroll and start the Elastic Agent',
}),
- children: selectedAPIKeyId && apiKey.data && (
+ children: selectedApiKeyId && apiKey.data && (
),
});
@@ -115,7 +116,7 @@ export const ManagedInstructions = React.memo(
}, [
agentPolicy,
agentPolicies,
- selectedAPIKeyId,
+ selectedApiKeyId,
apiKey.data,
isFleetServerPolicySelected,
settings.data?.item?.fleet_server_hosts,
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx
index 03cff88e63969..8b12994473e34 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx
@@ -49,14 +49,16 @@ export const DownloadStep = () => {
export const AgentPolicySelectionStep = ({
agentPolicies,
- setSelectedAPIKeyId,
setSelectedPolicyId,
- setIsFleetServerPolicySelected,
+ selectedApiKeyId,
+ setSelectedAPIKeyId,
excludeFleetServer,
+ setIsFleetServerPolicySelected,
}: {
agentPolicies?: AgentPolicy[];
- setSelectedAPIKeyId?: (key?: string) => void;
setSelectedPolicyId?: (policyId?: string) => void;
+ selectedApiKeyId?: string;
+ setSelectedAPIKeyId?: (key?: string) => void;
setIsFleetServerPolicySelected?: (selected: boolean) => void;
excludeFleetServer?: boolean;
}) => {
@@ -99,6 +101,7 @@ export const AgentPolicySelectionStep = ({
void;
}) => {
return {
@@ -132,6 +137,7 @@ export const AgentEnrollmentKeySelectionStep = ({
>
From a50d94908c3202ba462aac8421b3f9acb333f4d2 Mon Sep 17 00:00:00 2001
From: Scotty Bollinger
Date: Thu, 24 Jun 2021 07:57:49 -0500
Subject: [PATCH 27/86] [Enterprise Search] Add User management feature
(#103173)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Rename method to close both flyouts
This is shared with the forthcoming user flyouts
closeRoleMappingFlyout -> closeUsersAndRolesFlyout
* Add logic for elasticsearch users and single user role mappings
* Add logic for various form states
- Showing and hiding flyouts
- Select and text input values
- User created state to turn flyout into a success message state
* Add User server routes
* Add logic for saving a user
* Add User components
* Add User list and User flyout to RoleMappings view
* Fix path
* Rename things
- Users & roles -> Users and roles
- roleId -> roleMappingId (matches other places in code)
- also added a missing prop to the actions col
* Set default group when modal closed
The UI sets the default group on page load but did not cover the case where the user has chosen a group in a previous interaction and the closed the flyout. This commit adds a method that resets that state when the flyout is closed
Part of porting of https://github.com/elastic/ent-search/pull/3865
Specifically:
https://github.com/elastic/ent-search/commit/a4131b95dab7c0df97bd78e660f25e09ac3e7cec
* Adds tooltip for external attribute
This was missed from the design
Part of porting of https://github.com/elastic/ent-search/pull/3865
Specifically:
https://github.com/elastic/ent-search/commit/03aa349cab4fb32069b64ab8c51a7252ba52e805
* Fix invitations link
* Fix incorrect role type
Role-> RoleTypes
🤷🏽♀️
* Add EuiPortal to Flyout
Wasn’t needed in ent-search; already done for RomeMappingFlyout. Hide whitespace changes plskthx
* Auth provider deprecation warning in mapping UI
Since we're moving fully into Kibana, we're losing our concept of auth providers. In 8.0, role mappings the specify an auth provider will no
longer work, so this adds a small deprecation warning in the role mappings table.
https://github.com/elastic/ent-search/pull/3885
* Email is no longer required
After a slack discussion, it was determined that email should be optional.
This commit also fixes another instance of the App Search role type being wrong.
* Existing users’ usernames should not be editable
* Use EuiLink instead of anchor
* Add validation tests
* Change URL for users_and_roles
Need to change folder and file names but will punt until after 7.14FF
I did throw in updating the logic file path
* Remove unused import
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../app_search/components/layout/nav.test.tsx | 4 +-
.../app_search/components/layout/nav.tsx | 4 +-
.../components/role_mappings/role_mapping.tsx | 4 +-
.../role_mappings/role_mappings.test.tsx | 42 ++-
.../role_mappings/role_mappings.tsx | 31 ++
.../role_mappings/role_mappings_logic.test.ts | 274 +++++++++++++++-
.../role_mappings/role_mappings_logic.ts | 181 ++++++++++-
.../components/role_mappings/user.test.tsx | 124 +++++++
.../components/role_mappings/user.tsx | 106 ++++++
.../applications/app_search/index.test.tsx | 2 +-
.../public/applications/app_search/index.tsx | 6 +-
.../public/applications/app_search/routes.ts | 2 +-
.../shared/role_mapping/constants.ts | 18 +-
.../role_mapping/role_mapping_flyout.test.tsx | 4 +-
.../role_mapping/role_mapping_flyout.tsx | 10 +-
.../role_mapping/role_mappings_table.test.tsx | 12 +-
.../role_mapping/role_mappings_table.tsx | 42 ++-
.../role_mapping/user_added_info.test.tsx | 100 +++++-
.../shared/role_mapping/user_added_info.tsx | 6 +-
.../shared/role_mapping/user_flyout.tsx | 39 ++-
.../role_mapping/user_invitation_callout.tsx | 2 +-
.../role_mapping/user_selector.test.tsx | 3 +-
.../shared/role_mapping/user_selector.tsx | 5 +-
.../shared/role_mapping/users_table.tsx | 5 +-
.../components/layout/nav.test.tsx | 4 +-
.../components/layout/nav.tsx | 6 +-
.../workplace_search/constants.ts | 2 +-
.../applications/workplace_search/index.tsx | 4 +-
.../applications/workplace_search/routes.ts | 2 +-
.../views/role_mappings/role_mapping.tsx | 4 +-
.../role_mappings/role_mappings.test.tsx | 40 ++-
.../views/role_mappings/role_mappings.tsx | 31 ++
.../role_mappings/role_mappings_logic.test.ts | 302 ++++++++++++++++--
.../role_mappings/role_mappings_logic.ts | 192 ++++++++++-
.../views/role_mappings/user.test.tsx | 123 +++++++
.../views/role_mappings/user.tsx | 103 ++++++
.../routes/app_search/role_mappings.test.ts | 49 +++
.../server/routes/app_search/role_mappings.ts | 26 ++
.../workplace_search/role_mappings.test.ts | 49 +++
.../routes/workplace_search/role_mappings.ts | 29 ++
40 files changed, 1868 insertions(+), 124 deletions(-)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.test.tsx
index c9f5452e254e1..ce4a118bef095 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.test.tsx
@@ -100,8 +100,8 @@ describe('useAppSearchNav', () => {
},
{
id: 'usersRoles',
- name: 'Users & roles',
- href: '/role_mappings',
+ name: 'Users and roles',
+ href: '/users_and_roles',
},
],
},
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.tsx
index c3b8ec642233b..793a36f48fe82 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.tsx
@@ -13,7 +13,7 @@ import { generateNavLink } from '../../../shared/layout';
import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants';
import { AppLogic } from '../../app_logic';
-import { ENGINES_PATH, SETTINGS_PATH, CREDENTIALS_PATH, ROLE_MAPPINGS_PATH } from '../../routes';
+import { ENGINES_PATH, SETTINGS_PATH, CREDENTIALS_PATH, USERS_AND_ROLES_PATH } from '../../routes';
import { CREDENTIALS_TITLE } from '../credentials';
import { useEngineNav } from '../engine/engine_nav';
import { ENGINES_TITLE } from '../engines';
@@ -57,7 +57,7 @@ export const useAppSearchNav = () => {
navItems.push({
id: 'usersRoles',
name: ROLE_MAPPINGS_TITLE,
- ...generateNavLink({ to: ROLE_MAPPINGS_PATH }),
+ ...generateNavLink({ to: USERS_AND_ROLES_PATH }),
});
}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx
index b6a9dd72cfd05..dbebd8e46a219 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx
@@ -28,7 +28,7 @@ export const RoleMapping: React.FC = () => {
handleAuthProviderChange,
handleRoleChange,
handleSaveMapping,
- closeRoleMappingFlyout,
+ closeUsersAndRolesFlyout,
} = useActions(RoleMappingsLogic);
const {
@@ -68,7 +68,7 @@ export const RoleMapping: React.FC = () => {
0} error={roleMappingErrors}>
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.test.tsx
index 308022ccb2e5a..64bf41a50a2f0 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.test.tsx
@@ -12,26 +12,39 @@ import React from 'react';
import { shallow } from 'enzyme';
-import { RoleMappingsTable, RoleMappingsHeading } from '../../../shared/role_mapping';
-import { wsRoleMapping } from '../../../shared/role_mapping/__mocks__/roles';
+import {
+ RoleMappingsTable,
+ RoleMappingsHeading,
+ UsersHeading,
+ UsersEmptyPrompt,
+} from '../../../shared/role_mapping';
+import {
+ asRoleMapping,
+ asSingleUserRoleMapping,
+} from '../../../shared/role_mapping/__mocks__/roles';
import { RoleMapping } from './role_mapping';
import { RoleMappings } from './role_mappings';
+import { User } from './user';
describe('RoleMappings', () => {
const initializeRoleMappings = jest.fn();
const initializeRoleMapping = jest.fn();
+ const initializeSingleUserRoleMapping = jest.fn();
const handleDeleteMapping = jest.fn();
const mockValues = {
- roleMappings: [wsRoleMapping],
+ roleMappings: [asRoleMapping],
dataLoading: false,
multipleAuthProvidersConfig: false,
+ singleUserRoleMappings: [asSingleUserRoleMapping],
+ singleUserRoleMappingFlyoutOpen: false,
};
beforeEach(() => {
setMockActions({
initializeRoleMappings,
initializeRoleMapping,
+ initializeSingleUserRoleMapping,
handleDeleteMapping,
});
setMockValues(mockValues);
@@ -50,10 +63,31 @@ describe('RoleMappings', () => {
expect(wrapper.find(RoleMapping)).toHaveLength(1);
});
- it('handles onClick', () => {
+ it('renders User flyout', () => {
+ setMockValues({ ...mockValues, singleUserRoleMappingFlyoutOpen: true });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(User)).toHaveLength(1);
+ });
+
+ it('handles RoleMappingsHeading onClick', () => {
const wrapper = shallow( );
wrapper.find(RoleMappingsHeading).prop('onClick')();
expect(initializeRoleMapping).toHaveBeenCalled();
});
+
+ it('handles UsersHeading onClick', () => {
+ const wrapper = shallow( );
+ wrapper.find(UsersHeading).prop('onClick')();
+
+ expect(initializeSingleUserRoleMapping).toHaveBeenCalled();
+ });
+
+ it('handles empty users state', () => {
+ setMockValues({ ...mockValues, singleUserRoleMappings: [] });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(UsersEmptyPrompt)).toHaveLength(1);
+ });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx
index 03e2ae67eca9e..3e692aa48623e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx
@@ -9,11 +9,16 @@ import React, { useEffect } from 'react';
import { useActions, useValues } from 'kea';
+import { EuiSpacer } from '@elastic/eui';
+
import { APP_SEARCH_PLUGIN } from '../../../../../common/constants';
import {
RoleMappingsTable,
RoleMappingsHeading,
RolesEmptyPrompt,
+ UsersTable,
+ UsersHeading,
+ UsersEmptyPrompt,
} from '../../../shared/role_mapping';
import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants';
@@ -23,6 +28,7 @@ import { AppSearchPageTemplate } from '../layout';
import { ROLE_MAPPINGS_ENGINE_ACCESS_HEADING } from './constants';
import { RoleMapping } from './role_mapping';
import { RoleMappingsLogic } from './role_mappings_logic';
+import { User } from './user';
const ROLES_DOCS_LINK = `${DOCS_PREFIX}/security-and-users.html`;
@@ -31,14 +37,17 @@ export const RoleMappings: React.FC = () => {
enableRoleBasedAccess,
initializeRoleMappings,
initializeRoleMapping,
+ initializeSingleUserRoleMapping,
handleDeleteMapping,
resetState,
} = useActions(RoleMappingsLogic);
const {
roleMappings,
+ singleUserRoleMappings,
multipleAuthProvidersConfig,
dataLoading,
roleMappingFlyoutOpen,
+ singleUserRoleMappingFlyoutOpen,
} = useValues(RoleMappingsLogic);
useEffect(() => {
@@ -46,6 +55,8 @@ export const RoleMappings: React.FC = () => {
return resetState;
}, []);
+ const hasUsers = singleUserRoleMappings.length > 0;
+
const rolesEmptyState = (
{
);
+ const usersTable = (
+
+ );
+
+ const usersSection = (
+ <>
+ initializeSingleUserRoleMapping()} />
+
+ {hasUsers ? usersTable : }
+ >
+ );
+
return (
{
emptyState={rolesEmptyState}
>
{roleMappingFlyoutOpen && }
+ {singleUserRoleMappingFlyoutOpen && }
{roleMappingsSection}
+
+ {usersSection}
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts
index 6985f213d1dd5..16b44e9ec1f11 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts
@@ -15,11 +15,18 @@ import { engines } from '../../__mocks__/engines.mock';
import { nextTick } from '@kbn/test/jest';
-import { asRoleMapping } from '../../../shared/role_mapping/__mocks__/roles';
+import { elasticsearchUsers } from '../../../shared/role_mapping/__mocks__/elasticsearch_users';
+
+import {
+ asRoleMapping,
+ asSingleUserRoleMapping,
+} from '../../../shared/role_mapping/__mocks__/roles';
import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants';
import { RoleMappingsLogic } from './role_mappings_logic';
+const emptyUser = { username: '', email: '' };
+
describe('RoleMappingsLogic', () => {
const { http } = mockHttpValues;
const { clearFlashMessages, flashAPIErrors, setSuccessMessage } = mockFlashMessageHelpers;
@@ -28,6 +35,8 @@ describe('RoleMappingsLogic', () => {
attributes: [],
availableAuthProviders: [],
elasticsearchRoles: [],
+ elasticsearchUser: emptyUser,
+ elasticsearchUsers: [],
roleMapping: null,
roleMappingFlyoutOpen: false,
roleMappings: [],
@@ -43,6 +52,12 @@ describe('RoleMappingsLogic', () => {
selectedAuthProviders: [ANY_AUTH_PROVIDER],
selectedOptions: [],
roleMappingErrors: [],
+ singleUserRoleMapping: null,
+ singleUserRoleMappings: [],
+ singleUserRoleMappingFlyoutOpen: false,
+ userCreated: false,
+ userFormIsNewUser: true,
+ userFormUserIsExisting: true,
};
const mappingsServerProps = {
@@ -53,6 +68,8 @@ describe('RoleMappingsLogic', () => {
availableEngines: engines,
elasticsearchRoles: [],
hasAdvancedRoles: false,
+ singleUserRoleMappings: [asSingleUserRoleMapping],
+ elasticsearchUsers,
};
beforeEach(() => {
@@ -83,7 +100,19 @@ describe('RoleMappingsLogic', () => {
elasticsearchRoles: mappingsServerProps.elasticsearchRoles,
selectedEngines: new Set(),
selectedOptions: [],
+ elasticsearchUsers,
+ elasticsearchUser: elasticsearchUsers[0],
+ singleUserRoleMappings: [asSingleUserRoleMapping],
+ });
+ });
+
+ it('handles fallback if no elasticsearch users present', () => {
+ RoleMappingsLogic.actions.setRoleMappingsData({
+ ...mappingsServerProps,
+ elasticsearchUsers: [],
});
+
+ expect(RoleMappingsLogic.values.elasticsearchUser).toEqual(emptyUser);
});
});
@@ -94,6 +123,26 @@ describe('RoleMappingsLogic', () => {
expect(RoleMappingsLogic.values.dataLoading).toEqual(false);
});
+ describe('setElasticsearchUser', () => {
+ it('sets user', () => {
+ RoleMappingsLogic.actions.setElasticsearchUser(elasticsearchUsers[0]);
+
+ expect(RoleMappingsLogic.values.elasticsearchUser).toEqual(elasticsearchUsers[0]);
+ });
+
+ it('handles fallback if no user present', () => {
+ RoleMappingsLogic.actions.setElasticsearchUser(undefined);
+
+ expect(RoleMappingsLogic.values.elasticsearchUser).toEqual(emptyUser);
+ });
+ });
+
+ it('setSingleUserRoleMapping', () => {
+ RoleMappingsLogic.actions.setSingleUserRoleMapping(asSingleUserRoleMapping);
+
+ expect(RoleMappingsLogic.values.singleUserRoleMapping).toEqual(asSingleUserRoleMapping);
+ });
+
it('handleRoleChange', () => {
RoleMappingsLogic.actions.handleRoleChange('dev');
@@ -152,6 +201,12 @@ describe('RoleMappingsLogic', () => {
});
});
+ it('setUserExistingRadioValue', () => {
+ RoleMappingsLogic.actions.setUserExistingRadioValue(false);
+
+ expect(RoleMappingsLogic.values.userFormUserIsExisting).toEqual(false);
+ });
+
describe('handleAttributeSelectorChange', () => {
const elasticsearchRoles = ['foo', 'bar'];
@@ -174,6 +229,8 @@ describe('RoleMappingsLogic', () => {
attributeName: 'role',
elasticsearchRoles,
selectedEngines: new Set(),
+ elasticsearchUsers,
+ singleUserRoleMappings: [asSingleUserRoleMapping],
});
});
@@ -260,16 +317,59 @@ describe('RoleMappingsLogic', () => {
expect(clearFlashMessages).toHaveBeenCalled();
});
- it('closeRoleMappingFlyout', () => {
+ it('openSingleUserRoleMappingFlyout', () => {
+ mount(mappingsServerProps);
+ RoleMappingsLogic.actions.openSingleUserRoleMappingFlyout();
+
+ expect(RoleMappingsLogic.values.singleUserRoleMappingFlyoutOpen).toEqual(true);
+ expect(clearFlashMessages).toHaveBeenCalled();
+ });
+
+ it('closeUsersAndRolesFlyout', () => {
mount({
...mappingsServerProps,
roleMappingFlyoutOpen: true,
});
- RoleMappingsLogic.actions.closeRoleMappingFlyout();
+ RoleMappingsLogic.actions.closeUsersAndRolesFlyout();
expect(RoleMappingsLogic.values.roleMappingFlyoutOpen).toEqual(false);
expect(clearFlashMessages).toHaveBeenCalled();
});
+
+ it('setElasticsearchUsernameValue', () => {
+ const username = 'newName';
+ RoleMappingsLogic.actions.setElasticsearchUsernameValue(username);
+
+ expect(RoleMappingsLogic.values).toEqual({
+ ...DEFAULT_VALUES,
+ elasticsearchUser: {
+ ...RoleMappingsLogic.values.elasticsearchUser,
+ username,
+ },
+ });
+ });
+
+ it('setElasticsearchEmailValue', () => {
+ const email = 'newEmail@foo.cats';
+ RoleMappingsLogic.actions.setElasticsearchEmailValue(email);
+
+ expect(RoleMappingsLogic.values).toEqual({
+ ...DEFAULT_VALUES,
+ elasticsearchUser: {
+ ...RoleMappingsLogic.values.elasticsearchUser,
+ email,
+ },
+ });
+ });
+
+ it('setUserCreated', () => {
+ RoleMappingsLogic.actions.setUserCreated();
+
+ expect(RoleMappingsLogic.values).toEqual({
+ ...DEFAULT_VALUES,
+ userCreated: true,
+ });
+ });
});
describe('listeners', () => {
@@ -335,6 +435,39 @@ describe('RoleMappingsLogic', () => {
});
});
+ describe('initializeSingleUserRoleMapping', () => {
+ let setElasticsearchUserSpy: jest.MockedFunction;
+ let setRoleMappingSpy: jest.MockedFunction;
+ let setSingleUserRoleMappingSpy: jest.MockedFunction;
+ beforeEach(() => {
+ setElasticsearchUserSpy = jest.spyOn(RoleMappingsLogic.actions, 'setElasticsearchUser');
+ setRoleMappingSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMapping');
+ setSingleUserRoleMappingSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setSingleUserRoleMapping'
+ );
+ });
+
+ it('should handle the new user state and only set an empty mapping', () => {
+ RoleMappingsLogic.actions.initializeSingleUserRoleMapping();
+
+ expect(setElasticsearchUserSpy).not.toHaveBeenCalled();
+ expect(setRoleMappingSpy).not.toHaveBeenCalled();
+ expect(setSingleUserRoleMappingSpy).toHaveBeenCalledWith(undefined);
+ });
+
+ it('should handle an existing user state and set mapping', () => {
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+ RoleMappingsLogic.actions.initializeSingleUserRoleMapping(
+ asSingleUserRoleMapping.roleMapping.id
+ );
+
+ expect(setElasticsearchUserSpy).toHaveBeenCalled();
+ expect(setRoleMappingSpy).toHaveBeenCalled();
+ expect(setSingleUserRoleMappingSpy).toHaveBeenCalledWith(asSingleUserRoleMapping);
+ });
+ });
+
describe('handleSaveMapping', () => {
const body = {
roleType: 'owner',
@@ -430,6 +563,94 @@ describe('RoleMappingsLogic', () => {
});
});
+ describe('handleSaveUser', () => {
+ it('calls API and refreshes list when new mapping', async () => {
+ const initializeRoleMappingsSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'initializeRoleMappings'
+ );
+ const setUserCreatedSpy = jest.spyOn(RoleMappingsLogic.actions, 'setUserCreated');
+ const setSingleUserRoleMappingSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setSingleUserRoleMapping'
+ );
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+
+ http.post.mockReturnValue(Promise.resolve(mappingsServerProps));
+ RoleMappingsLogic.actions.handleSaveUser();
+
+ expect(http.post).toHaveBeenCalledWith('/api/app_search/single_user_role_mapping', {
+ body: JSON.stringify({
+ roleMapping: {
+ engines: [],
+ roleType: 'owner',
+ accessAllEngines: true,
+ },
+ elasticsearchUser: {
+ username: elasticsearchUsers[0].username,
+ email: elasticsearchUsers[0].email,
+ },
+ }),
+ });
+ await nextTick();
+
+ expect(initializeRoleMappingsSpy).toHaveBeenCalled();
+ expect(setUserCreatedSpy).toHaveBeenCalled();
+ expect(setSingleUserRoleMappingSpy).toHaveBeenCalled();
+ });
+
+ it('calls API and refreshes list when existing mapping', async () => {
+ const initializeRoleMappingsSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'initializeRoleMappings'
+ );
+ RoleMappingsLogic.actions.setSingleUserRoleMapping(asSingleUserRoleMapping);
+ RoleMappingsLogic.actions.handleAccessAllEnginesChange(false);
+
+ http.put.mockReturnValue(Promise.resolve(mappingsServerProps));
+ RoleMappingsLogic.actions.handleSaveUser();
+
+ expect(http.post).toHaveBeenCalledWith('/api/app_search/single_user_role_mapping', {
+ body: JSON.stringify({
+ roleMapping: {
+ engines: [],
+ roleType: 'owner',
+ accessAllEngines: false,
+ id: asSingleUserRoleMapping.roleMapping.id,
+ },
+ elasticsearchUser: {
+ username: '',
+ email: '',
+ },
+ }),
+ });
+ await nextTick();
+
+ expect(initializeRoleMappingsSpy).toHaveBeenCalled();
+ });
+
+ it('handles error', async () => {
+ const setRoleMappingErrorsSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setRoleMappingErrors'
+ );
+
+ http.post.mockReturnValue(
+ Promise.reject({
+ body: {
+ attributes: {
+ errors: ['this is an error'],
+ },
+ },
+ })
+ );
+ RoleMappingsLogic.actions.handleSaveUser();
+ await nextTick();
+
+ expect(setRoleMappingErrorsSpy).toHaveBeenCalledWith(['this is an error']);
+ });
+ });
+
describe('handleDeleteMapping', () => {
const roleMappingId = 'r1';
@@ -458,5 +679,52 @@ describe('RoleMappingsLogic', () => {
expect(flashAPIErrors).toHaveBeenCalledWith('this is an error');
});
});
+
+ describe('handleUsernameSelectChange', () => {
+ it('sets elasticsearchUser when match found', () => {
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+ const setElasticsearchUserSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setElasticsearchUser'
+ );
+ RoleMappingsLogic.actions.handleUsernameSelectChange(elasticsearchUsers[0].username);
+
+ expect(setElasticsearchUserSpy).toHaveBeenCalledWith(elasticsearchUsers[0]);
+ });
+
+ it('does not set elasticsearchUser when no match found', () => {
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+ const setElasticsearchUserSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setElasticsearchUser'
+ );
+ RoleMappingsLogic.actions.handleUsernameSelectChange('bogus');
+
+ expect(setElasticsearchUserSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('setUserExistingRadioValue', () => {
+ it('handles existing user', () => {
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+ const setElasticsearchUserSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setElasticsearchUser'
+ );
+ RoleMappingsLogic.actions.setUserExistingRadioValue(true);
+
+ expect(setElasticsearchUserSpy).toHaveBeenCalledWith(elasticsearchUsers[0]);
+ });
+
+ it('handles new user', () => {
+ const setElasticsearchUserSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setElasticsearchUser'
+ );
+ RoleMappingsLogic.actions.setUserExistingRadioValue(false);
+
+ expect(setElasticsearchUserSpy).toHaveBeenCalledWith(emptyUser);
+ });
+ });
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts
index e2ef75897528c..0b57e1d08a294 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts
@@ -16,7 +16,7 @@ import {
} from '../../../shared/flash_messages';
import { HttpLogic } from '../../../shared/http';
import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants';
-import { AttributeName } from '../../../shared/types';
+import { AttributeName, SingleUserRoleMapping, ElasticsearchUser } from '../../../shared/types';
import { ASRoleMapping, RoleTypes } from '../../types';
import { roleHasScopedEngines } from '../../utils/role/has_scoped_engines';
import { Engine } from '../engine/types';
@@ -27,20 +27,25 @@ import {
ROLE_MAPPING_UPDATED_MESSAGE,
} from './constants';
+type UserMapping = SingleUserRoleMapping;
+
interface RoleMappingsServerDetails {
roleMappings: ASRoleMapping[];
attributes: string[];
authProviders: string[];
availableEngines: Engine[];
elasticsearchRoles: string[];
+ elasticsearchUsers: ElasticsearchUser[];
hasAdvancedRoles: boolean;
multipleAuthProvidersConfig: boolean;
+ singleUserRoleMappings: UserMapping[];
}
const getFirstAttributeName = (roleMapping: ASRoleMapping) =>
Object.entries(roleMapping.rules)[0][0] as AttributeName;
const getFirstAttributeValue = (roleMapping: ASRoleMapping) =>
Object.entries(roleMapping.rules)[0][1] as AttributeName;
+const emptyUser = { username: '', email: '' } as ElasticsearchUser;
interface RoleMappingsActions {
handleAccessAllEnginesChange(selected: boolean): { selected: boolean };
@@ -53,21 +58,34 @@ interface RoleMappingsActions {
handleDeleteMapping(roleMappingId: string): { roleMappingId: string };
handleEngineSelectionChange(engineNames: string[]): { engineNames: string[] };
handleRoleChange(roleType: RoleTypes): { roleType: RoleTypes };
+ handleUsernameSelectChange(username: string): { username: string };
handleSaveMapping(): void;
+ handleSaveUser(): void;
initializeRoleMapping(roleMappingId?: string): { roleMappingId?: string };
+ initializeSingleUserRoleMapping(roleMappingId?: string): { roleMappingId?: string };
initializeRoleMappings(): void;
resetState(): void;
setRoleMapping(roleMapping: ASRoleMapping): { roleMapping: ASRoleMapping };
+ setSingleUserRoleMapping(data?: UserMapping): { singleUserRoleMapping: UserMapping };
setRoleMappings({
roleMappings,
}: {
roleMappings: ASRoleMapping[];
}): { roleMappings: ASRoleMapping[] };
setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails;
+ setElasticsearchUser(
+ elasticsearchUser?: ElasticsearchUser
+ ): { elasticsearchUser: ElasticsearchUser };
openRoleMappingFlyout(): void;
- closeRoleMappingFlyout(): void;
+ openSingleUserRoleMappingFlyout(): void;
+ closeUsersAndRolesFlyout(): void;
setRoleMappingErrors(errors: string[]): { errors: string[] };
enableRoleBasedAccess(): void;
+ setUserExistingRadioValue(userFormUserIsExisting: boolean): { userFormUserIsExisting: boolean };
+ setElasticsearchUsernameValue(username: string): { username: string };
+ setElasticsearchEmailValue(email: string): { email: string };
+ setUserCreated(): void;
+ setUserFormIsNewUser(userFormIsNewUser: boolean): { userFormIsNewUser: boolean };
}
interface RoleMappingsValues {
@@ -79,27 +97,38 @@ interface RoleMappingsValues {
availableEngines: Engine[];
dataLoading: boolean;
elasticsearchRoles: string[];
+ elasticsearchUsers: ElasticsearchUser[];
+ elasticsearchUser: ElasticsearchUser;
hasAdvancedRoles: boolean;
multipleAuthProvidersConfig: boolean;
roleMapping: ASRoleMapping | null;
roleMappings: ASRoleMapping[];
+ singleUserRoleMapping: UserMapping | null;
+ singleUserRoleMappings: UserMapping[];
roleType: RoleTypes;
selectedAuthProviders: string[];
selectedEngines: Set;
roleMappingFlyoutOpen: boolean;
+ singleUserRoleMappingFlyoutOpen: boolean;
selectedOptions: EuiComboBoxOptionOption[];
roleMappingErrors: string[];
+ userFormUserIsExisting: boolean;
+ userCreated: boolean;
+ userFormIsNewUser: boolean;
}
export const RoleMappingsLogic = kea>({
- path: ['enterprise_search', 'app_search', 'role_mappings'],
+ path: ['enterprise_search', 'app_search', 'users_and_roles'],
actions: {
setRoleMappingsData: (data: RoleMappingsServerDetails) => data,
setRoleMapping: (roleMapping: ASRoleMapping) => ({ roleMapping }),
+ setElasticsearchUser: (elasticsearchUser: ElasticsearchUser) => ({ elasticsearchUser }),
+ setSingleUserRoleMapping: (singleUserRoleMapping: UserMapping) => ({ singleUserRoleMapping }),
setRoleMappings: ({ roleMappings }: { roleMappings: ASRoleMapping[] }) => ({ roleMappings }),
setRoleMappingErrors: (errors: string[]) => ({ errors }),
handleAuthProviderChange: (value: string) => ({ value }),
handleRoleChange: (roleType: RoleTypes) => ({ roleType }),
+ handleUsernameSelectChange: (username: string) => ({ username }),
handleEngineSelectionChange: (engineNames: string[]) => ({ engineNames }),
handleAttributeSelectorChange: (value: string, firstElasticsearchRole: string) => ({
value,
@@ -108,13 +137,21 @@ export const RoleMappingsLogic = kea ({ value }),
handleAccessAllEnginesChange: (selected: boolean) => ({ selected }),
enableRoleBasedAccess: true,
+ openSingleUserRoleMappingFlyout: true,
+ setUserExistingRadioValue: (userFormUserIsExisting: boolean) => ({ userFormUserIsExisting }),
resetState: true,
initializeRoleMappings: true,
+ initializeSingleUserRoleMapping: (roleMappingId?: string) => ({ roleMappingId }),
initializeRoleMapping: (roleMappingId) => ({ roleMappingId }),
handleDeleteMapping: (roleMappingId: string) => ({ roleMappingId }),
handleSaveMapping: true,
+ handleSaveUser: true,
openRoleMappingFlyout: true,
- closeRoleMappingFlyout: false,
+ closeUsersAndRolesFlyout: false,
+ setElasticsearchUsernameValue: (username: string) => ({ username }),
+ setElasticsearchEmailValue: (email: string) => ({ email }),
+ setUserCreated: true,
+ setUserFormIsNewUser: (userFormIsNewUser: boolean) => ({ userFormIsNewUser }),
},
reducers: {
dataLoading: [
@@ -134,6 +171,13 @@ export const RoleMappingsLogic = kea [],
},
],
+ singleUserRoleMappings: [
+ [],
+ {
+ setRoleMappingsData: (_, { singleUserRoleMappings }) => singleUserRoleMappings,
+ resetState: () => [],
+ },
+ ],
multipleAuthProvidersConfig: [
false,
{
@@ -165,6 +209,14 @@ export const RoleMappingsLogic = kea elasticsearchRoles,
+ closeUsersAndRolesFlyout: () => [ANY_AUTH_PROVIDER],
+ },
+ ],
+ elasticsearchUsers: [
+ [],
+ {
+ setRoleMappingsData: (_, { elasticsearchUsers }) => elasticsearchUsers,
+ resetState: () => [],
},
],
roleMapping: [
@@ -172,7 +224,7 @@ export const RoleMappingsLogic = kea roleMapping,
resetState: () => null,
- closeRoleMappingFlyout: () => null,
+ closeUsersAndRolesFlyout: () => null,
},
],
roleType: [
@@ -188,6 +240,7 @@ export const RoleMappingsLogic = kea roleMapping.accessAllEngines,
handleRoleChange: (_, { roleType }) => !roleHasScopedEngines(roleType),
handleAccessAllEnginesChange: (_, { selected }) => selected,
+ closeUsersAndRolesFlyout: () => true,
},
],
attributeValue: [
@@ -198,7 +251,7 @@ export const RoleMappingsLogic = kea value,
resetState: () => '',
- closeRoleMappingFlyout: () => '',
+ closeUsersAndRolesFlyout: () => '',
},
],
attributeName: [
@@ -207,7 +260,7 @@ export const RoleMappingsLogic = kea getFirstAttributeName(roleMapping),
handleAttributeSelectorChange: (_, { value }) => value,
resetState: () => 'username',
- closeRoleMappingFlyout: () => 'username',
+ closeUsersAndRolesFlyout: () => 'username',
},
],
selectedEngines: [
@@ -222,6 +275,7 @@ export const RoleMappingsLogic = kea new Set(),
},
],
availableAuthProviders: [
@@ -251,17 +305,68 @@ export const RoleMappingsLogic = kea true,
- closeRoleMappingFlyout: () => false,
+ closeUsersAndRolesFlyout: () => false,
initializeRoleMappings: () => false,
initializeRoleMapping: () => true,
},
],
+ singleUserRoleMappingFlyoutOpen: [
+ false,
+ {
+ openSingleUserRoleMappingFlyout: () => true,
+ closeUsersAndRolesFlyout: () => false,
+ initializeSingleUserRoleMapping: () => true,
+ },
+ ],
+ singleUserRoleMapping: [
+ null,
+ {
+ setSingleUserRoleMapping: (_, { singleUserRoleMapping }) => singleUserRoleMapping || null,
+ closeUsersAndRolesFlyout: () => null,
+ },
+ ],
roleMappingErrors: [
[],
{
setRoleMappingErrors: (_, { errors }) => errors,
handleSaveMapping: () => [],
- closeRoleMappingFlyout: () => [],
+ closeUsersAndRolesFlyout: () => [],
+ },
+ ],
+ userFormUserIsExisting: [
+ true,
+ {
+ setUserExistingRadioValue: (_, { userFormUserIsExisting }) => userFormUserIsExisting,
+ closeUsersAndRolesFlyout: () => true,
+ },
+ ],
+ elasticsearchUser: [
+ emptyUser,
+ {
+ setRoleMappingsData: (_, { elasticsearchUsers }) => elasticsearchUsers[0] || emptyUser,
+ setElasticsearchUser: (_, { elasticsearchUser }) => elasticsearchUser || emptyUser,
+ setElasticsearchUsernameValue: (state, { username }) => ({
+ ...state,
+ username,
+ }),
+ setElasticsearchEmailValue: (state, { email }) => ({
+ ...state,
+ email,
+ }),
+ closeUsersAndRolesFlyout: () => emptyUser,
+ },
+ ],
+ userCreated: [
+ false,
+ {
+ setUserCreated: () => true,
+ closeUsersAndRolesFlyout: () => false,
+ },
+ ],
+ userFormIsNewUser: [
+ true,
+ {
+ setUserFormIsNewUser: (_, { userFormIsNewUser }) => userFormIsNewUser,
},
],
},
@@ -303,6 +408,17 @@ export const RoleMappingsLogic = kea id === roleMappingId);
if (roleMapping) actions.setRoleMapping(roleMapping);
},
+ initializeSingleUserRoleMapping: ({ roleMappingId }) => {
+ const singleUserRoleMapping = values.singleUserRoleMappings.find(
+ ({ roleMapping }) => roleMapping.id === roleMappingId
+ );
+ if (singleUserRoleMapping) {
+ actions.setElasticsearchUser(singleUserRoleMapping.elasticsearchUser);
+ actions.setRoleMapping(singleUserRoleMapping.roleMapping);
+ }
+ actions.setSingleUserRoleMapping(singleUserRoleMapping);
+ actions.setUserFormIsNewUser(!singleUserRoleMapping);
+ },
handleDeleteMapping: async ({ roleMappingId }) => {
const { http } = HttpLogic.values;
const route = `/api/app_search/role_mappings/${roleMappingId}`;
@@ -357,11 +473,56 @@ export const RoleMappingsLogic = kea {
clearFlashMessages();
},
- closeRoleMappingFlyout: () => {
+ handleSaveUser: async () => {
+ const { http } = HttpLogic.values;
+ const {
+ roleType,
+ singleUserRoleMapping,
+ accessAllEngines,
+ selectedEngines,
+ elasticsearchUser: { email, username },
+ } = values;
+
+ const body = JSON.stringify({
+ roleMapping: {
+ engines: accessAllEngines ? [] : Array.from(selectedEngines),
+ roleType,
+ accessAllEngines,
+ id: singleUserRoleMapping?.roleMapping?.id,
+ },
+ elasticsearchUser: {
+ username,
+ email,
+ },
+ });
+
+ try {
+ const response = await http.post('/api/app_search/single_user_role_mapping', { body });
+ actions.setSingleUserRoleMapping(response);
+ actions.setUserCreated();
+ actions.initializeRoleMappings();
+ } catch (e) {
+ actions.setRoleMappingErrors(e?.body?.attributes?.errors);
+ }
+ },
+ closeUsersAndRolesFlyout: () => {
clearFlashMessages();
+ const firstUser = values.elasticsearchUsers[0];
+ actions.setElasticsearchUser(firstUser);
},
openRoleMappingFlyout: () => {
clearFlashMessages();
},
+ openSingleUserRoleMappingFlyout: () => {
+ clearFlashMessages();
+ },
+ setUserExistingRadioValue: ({ userFormUserIsExisting }) => {
+ const firstUser = values.elasticsearchUsers[0];
+ actions.setElasticsearchUser(userFormUserIsExisting ? firstUser : emptyUser);
+ },
+ handleUsernameSelectChange: ({ username }) => {
+ const user = values.elasticsearchUsers.find((u) => u.username === username);
+ if (user) actions.setElasticsearchUser(user);
+ },
}),
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx
new file mode 100644
index 0000000000000..88103532bd149
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx
@@ -0,0 +1,124 @@
+/*
+ * 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 '../../../__mocks__/react_router';
+import '../../../__mocks__/shallow_useeffect.mock';
+import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic';
+import { engines } from '../../__mocks__/engines.mock';
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { UserFlyout, UserAddedInfo, UserInvitationCallout } from '../../../shared/role_mapping';
+import { elasticsearchUsers } from '../../../shared/role_mapping/__mocks__/elasticsearch_users';
+import { wsSingleUserRoleMapping } from '../../../shared/role_mapping/__mocks__/roles';
+
+import { EngineAssignmentSelector } from './engine_assignment_selector';
+import { User } from './user';
+
+describe('User', () => {
+ const handleSaveUser = jest.fn();
+ const closeUsersAndRolesFlyout = jest.fn();
+ const setUserExistingRadioValue = jest.fn();
+ const setElasticsearchUsernameValue = jest.fn();
+ const setElasticsearchEmailValue = jest.fn();
+ const handleRoleChange = jest.fn();
+ const handleUsernameSelectChange = jest.fn();
+
+ const mockValues = {
+ availableEngines: [],
+ singleUserRoleMapping: null,
+ userFormUserIsExisting: false,
+ elasticsearchUsers: [],
+ elasticsearchUser: {},
+ roleType: 'admin',
+ roleMappingErrors: [],
+ userCreated: false,
+ userFormIsNewUser: false,
+ hasAdvancedRoles: false,
+ };
+
+ beforeEach(() => {
+ setMockActions({
+ handleSaveUser,
+ closeUsersAndRolesFlyout,
+ setUserExistingRadioValue,
+ setElasticsearchUsernameValue,
+ setElasticsearchEmailValue,
+ handleRoleChange,
+ handleUsernameSelectChange,
+ });
+
+ setMockValues(mockValues);
+ });
+
+ it('renders', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(UserFlyout)).toHaveLength(1);
+ });
+
+ it('renders engine assignment selector when groups present', () => {
+ setMockValues({ ...mockValues, availableEngines: engines, hasAdvancedRoles: true });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(EngineAssignmentSelector)).toHaveLength(1);
+ });
+
+ it('renders userInvitationCallout', () => {
+ setMockValues({
+ ...mockValues,
+ singleUserRoleMapping: wsSingleUserRoleMapping,
+ });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(UserInvitationCallout)).toHaveLength(1);
+ });
+
+ it('renders user added info when user created', () => {
+ setMockValues({
+ ...mockValues,
+ singleUserRoleMapping: wsSingleUserRoleMapping,
+ userCreated: true,
+ });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(UserAddedInfo)).toHaveLength(1);
+ });
+
+ it('disables form when username value not present', () => {
+ setMockValues({
+ ...mockValues,
+ singleUserRoleMapping: wsSingleUserRoleMapping,
+ elasticsearchUsers,
+ elasticsearchUser: {
+ username: null,
+ email: 'email@user.com',
+ },
+ });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(UserFlyout).prop('disabled')).toEqual(true);
+ });
+
+ it('enables form when userFormUserIsExisting', () => {
+ setMockValues({
+ ...mockValues,
+ userFormUserIsExisting: true.valueOf,
+ singleUserRoleMapping: wsSingleUserRoleMapping,
+ elasticsearchUsers,
+ elasticsearchUser: {
+ username: null,
+ email: 'email@user.com',
+ },
+ });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(UserFlyout).prop('disabled')).toEqual(false);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx
new file mode 100644
index 0000000000000..df231fac64df7
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx
@@ -0,0 +1,106 @@
+/*
+ * 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 { useActions, useValues } from 'kea';
+
+import { EuiForm } from '@elastic/eui';
+
+import { getAppSearchUrl } from '../../../shared/enterprise_search_url';
+import {
+ UserFlyout,
+ UserSelector,
+ UserAddedInfo,
+ UserInvitationCallout,
+} from '../../../shared/role_mapping';
+import { RoleTypes } from '../../types';
+
+import { EngineAssignmentSelector } from './engine_assignment_selector';
+import { RoleMappingsLogic } from './role_mappings_logic';
+
+const standardRoles = (['owner', 'admin'] as unknown) as RoleTypes[];
+const advancedRoles = (['dev', 'editor', 'analyst'] as unknown) as RoleTypes[];
+
+export const User: React.FC = () => {
+ const {
+ handleSaveUser,
+ closeUsersAndRolesFlyout,
+ setUserExistingRadioValue,
+ setElasticsearchUsernameValue,
+ setElasticsearchEmailValue,
+ handleRoleChange,
+ handleUsernameSelectChange,
+ } = useActions(RoleMappingsLogic);
+
+ const {
+ availableEngines,
+ singleUserRoleMapping,
+ hasAdvancedRoles,
+ userFormUserIsExisting,
+ elasticsearchUsers,
+ elasticsearchUser,
+ roleType,
+ roleMappingErrors,
+ userCreated,
+ userFormIsNewUser,
+ } = useValues(RoleMappingsLogic);
+
+ const roleTypes = hasAdvancedRoles ? [...standardRoles, ...advancedRoles] : standardRoles;
+ const hasEngines = availableEngines.length > 0;
+ const showEngineAssignmentSelector = hasEngines && hasAdvancedRoles;
+ const flyoutDisabled =
+ !userFormUserIsExisting && (!elasticsearchUser.email || !elasticsearchUser.username);
+
+ const userAddedInfo = singleUserRoleMapping && (
+
+ );
+
+ const userInvitationCallout = singleUserRoleMapping?.invitation && (
+
+ );
+
+ const createUserForm = (
+ 0} error={roleMappingErrors}>
+
+ {showEngineAssignmentSelector && }
+
+ );
+
+ return (
+
+ {userCreated ? userAddedInfo : createUserForm}
+ {userInvitationCallout}
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx
index 2402a6ecc6401..00acea945177a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx
@@ -196,6 +196,6 @@ describe('AppSearchNav', () => {
setMockValues({ myRole: { canViewRoleMappings: true } });
const wrapper = shallow( );
- expect(wrapper.find(SideNavLink).last().prop('to')).toEqual('/role_mappings');
+ expect(wrapper.find(SideNavLink).last().prop('to')).toEqual('/users_and_roles');
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx
index 191758af26758..d7ddad5683f38 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx
@@ -37,7 +37,7 @@ import {
SETUP_GUIDE_PATH,
SETTINGS_PATH,
CREDENTIALS_PATH,
- ROLE_MAPPINGS_PATH,
+ USERS_AND_ROLES_PATH,
ENGINES_PATH,
ENGINE_PATH,
LIBRARY_PATH,
@@ -128,7 +128,7 @@ export const AppSearchConfigured: React.FC> = (props) =
)}
{canViewRoleMappings && (
-
+
)}
@@ -162,7 +162,7 @@ export const AppSearchNav: React.FC = () => {
{CREDENTIALS_TITLE}
)}
{canViewRoleMappings && (
-
+
{ROLE_MAPPINGS_TITLE}
)}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts
index d9d1935c648f7..f086a32bbf590 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts
@@ -15,7 +15,7 @@ export const LIBRARY_PATH = '/library';
export const SETTINGS_PATH = '/settings';
export const CREDENTIALS_PATH = '/credentials';
-export const ROLE_MAPPINGS_PATH = '/role_mappings';
+export const USERS_AND_ROLES_PATH = '/users_and_roles';
export const ENGINES_PATH = '/engines';
export const ENGINE_CREATION_PATH = `${ENGINES_PATH}/new`; // This is safe from conflicting with an :engineName path because new is a reserved name
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts
index 45cab32b67e08..215c76ffb7ef4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts
@@ -136,7 +136,7 @@ export const FILTER_ROLE_MAPPINGS_PLACEHOLDER = i18n.translate(
export const ROLE_MAPPINGS_TITLE = i18n.translate(
'xpack.enterpriseSearch.roleMapping.roleMappingsTitle',
{
- defaultMessage: 'Users & roles',
+ defaultMessage: 'Users and roles',
}
);
@@ -406,3 +406,19 @@ export const FILTER_USERS_LABEL = i18n.translate(
export const NO_USERS_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.noUsersLabel', {
defaultMessage: 'No matching users found',
});
+
+export const EXTERNAL_ATTRIBUTE_TOOLTIP = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.externalAttributeTooltip',
+ {
+ defaultMessage:
+ 'External attributes are defined by the identity provider, and varies from service to service.',
+ }
+);
+
+export const AUTH_PROVIDER_TOOLTIP = i18n.translate(
+ 'xpack.enterpriseSearch.roleMapping.authProviderTooltip',
+ {
+ defaultMessage:
+ 'Provider-specific role mapping is still applied, but configuration is now deprecated.',
+ }
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx
index c0973bb2c9504..ffcf5508233fc 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx
@@ -20,13 +20,13 @@ import {
import { RoleMappingFlyout } from './role_mapping_flyout';
describe('RoleMappingFlyout', () => {
- const closeRoleMappingFlyout = jest.fn();
+ const closeUsersAndRolesFlyout = jest.fn();
const handleSaveMapping = jest.fn();
const props = {
isNew: true,
disabled: false,
- closeRoleMappingFlyout,
+ closeUsersAndRolesFlyout,
handleSaveMapping,
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx
index bae991fef3655..4416a2de28011 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx
@@ -36,7 +36,7 @@ interface Props {
children: React.ReactNode;
isNew: boolean;
disabled: boolean;
- closeRoleMappingFlyout(): void;
+ closeUsersAndRolesFlyout(): void;
handleSaveMapping(): void;
}
@@ -44,13 +44,13 @@ export const RoleMappingFlyout: React.FC = ({
children,
isNew,
disabled,
- closeRoleMappingFlyout,
+ closeUsersAndRolesFlyout,
handleSaveMapping,
}) => (
@@ -71,7 +71,9 @@ export const RoleMappingFlyout: React.FC = ({
- {CANCEL_BUTTON_LABEL}
+
+ {CANCEL_BUTTON_LABEL}
+
{
});
it('renders auth provider display names', () => {
- const wrapper = mount( );
+ const roleMappingWithAuths = {
+ ...wsRoleMapping,
+ authProvider: ['saml', 'native'],
+ };
+ const wrapper = mount( );
- expect(wrapper.find('[data-test-subj="AuthProviderDisplayValue"]').prop('children')).toEqual(
- `${ANY_AUTH_PROVIDER_OPTION_LABEL}, other_auth`
- );
+ expect(wrapper.find('[data-test-subj="ProviderSpecificList"]')).toHaveLength(1);
});
it('handles manage click', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx
index eb9621c7a242c..4136d114d3420 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx
@@ -7,14 +7,17 @@
import React from 'react';
-import { EuiIconTip, EuiInMemoryTable, EuiBasicTableColumn } from '@elastic/eui';
+import { EuiIconTip, EuiInMemoryTable, EuiBasicTableColumn, EuiLink } from '@elastic/eui';
import { ASRoleMapping } from '../../app_search/types';
import { WSRoleMapping } from '../../workplace_search/types';
+import { docLinks } from '../doc_links';
import { RoleRules } from '../types';
import './role_mappings_table.scss';
+const AUTH_PROVIDER_DOCUMENTATION_URL = `${docLinks.enterpriseSearchBase}/users-access.html`;
+
import {
ANY_AUTH_PROVIDER,
ANY_AUTH_PROVIDER_OPTION_LABEL,
@@ -25,6 +28,8 @@ import {
ATTRIBUTE_VALUE_LABEL,
FILTER_ROLE_MAPPINGS_PLACEHOLDER,
ROLE_MAPPINGS_NO_RESULTS_MESSAGE,
+ EXTERNAL_ATTRIBUTE_TOOLTIP,
+ AUTH_PROVIDER_TOOLTIP,
} from './constants';
import { UsersAndRolesRowActions } from './users_and_roles_row_actions';
@@ -46,9 +51,6 @@ interface Props {
handleDeleteMapping(roleMappingId: string): void;
}
-const getAuthProviderDisplayValue = (authProvider: string) =>
- authProvider === ANY_AUTH_PROVIDER ? ANY_AUTH_PROVIDER_OPTION_LABEL : authProvider;
-
export const RoleMappingsTable: React.FC = ({
accessItemKey,
accessHeader,
@@ -69,7 +71,19 @@ export const RoleMappingsTable: React.FC = ({
const attributeNameCol: EuiBasicTableColumn = {
field: 'attribute',
- name: EXTERNAL_ATTRIBUTE_LABEL,
+ name: (
+
+ {EXTERNAL_ATTRIBUTE_LABEL}{' '}
+
+
+ ),
render: (_, { rules }: SharedRoleMapping) => getFirstAttributeName(rules),
};
@@ -105,11 +119,19 @@ export const RoleMappingsTable: React.FC = ({
const authProviderCol: EuiBasicTableColumn = {
field: 'authProvider',
name: AUTH_PROVIDER_LABEL,
- render: (_, { authProvider }: SharedRoleMapping) => (
-
- {authProvider.map(getAuthProviderDisplayValue).join(', ')}
-
- ),
+ render: (_, { authProvider }: SharedRoleMapping) => {
+ if (authProvider[0] === ANY_AUTH_PROVIDER) {
+ return ANY_AUTH_PROVIDER_OPTION_LABEL;
+ }
+ return (
+
+ {authProvider.join(', ')}{' '}
+
+
+
+
+ );
+ },
};
const actionsCol: EuiBasicTableColumn = {
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.test.tsx
index 30bdaa0010b58..57200b389591d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.test.tsx
@@ -9,8 +9,6 @@ import React from 'react';
import { shallow } from 'enzyme';
-import { EuiText } from '@elastic/eui';
-
import { UserAddedInfo } from './';
describe('UserAddedInfo', () => {
@@ -20,9 +18,103 @@ describe('UserAddedInfo', () => {
roleType: 'user',
};
- it('renders', () => {
+ it('renders with email', () => {
const wrapper = shallow( );
- expect(wrapper.find(EuiText)).toHaveLength(6);
+ expect(wrapper).toMatchInlineSnapshot(`
+
+
+
+ Username
+
+
+
+ user1
+
+
+
+
+ Email
+
+
+
+ test@test.com
+
+
+
+
+ Role
+
+
+
+ user
+
+
+
+ `);
+ });
+
+ it('renders without email', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper).toMatchInlineSnapshot(`
+
+
+
+ Username
+
+
+
+ user1
+
+
+
+
+ Email
+
+
+
+
+ —
+
+
+
+
+
+ Role
+
+
+
+ user
+
+
+
+ `);
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.tsx
index a12eae66262a0..37804414a94a9 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_added_info.tsx
@@ -7,7 +7,7 @@
import React from 'react';
-import { EuiSpacer, EuiText } from '@elastic/eui';
+import { EuiSpacer, EuiText, EuiTextColor } from '@elastic/eui';
import { USERNAME_LABEL, EMAIL_LABEL } from '../constants';
@@ -19,6 +19,8 @@ interface Props {
roleType: string;
}
+const noItemsPlaceholder = — ;
+
export const UserAddedInfo: React.FC = ({ username, email, roleType }) => (
<>
@@ -29,7 +31,7 @@ export const UserAddedInfo: React.FC = ({ username, email, roleType }) =>
{EMAIL_LABEL}
- {email}
+ {email || noItemsPlaceholder}
{ROLE_LABEL}
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx
index e13a56a716929..a3be5e295ddfe 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx
@@ -17,6 +17,7 @@ import {
EuiFlyoutFooter,
EuiFlyoutHeader,
EuiIcon,
+ EuiPortal,
EuiText,
EuiTitle,
EuiSpacer,
@@ -92,22 +93,26 @@ export const UserFlyout: React.FC = ({
);
return (
-
-
-
- {isComplete ? IS_COMPLETE_HEADING : IS_EDITING_HEADING}
-
- {!isComplete && (
-
- {IS_EDITING_DESCRIPTION}
-
- )}
-
-
- {children}
-
-
- {isComplete ? completedFooterAction : editingFooterActions}
-
+
+
+
+
+ {isComplete ? IS_COMPLETE_HEADING : IS_EDITING_HEADING}
+
+ {!isComplete && (
+
+ {IS_EDITING_DESCRIPTION}
+
+ )}
+
+
+ {children}
+
+
+
+ {isComplete ? completedFooterAction : editingFooterActions}
+
+
+
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_invitation_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_invitation_callout.tsx
index 8310077ad6f2e..d6d0ce7b050ab 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_invitation_callout.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_invitation_callout.tsx
@@ -23,7 +23,7 @@ interface Props {
}
export const UserInvitationCallout: React.FC = ({ isNew, invitationCode, urlPrefix }) => {
- const link = urlPrefix + invitationCode;
+ const link = `${urlPrefix}/invitations/${invitationCode}`;
const label = isNew ? NEW_INVITATION_LABEL : EXISTING_INVITATION_LABEL;
return (
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx
index 08ddc7ba5427f..60bac97d09835 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx
@@ -13,7 +13,7 @@ import { shallow } from 'enzyme';
import { EuiFormRow } from '@elastic/eui';
-import { Role as ASRole } from '../../app_search/types';
+import { RoleTypes as ASRole } from '../../app_search/types';
import { REQUIRED_LABEL, USERNAME_NO_USERS_TEXT } from './constants';
@@ -107,6 +107,5 @@ describe('UserSelector', () => {
expect(wrapper.find(EuiFormRow).at(0).prop('helpText')).toEqual(USERNAME_NO_USERS_TEXT);
expect(wrapper.find(EuiFormRow).at(1).prop('helpText')).toEqual(REQUIRED_LABEL);
- expect(wrapper.find(EuiFormRow).at(2).prop('helpText')).toEqual(REQUIRED_LABEL);
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx
index 70348bf29894a..d65f97265f6a3 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx
@@ -16,7 +16,7 @@ import {
EuiSpacer,
} from '@elastic/eui';
-import { Role as ASRole } from '../../app_search/types';
+import { RoleTypes as ASRole } from '../../app_search/types';
import { ElasticsearchUser } from '../../shared/types';
import { Role as WSRole } from '../../workplace_search/types';
@@ -80,7 +80,7 @@ export const UserSelector: React.FC = ({
);
const emailInput = (
-
+
= ({
setElasticsearchUsernameValue(e.target.value)}
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx
index 86dc2c2626229..674796775b1d3 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx
@@ -46,8 +46,8 @@ interface SharedRoleMapping extends ASRoleMapping, WSRoleMapping {
interface Props {
accessItemKey: 'groups' | 'engines';
singleUserRoleMappings: Array>;
- initializeSingleUserRoleMapping(roleId: string): string;
- handleDeleteMapping(roleId: string): string;
+ initializeSingleUserRoleMapping(roleMappingId: string): void;
+ handleDeleteMapping(roleMappingId: string): void;
}
const noItemsPlaceholder = — ;
@@ -110,6 +110,7 @@ export const UsersTable: React.FC = ({
{
field: 'id',
name: '',
+ align: 'right',
render: (_, { id, username }: SharedUser) => (
{
},
{
id: 'usersRoles',
- name: 'Users & roles',
- href: '/role_mappings',
+ name: 'Users and roles',
+ href: '/users_and_roles',
},
{
id: 'security',
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx
index ce2f8bf7ef7e4..c8d821dcdae2e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx
@@ -15,7 +15,7 @@ import { NAV } from '../../constants';
import {
SOURCES_PATH,
SECURITY_PATH,
- ROLE_MAPPINGS_PATH,
+ USERS_AND_ROLES_PATH,
GROUPS_PATH,
ORG_SETTINGS_PATH,
} from '../../routes';
@@ -48,7 +48,7 @@ export const useWorkplaceSearchNav = () => {
{
id: 'usersRoles',
name: NAV.ROLE_MAPPINGS,
- ...generateNavLink({ to: ROLE_MAPPINGS_PATH }),
+ ...generateNavLink({ to: USERS_AND_ROLES_PATH }),
},
{
id: 'security',
@@ -92,7 +92,7 @@ export const WorkplaceSearchNav: React.FC = ({
{NAV.GROUPS}
-
+
{NAV.ROLE_MAPPINGS}
{NAV.SECURITY}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts
index aa5419f12c7f3..cf459171a808a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts
@@ -40,7 +40,7 @@ export const NAV = {
defaultMessage: 'Content',
}),
ROLE_MAPPINGS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.roleMappings', {
- defaultMessage: 'Users & roles',
+ defaultMessage: 'Users and roles',
}),
SECURITY: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.security', {
defaultMessage: 'Security',
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
index 8a1e9c0275322..05018be2934b4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
@@ -26,7 +26,7 @@ import {
SOURCE_ADDED_PATH,
PERSONAL_SOURCES_PATH,
ORG_SETTINGS_PATH,
- ROLE_MAPPINGS_PATH,
+ USERS_AND_ROLES_PATH,
SECURITY_PATH,
PERSONAL_SETTINGS_PATH,
PERSONAL_PATH,
@@ -103,7 +103,7 @@ export const WorkplaceSearchConfigured: React.FC = (props) => {
-
+
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts
index 3c564c1f912ec..b9309ffd94809 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts
@@ -48,7 +48,7 @@ export const ENT_SEARCH_LICENSE_MANAGEMENT = `${docLinks.enterpriseSearchBase}/l
export const PERSONAL_PATH = '/p';
-export const ROLE_MAPPINGS_PATH = '/role_mappings';
+export const USERS_AND_ROLES_PATH = '/users_and_roles';
export const USERS_PATH = '/users';
export const SECURITY_PATH = '/security';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx
index cc773895bff1c..20211d40d7010 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx
@@ -43,7 +43,7 @@ export const RoleMapping: React.FC = () => {
handleAttributeSelectorChange,
handleRoleChange,
handleAuthProviderChange,
- closeRoleMappingFlyout,
+ closeUsersAndRolesFlyout,
} = useActions(RoleMappingsLogic);
const {
@@ -70,7 +70,7 @@ export const RoleMapping: React.FC = () => {
0} error={roleMappingErrors}>
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.test.tsx
index 308022ccb2e5a..2e13f24a13eee 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.test.tsx
@@ -12,26 +12,39 @@ import React from 'react';
import { shallow } from 'enzyme';
-import { RoleMappingsTable, RoleMappingsHeading } from '../../../shared/role_mapping';
-import { wsRoleMapping } from '../../../shared/role_mapping/__mocks__/roles';
+import {
+ RoleMappingsTable,
+ RoleMappingsHeading,
+ UsersHeading,
+ UsersEmptyPrompt,
+} from '../../../shared/role_mapping';
+import {
+ wsRoleMapping,
+ wsSingleUserRoleMapping,
+} from '../../../shared/role_mapping/__mocks__/roles';
import { RoleMapping } from './role_mapping';
import { RoleMappings } from './role_mappings';
+import { User } from './user';
describe('RoleMappings', () => {
const initializeRoleMappings = jest.fn();
const initializeRoleMapping = jest.fn();
+ const initializeSingleUserRoleMapping = jest.fn();
const handleDeleteMapping = jest.fn();
const mockValues = {
roleMappings: [wsRoleMapping],
dataLoading: false,
multipleAuthProvidersConfig: false,
+ singleUserRoleMappings: [wsSingleUserRoleMapping],
+ singleUserRoleMappingFlyoutOpen: false,
};
beforeEach(() => {
setMockActions({
initializeRoleMappings,
initializeRoleMapping,
+ initializeSingleUserRoleMapping,
handleDeleteMapping,
});
setMockValues(mockValues);
@@ -50,10 +63,31 @@ describe('RoleMappings', () => {
expect(wrapper.find(RoleMapping)).toHaveLength(1);
});
- it('handles onClick', () => {
+ it('renders User flyout', () => {
+ setMockValues({ ...mockValues, singleUserRoleMappingFlyoutOpen: true });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(User)).toHaveLength(1);
+ });
+
+ it('handles RoleMappingsHeading onClick', () => {
const wrapper = shallow( );
wrapper.find(RoleMappingsHeading).prop('onClick')();
expect(initializeRoleMapping).toHaveBeenCalled();
});
+
+ it('handles UsersHeading onClick', () => {
+ const wrapper = shallow( );
+ wrapper.find(UsersHeading).prop('onClick')();
+
+ expect(initializeSingleUserRoleMapping).toHaveBeenCalled();
+ });
+
+ it('handles empty users state', () => {
+ setMockValues({ ...mockValues, singleUserRoleMappings: [] });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(UsersEmptyPrompt)).toHaveLength(1);
+ });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx
index 01d32bec14ebd..df5d7e4267690 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx
@@ -9,11 +9,16 @@ import React, { useEffect } from 'react';
import { useActions, useValues } from 'kea';
+import { EuiSpacer } from '@elastic/eui';
+
import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants';
import {
RoleMappingsTable,
RoleMappingsHeading,
RolesEmptyPrompt,
+ UsersTable,
+ UsersHeading,
+ UsersEmptyPrompt,
} from '../../../shared/role_mapping';
import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants';
import { WorkplaceSearchPageTemplate } from '../../components/layout';
@@ -23,26 +28,32 @@ import { ROLE_MAPPINGS_TABLE_HEADER } from './constants';
import { RoleMapping } from './role_mapping';
import { RoleMappingsLogic } from './role_mappings_logic';
+import { User } from './user';
export const RoleMappings: React.FC = () => {
const {
enableRoleBasedAccess,
initializeRoleMappings,
initializeRoleMapping,
+ initializeSingleUserRoleMapping,
handleDeleteMapping,
} = useActions(RoleMappingsLogic);
const {
roleMappings,
+ singleUserRoleMappings,
dataLoading,
multipleAuthProvidersConfig,
roleMappingFlyoutOpen,
+ singleUserRoleMappingFlyoutOpen,
} = useValues(RoleMappingsLogic);
useEffect(() => {
initializeRoleMappings();
}, []);
+ const hasUsers = singleUserRoleMappings.length > 0;
+
const rolesEmptyState = (
{
);
+ const usersTable = (
+
+ );
+
+ const usersSection = (
+ <>
+ initializeSingleUserRoleMapping()} />
+
+ {hasUsers ? usersTable : }
+ >
+ );
+
return (
{
emptyState={rolesEmptyState}
>
{roleMappingFlyoutOpen && }
+ {singleUserRoleMappingFlyoutOpen && }
{roleMappingsSection}
+
+ {usersSection}
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts
index a4bbddbd23b49..c85e86ebcca2c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts
@@ -15,11 +15,18 @@ import { groups } from '../../__mocks__/groups.mock';
import { nextTick } from '@kbn/test/jest';
-import { wsRoleMapping } from '../../../shared/role_mapping/__mocks__/roles';
+import { elasticsearchUsers } from '../../../shared/role_mapping/__mocks__/elasticsearch_users';
+
+import {
+ wsRoleMapping,
+ wsSingleUserRoleMapping,
+} from '../../../shared/role_mapping/__mocks__/roles';
import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants';
import { RoleMappingsLogic } from './role_mappings_logic';
+const emptyUser = { username: '', email: '' };
+
describe('RoleMappingsLogic', () => {
const { http } = mockHttpValues;
const { clearFlashMessages, flashAPIErrors } = mockFlashMessageHelpers;
@@ -28,6 +35,8 @@ describe('RoleMappingsLogic', () => {
attributes: [],
availableAuthProviders: [],
elasticsearchRoles: [],
+ elasticsearchUser: emptyUser,
+ elasticsearchUsers: [],
roleMapping: null,
roleMappingFlyoutOpen: false,
roleMappings: [],
@@ -42,6 +51,12 @@ describe('RoleMappingsLogic', () => {
selectedAuthProviders: [ANY_AUTH_PROVIDER],
selectedOptions: [],
roleMappingErrors: [],
+ singleUserRoleMapping: null,
+ singleUserRoleMappings: [],
+ singleUserRoleMappingFlyoutOpen: false,
+ userCreated: false,
+ userFormIsNewUser: true,
+ userFormUserIsExisting: true,
};
const roleGroup = {
id: '123',
@@ -59,6 +74,8 @@ describe('RoleMappingsLogic', () => {
authProviders: [],
availableGroups: [roleGroup, defaultGroup],
elasticsearchRoles: [],
+ singleUserRoleMappings: [wsSingleUserRoleMapping],
+ elasticsearchUsers,
};
beforeEach(() => {
@@ -71,23 +88,36 @@ describe('RoleMappingsLogic', () => {
});
describe('actions', () => {
- it('setRoleMappingsData', () => {
- RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+ describe('setRoleMappingsData', () => {
+ it('sets data based on server response from the `mappings` (plural) endpoint', () => {
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
- expect(RoleMappingsLogic.values.roleMappings).toEqual([wsRoleMapping]);
- expect(RoleMappingsLogic.values.dataLoading).toEqual(false);
- expect(RoleMappingsLogic.values.multipleAuthProvidersConfig).toEqual(true);
- expect(RoleMappingsLogic.values.dataLoading).toEqual(false);
- expect(RoleMappingsLogic.values.attributes).toEqual(mappingsServerProps.attributes);
- expect(RoleMappingsLogic.values.availableGroups).toEqual(mappingsServerProps.availableGroups);
- expect(RoleMappingsLogic.values.includeInAllGroups).toEqual(false);
- expect(RoleMappingsLogic.values.elasticsearchRoles).toEqual(
- mappingsServerProps.elasticsearchRoles
- );
- expect(RoleMappingsLogic.values.selectedOptions).toEqual([
- { label: defaultGroup.name, value: defaultGroup.id },
- ]);
- expect(RoleMappingsLogic.values.selectedGroups).toEqual(new Set([defaultGroup.id]));
+ expect(RoleMappingsLogic.values.roleMappings).toEqual([wsRoleMapping]);
+ expect(RoleMappingsLogic.values.dataLoading).toEqual(false);
+ expect(RoleMappingsLogic.values.multipleAuthProvidersConfig).toEqual(true);
+ expect(RoleMappingsLogic.values.dataLoading).toEqual(false);
+ expect(RoleMappingsLogic.values.attributes).toEqual(mappingsServerProps.attributes);
+ expect(RoleMappingsLogic.values.availableGroups).toEqual(
+ mappingsServerProps.availableGroups
+ );
+ expect(RoleMappingsLogic.values.includeInAllGroups).toEqual(false);
+ expect(RoleMappingsLogic.values.elasticsearchRoles).toEqual(
+ mappingsServerProps.elasticsearchRoles
+ );
+ expect(RoleMappingsLogic.values.selectedOptions).toEqual([
+ { label: defaultGroup.name, value: defaultGroup.id },
+ ]);
+ expect(RoleMappingsLogic.values.selectedGroups).toEqual(new Set([defaultGroup.id]));
+ });
+
+ it('handles fallback if no elasticsearch users present', () => {
+ RoleMappingsLogic.actions.setRoleMappingsData({
+ ...mappingsServerProps,
+ elasticsearchUsers: [],
+ });
+
+ expect(RoleMappingsLogic.values.elasticsearchUser).toEqual(emptyUser);
+ });
});
it('setRoleMappings', () => {
@@ -97,6 +127,26 @@ describe('RoleMappingsLogic', () => {
expect(RoleMappingsLogic.values.dataLoading).toEqual(false);
});
+ describe('setElasticsearchUser', () => {
+ it('sets user', () => {
+ RoleMappingsLogic.actions.setElasticsearchUser(elasticsearchUsers[0]);
+
+ expect(RoleMappingsLogic.values.elasticsearchUser).toEqual(elasticsearchUsers[0]);
+ });
+
+ it('handles fallback if no user present', () => {
+ RoleMappingsLogic.actions.setElasticsearchUser(undefined);
+
+ expect(RoleMappingsLogic.values.elasticsearchUser).toEqual(emptyUser);
+ });
+ });
+
+ it('setSingleUserRoleMapping', () => {
+ RoleMappingsLogic.actions.setSingleUserRoleMapping(wsSingleUserRoleMapping);
+
+ expect(RoleMappingsLogic.values.singleUserRoleMapping).toEqual(wsSingleUserRoleMapping);
+ });
+
it('handleRoleChange', () => {
RoleMappingsLogic.actions.handleRoleChange('user');
@@ -133,6 +183,12 @@ describe('RoleMappingsLogic', () => {
expect(RoleMappingsLogic.values.includeInAllGroups).toEqual(true);
});
+ it('setUserExistingRadioValue', () => {
+ RoleMappingsLogic.actions.setUserExistingRadioValue(false);
+
+ expect(RoleMappingsLogic.values.userFormUserIsExisting).toEqual(false);
+ });
+
describe('handleAttributeSelectorChange', () => {
const elasticsearchRoles = ['foo', 'bar'];
@@ -228,16 +284,50 @@ describe('RoleMappingsLogic', () => {
expect(clearFlashMessages).toHaveBeenCalled();
});
- it('closeRoleMappingFlyout', () => {
+ it('openSingleUserRoleMappingFlyout', () => {
+ mount(mappingsServerProps);
+ RoleMappingsLogic.actions.openSingleUserRoleMappingFlyout();
+
+ expect(RoleMappingsLogic.values.singleUserRoleMappingFlyoutOpen).toEqual(true);
+ expect(clearFlashMessages).toHaveBeenCalled();
+ });
+
+ it('closeUsersAndRolesFlyout', () => {
mount({
...mappingsServerProps,
roleMappingFlyoutOpen: true,
});
- RoleMappingsLogic.actions.closeRoleMappingFlyout();
+ RoleMappingsLogic.actions.closeUsersAndRolesFlyout();
expect(RoleMappingsLogic.values.roleMappingFlyoutOpen).toEqual(false);
expect(clearFlashMessages).toHaveBeenCalled();
});
+
+ it('setElasticsearchUsernameValue', () => {
+ const username = 'newName';
+ RoleMappingsLogic.actions.setElasticsearchUsernameValue(username);
+
+ expect(RoleMappingsLogic.values.elasticsearchUser).toEqual({
+ ...RoleMappingsLogic.values.elasticsearchUser,
+ username,
+ });
+ });
+
+ it('setElasticsearchEmailValue', () => {
+ const email = 'newEmail@foo.cats';
+ RoleMappingsLogic.actions.setElasticsearchEmailValue(email);
+
+ expect(RoleMappingsLogic.values.elasticsearchUser).toEqual({
+ ...RoleMappingsLogic.values.elasticsearchUser,
+ email,
+ });
+ });
+
+ it('setUserCreated', () => {
+ RoleMappingsLogic.actions.setUserCreated();
+
+ expect(RoleMappingsLogic.values.userCreated).toEqual(true);
+ });
});
describe('listeners', () => {
@@ -303,6 +393,39 @@ describe('RoleMappingsLogic', () => {
});
});
+ describe('initializeSingleUserRoleMapping', () => {
+ let setElasticsearchUserSpy: jest.MockedFunction;
+ let setRoleMappingSpy: jest.MockedFunction;
+ let setSingleUserRoleMappingSpy: jest.MockedFunction;
+ beforeEach(() => {
+ setElasticsearchUserSpy = jest.spyOn(RoleMappingsLogic.actions, 'setElasticsearchUser');
+ setRoleMappingSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMapping');
+ setSingleUserRoleMappingSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setSingleUserRoleMapping'
+ );
+ });
+
+ it('should handle the new user state and only set an empty mapping', () => {
+ RoleMappingsLogic.actions.initializeSingleUserRoleMapping();
+
+ expect(setElasticsearchUserSpy).not.toHaveBeenCalled();
+ expect(setRoleMappingSpy).not.toHaveBeenCalled();
+ expect(setSingleUserRoleMappingSpy).toHaveBeenCalledWith(undefined);
+ });
+
+ it('should handle an existing user state and set mapping', () => {
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+ RoleMappingsLogic.actions.initializeSingleUserRoleMapping(
+ wsSingleUserRoleMapping.roleMapping.id
+ );
+
+ expect(setElasticsearchUserSpy).toHaveBeenCalled();
+ expect(setRoleMappingSpy).toHaveBeenCalled();
+ expect(setSingleUserRoleMappingSpy).toHaveBeenCalledWith(wsSingleUserRoleMapping);
+ });
+ });
+
describe('handleSaveMapping', () => {
it('calls API and refreshes list when new mapping', async () => {
const initializeRoleMappingsSpy = jest.spyOn(
@@ -381,6 +504,100 @@ describe('RoleMappingsLogic', () => {
});
});
+ describe('handleSaveUser', () => {
+ it('calls API and refreshes list when new mapping', async () => {
+ const initializeRoleMappingsSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'initializeRoleMappings'
+ );
+ const setUserCreatedSpy = jest.spyOn(RoleMappingsLogic.actions, 'setUserCreated');
+ const setSingleUserRoleMappingSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setSingleUserRoleMapping'
+ );
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+
+ http.post.mockReturnValue(Promise.resolve(mappingsServerProps));
+ RoleMappingsLogic.actions.handleSaveUser();
+
+ expect(http.post).toHaveBeenCalledWith(
+ '/api/workplace_search/org/single_user_role_mapping',
+ {
+ body: JSON.stringify({
+ roleMapping: {
+ groups: [defaultGroup.id],
+ roleType: 'admin',
+ allGroups: false,
+ },
+ elasticsearchUser: {
+ username: elasticsearchUsers[0].username,
+ email: elasticsearchUsers[0].email,
+ },
+ }),
+ }
+ );
+ await nextTick();
+
+ expect(initializeRoleMappingsSpy).toHaveBeenCalled();
+ expect(setUserCreatedSpy).toHaveBeenCalled();
+ expect(setSingleUserRoleMappingSpy).toHaveBeenCalled();
+ });
+
+ it('calls API and refreshes list when existing mapping', async () => {
+ const initializeRoleMappingsSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'initializeRoleMappings'
+ );
+ RoleMappingsLogic.actions.setSingleUserRoleMapping(wsSingleUserRoleMapping);
+ RoleMappingsLogic.actions.handleAllGroupsSelectionChange(true);
+
+ http.put.mockReturnValue(Promise.resolve(mappingsServerProps));
+ RoleMappingsLogic.actions.handleSaveUser();
+
+ expect(http.post).toHaveBeenCalledWith(
+ '/api/workplace_search/org/single_user_role_mapping',
+ {
+ body: JSON.stringify({
+ roleMapping: {
+ groups: [],
+ roleType: 'admin',
+ allGroups: true,
+ id: wsSingleUserRoleMapping.roleMapping.id,
+ },
+ elasticsearchUser: {
+ username: '',
+ email: '',
+ },
+ }),
+ }
+ );
+ await nextTick();
+
+ expect(initializeRoleMappingsSpy).toHaveBeenCalled();
+ });
+
+ it('handles error', async () => {
+ const setRoleMappingErrorsSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setRoleMappingErrors'
+ );
+
+ http.post.mockReturnValue(
+ Promise.reject({
+ body: {
+ attributes: {
+ errors: ['this is an error'],
+ },
+ },
+ })
+ );
+ RoleMappingsLogic.actions.handleSaveUser();
+ await nextTick();
+
+ expect(setRoleMappingErrorsSpy).toHaveBeenCalledWith(['this is an error']);
+ });
+ });
+
describe('handleDeleteMapping', () => {
const roleMappingId = 'r1';
@@ -410,5 +627,52 @@ describe('RoleMappingsLogic', () => {
expect(flashAPIErrors).toHaveBeenCalledWith('this is an error');
});
});
+
+ describe('handleUsernameSelectChange', () => {
+ it('sets elasticsearchUser when match found', () => {
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+ const setElasticsearchUserSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setElasticsearchUser'
+ );
+ RoleMappingsLogic.actions.handleUsernameSelectChange(elasticsearchUsers[0].username);
+
+ expect(setElasticsearchUserSpy).toHaveBeenCalledWith(elasticsearchUsers[0]);
+ });
+
+ it('does not set elasticsearchUser when no match found', () => {
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+ const setElasticsearchUserSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setElasticsearchUser'
+ );
+ RoleMappingsLogic.actions.handleUsernameSelectChange('bogus');
+
+ expect(setElasticsearchUserSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('setUserExistingRadioValue', () => {
+ it('handles existing user', () => {
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+ const setElasticsearchUserSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setElasticsearchUser'
+ );
+ RoleMappingsLogic.actions.setUserExistingRadioValue(true);
+
+ expect(setElasticsearchUserSpy).toHaveBeenCalledWith(elasticsearchUsers[0]);
+ });
+
+ it('handles new user', () => {
+ const setElasticsearchUserSpy = jest.spyOn(
+ RoleMappingsLogic.actions,
+ 'setElasticsearchUser'
+ );
+ RoleMappingsLogic.actions.setUserExistingRadioValue(false);
+
+ expect(setElasticsearchUserSpy).toHaveBeenCalledWith(emptyUser);
+ });
+ });
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts
index 76b41b2f383eb..7f26c8738786c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts
@@ -16,7 +16,7 @@ import {
} from '../../../shared/flash_messages';
import { HttpLogic } from '../../../shared/http';
import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants';
-import { AttributeName } from '../../../shared/types';
+import { AttributeName, SingleUserRoleMapping, ElasticsearchUser } from '../../../shared/types';
import { RoleGroup, WSRoleMapping, Role } from '../../types';
import {
@@ -26,19 +26,24 @@ import {
DEFAULT_GROUP_NAME,
} from './constants';
+type UserMapping = SingleUserRoleMapping;
+
interface RoleMappingsServerDetails {
roleMappings: WSRoleMapping[];
attributes: string[];
authProviders: string[];
availableGroups: RoleGroup[];
+ elasticsearchUsers: ElasticsearchUser[];
elasticsearchRoles: string[];
multipleAuthProvidersConfig: boolean;
+ singleUserRoleMappings: UserMapping[];
}
const getFirstAttributeName = (roleMapping: WSRoleMapping): AttributeName =>
Object.entries(roleMapping.rules)[0][0] as AttributeName;
const getFirstAttributeValue = (roleMapping: WSRoleMapping): string =>
Object.entries(roleMapping.rules)[0][1] as string;
+const emptyUser = { username: '', email: '' } as ElasticsearchUser;
interface RoleMappingsActions {
handleAllGroupsSelectionChange(selected: boolean): { selected: boolean };
@@ -51,21 +56,35 @@ interface RoleMappingsActions {
handleDeleteMapping(roleMappingId: string): { roleMappingId: string };
handleGroupSelectionChange(groupIds: string[]): { groupIds: string[] };
handleRoleChange(roleType: Role): { roleType: Role };
+ handleUsernameSelectChange(username: string): { username: string };
handleSaveMapping(): void;
+ handleSaveUser(): void;
initializeRoleMapping(roleMappingId?: string): { roleMappingId?: string };
+ initializeSingleUserRoleMapping(roleMappingId?: string): { roleMappingId?: string };
initializeRoleMappings(): void;
resetState(): void;
setRoleMapping(roleMapping: WSRoleMapping): { roleMapping: WSRoleMapping };
+ setSingleUserRoleMapping(data?: UserMapping): { singleUserRoleMapping: UserMapping };
setRoleMappings({
roleMappings,
}: {
roleMappings: WSRoleMapping[];
}): { roleMappings: WSRoleMapping[] };
setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails;
+ setElasticsearchUser(
+ elasticsearchUser?: ElasticsearchUser
+ ): { elasticsearchUser: ElasticsearchUser };
+ setDefaultGroup(availableGroups: RoleGroup[]): { availableGroups: RoleGroup[] };
openRoleMappingFlyout(): void;
- closeRoleMappingFlyout(): void;
+ openSingleUserRoleMappingFlyout(): void;
+ closeUsersAndRolesFlyout(): void;
setRoleMappingErrors(errors: string[]): { errors: string[] };
enableRoleBasedAccess(): void;
+ setUserExistingRadioValue(userFormUserIsExisting: boolean): { userFormUserIsExisting: boolean };
+ setElasticsearchUsernameValue(username: string): { username: string };
+ setElasticsearchEmailValue(email: string): { email: string };
+ setUserCreated(): void;
+ setUserFormIsNewUser(userFormIsNewUser: boolean): { userFormIsNewUser: boolean };
}
interface RoleMappingsValues {
@@ -77,26 +96,37 @@ interface RoleMappingsValues {
availableGroups: RoleGroup[];
dataLoading: boolean;
elasticsearchRoles: string[];
+ elasticsearchUsers: ElasticsearchUser[];
+ elasticsearchUser: ElasticsearchUser;
multipleAuthProvidersConfig: boolean;
roleMapping: WSRoleMapping | null;
roleMappings: WSRoleMapping[];
+ singleUserRoleMapping: UserMapping | null;
+ singleUserRoleMappings: UserMapping[];
roleType: Role;
selectedAuthProviders: string[];
selectedGroups: Set;
roleMappingFlyoutOpen: boolean;
+ singleUserRoleMappingFlyoutOpen: boolean;
selectedOptions: EuiComboBoxOptionOption[];
roleMappingErrors: string[];
+ userFormUserIsExisting: boolean;
+ userCreated: boolean;
+ userFormIsNewUser: boolean;
}
export const RoleMappingsLogic = kea>({
- path: ['enterprise_search', 'workplace_search', 'role_mappings'],
+ path: ['enterprise_search', 'workplace_search', 'users_and_roles'],
actions: {
setRoleMappingsData: (data: RoleMappingsServerDetails) => data,
setRoleMapping: (roleMapping: WSRoleMapping) => ({ roleMapping }),
+ setElasticsearchUser: (elasticsearchUser: ElasticsearchUser) => ({ elasticsearchUser }),
+ setSingleUserRoleMapping: (singleUserRoleMapping: UserMapping) => ({ singleUserRoleMapping }),
setRoleMappings: ({ roleMappings }: { roleMappings: WSRoleMapping[] }) => ({ roleMappings }),
setRoleMappingErrors: (errors: string[]) => ({ errors }),
handleAuthProviderChange: (value: string[]) => ({ value }),
handleRoleChange: (roleType: Role) => ({ roleType }),
+ handleUsernameSelectChange: (username: string) => ({ username }),
handleGroupSelectionChange: (groupIds: string[]) => ({ groupIds }),
handleAttributeSelectorChange: (value: string, firstElasticsearchRole: string) => ({
value,
@@ -105,13 +135,22 @@ export const RoleMappingsLogic = kea ({ value }),
handleAllGroupsSelectionChange: (selected: boolean) => ({ selected }),
enableRoleBasedAccess: true,
+ openSingleUserRoleMappingFlyout: true,
+ setUserExistingRadioValue: (userFormUserIsExisting: boolean) => ({ userFormUserIsExisting }),
resetState: true,
initializeRoleMappings: true,
+ initializeSingleUserRoleMapping: (roleMappingId?: string) => ({ roleMappingId }),
initializeRoleMapping: (roleMappingId?: string) => ({ roleMappingId }),
handleDeleteMapping: (roleMappingId: string) => ({ roleMappingId }),
handleSaveMapping: true,
+ handleSaveUser: true,
+ setDefaultGroup: (availableGroups: RoleGroup[]) => ({ availableGroups }),
openRoleMappingFlyout: true,
- closeRoleMappingFlyout: false,
+ closeUsersAndRolesFlyout: false,
+ setElasticsearchUsernameValue: (username: string) => ({ username }),
+ setElasticsearchEmailValue: (email: string) => ({ email }),
+ setUserCreated: true,
+ setUserFormIsNewUser: (userFormIsNewUser: boolean) => ({ userFormIsNewUser }),
},
reducers: {
dataLoading: [
@@ -131,6 +170,13 @@ export const RoleMappingsLogic = kea [],
},
],
+ singleUserRoleMappings: [
+ [],
+ {
+ setRoleMappingsData: (_, { singleUserRoleMappings }) => singleUserRoleMappings,
+ resetState: () => [],
+ },
+ ],
multipleAuthProvidersConfig: [
false,
{
@@ -154,6 +200,13 @@ export const RoleMappingsLogic = kea elasticsearchRoles,
+ closeUsersAndRolesFlyout: () => [ANY_AUTH_PROVIDER],
+ },
+ ],
+ elasticsearchUsers: [
+ [],
+ {
+ setRoleMappingsData: (_, { elasticsearchUsers }) => elasticsearchUsers,
},
],
roleMapping: [
@@ -161,7 +214,14 @@ export const RoleMappingsLogic = kea roleMapping,
resetState: () => null,
- closeRoleMappingFlyout: () => null,
+ closeUsersAndRolesFlyout: () => null,
+ },
+ ],
+ singleUserRoleMapping: [
+ null,
+ {
+ setSingleUserRoleMapping: (_, { singleUserRoleMapping }) => singleUserRoleMapping || null,
+ closeUsersAndRolesFlyout: () => null,
},
],
roleType: [
@@ -176,6 +236,7 @@ export const RoleMappingsLogic = kea roleMapping.allGroups,
handleAllGroupsSelectionChange: (_, { selected }) => selected,
+ closeUsersAndRolesFlyout: () => false,
},
],
attributeValue: [
@@ -186,7 +247,7 @@ export const RoleMappingsLogic = kea value,
resetState: () => '',
- closeRoleMappingFlyout: () => '',
+ closeUsersAndRolesFlyout: () => '',
},
],
attributeName: [
@@ -195,7 +256,7 @@ export const RoleMappingsLogic = kea getFirstAttributeName(roleMapping),
handleAttributeSelectorChange: (_, { value }) => value,
resetState: () => 'username',
- closeRoleMappingFlyout: () => 'username',
+ closeUsersAndRolesFlyout: () => 'username',
},
],
selectedGroups: [
@@ -207,6 +268,12 @@ export const RoleMappingsLogic = kea group.name === DEFAULT_GROUP_NAME)
.map((group) => group.id)
),
+ setDefaultGroup: (_, { availableGroups }) =>
+ new Set(
+ availableGroups
+ .filter((group) => group.name === DEFAULT_GROUP_NAME)
+ .map((group) => group.id)
+ ),
setRoleMapping: (_, { roleMapping }) =>
new Set(roleMapping.groups.map((group: RoleGroup) => group.id)),
handleGroupSelectionChange: (_, { groupIds }) => {
@@ -215,6 +282,7 @@ export const RoleMappingsLogic = kea new Set(),
},
],
availableAuthProviders: [
@@ -244,17 +312,61 @@ export const RoleMappingsLogic = kea true,
- closeRoleMappingFlyout: () => false,
+ closeUsersAndRolesFlyout: () => false,
initializeRoleMappings: () => false,
initializeRoleMapping: () => true,
},
],
+ singleUserRoleMappingFlyoutOpen: [
+ false,
+ {
+ openSingleUserRoleMappingFlyout: () => true,
+ closeUsersAndRolesFlyout: () => false,
+ initializeSingleUserRoleMapping: () => true,
+ },
+ ],
roleMappingErrors: [
[],
{
setRoleMappingErrors: (_, { errors }) => errors,
handleSaveMapping: () => [],
- closeRoleMappingFlyout: () => [],
+ closeUsersAndRolesFlyout: () => [],
+ },
+ ],
+ userFormUserIsExisting: [
+ true,
+ {
+ setUserExistingRadioValue: (_, { userFormUserIsExisting }) => userFormUserIsExisting,
+ closeUsersAndRolesFlyout: () => true,
+ },
+ ],
+ elasticsearchUser: [
+ emptyUser,
+ {
+ setRoleMappingsData: (_, { elasticsearchUsers }) => elasticsearchUsers[0] || emptyUser,
+ setElasticsearchUser: (_, { elasticsearchUser }) => elasticsearchUser || emptyUser,
+ setElasticsearchUsernameValue: (state, { username }) => ({
+ ...state,
+ username,
+ }),
+ setElasticsearchEmailValue: (state, { email }) => ({
+ ...state,
+ email,
+ }),
+ closeUsersAndRolesFlyout: () => emptyUser,
+ },
+ ],
+ userCreated: [
+ false,
+ {
+ setUserCreated: () => true,
+ closeUsersAndRolesFlyout: () => false,
+ },
+ ],
+ userFormIsNewUser: [
+ true,
+ {
+ setUserFormIsNewUser: (_, { userFormIsNewUser }) => userFormIsNewUser,
},
],
},
@@ -296,6 +408,18 @@ export const RoleMappingsLogic = kea id === roleMappingId);
if (roleMapping) actions.setRoleMapping(roleMapping);
},
+ initializeSingleUserRoleMapping: ({ roleMappingId }) => {
+ const singleUserRoleMapping = values.singleUserRoleMappings.find(
+ ({ roleMapping }) => roleMapping.id === roleMappingId
+ );
+
+ if (singleUserRoleMapping) {
+ actions.setElasticsearchUser(singleUserRoleMapping.elasticsearchUser);
+ actions.setRoleMapping(singleUserRoleMapping.roleMapping);
+ }
+ actions.setSingleUserRoleMapping(singleUserRoleMapping);
+ actions.setUserFormIsNewUser(!singleUserRoleMapping);
+ },
handleDeleteMapping: async ({ roleMappingId }) => {
const { http } = HttpLogic.values;
const route = `/api/workplace_search/org/role_mappings/${roleMappingId}`;
@@ -349,11 +473,59 @@ export const RoleMappingsLogic = kea {
clearFlashMessages();
},
- closeRoleMappingFlyout: () => {
+ handleSaveUser: async () => {
+ const { http } = HttpLogic.values;
+ const {
+ roleType,
+ singleUserRoleMapping,
+ includeInAllGroups,
+ selectedGroups,
+ elasticsearchUser: { email, username },
+ } = values;
+
+ const body = JSON.stringify({
+ roleMapping: {
+ groups: includeInAllGroups ? [] : Array.from(selectedGroups),
+ roleType,
+ allGroups: includeInAllGroups,
+ id: singleUserRoleMapping?.roleMapping?.id,
+ },
+ elasticsearchUser: {
+ username,
+ email,
+ },
+ });
+
+ try {
+ const response = await http.post('/api/workplace_search/org/single_user_role_mapping', {
+ body,
+ });
+ actions.setSingleUserRoleMapping(response);
+ actions.setUserCreated();
+ actions.initializeRoleMappings();
+ } catch (e) {
+ actions.setRoleMappingErrors(e?.body?.attributes?.errors);
+ }
+ },
+ closeUsersAndRolesFlyout: () => {
clearFlashMessages();
+ const firstUser = values.elasticsearchUsers[0];
+ actions.setElasticsearchUser(firstUser);
+ actions.setDefaultGroup(values.availableGroups);
},
openRoleMappingFlyout: () => {
clearFlashMessages();
},
+ openSingleUserRoleMappingFlyout: () => {
+ clearFlashMessages();
+ },
+ setUserExistingRadioValue: ({ userFormUserIsExisting }) => {
+ const firstUser = values.elasticsearchUsers[0];
+ actions.setElasticsearchUser(userFormUserIsExisting ? firstUser : emptyUser);
+ },
+ handleUsernameSelectChange: ({ username }) => {
+ const user = values.elasticsearchUsers.find((u) => u.username === username);
+ if (user) actions.setElasticsearchUser(user);
+ },
}),
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.test.tsx
new file mode 100644
index 0000000000000..32ee1a7f22875
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.test.tsx
@@ -0,0 +1,123 @@
+/*
+ * 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 '../../../__mocks__/react_router';
+import '../../../__mocks__/shallow_useeffect.mock';
+import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic';
+import { groups } from '../../__mocks__/groups.mock';
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { UserFlyout, UserAddedInfo, UserInvitationCallout } from '../../../shared/role_mapping';
+import { elasticsearchUsers } from '../../../shared/role_mapping/__mocks__/elasticsearch_users';
+import { wsSingleUserRoleMapping } from '../../../shared/role_mapping/__mocks__/roles';
+
+import { GroupAssignmentSelector } from './group_assignment_selector';
+import { User } from './user';
+
+describe('User', () => {
+ const handleSaveUser = jest.fn();
+ const closeUsersAndRolesFlyout = jest.fn();
+ const setUserExistingRadioValue = jest.fn();
+ const setElasticsearchUsernameValue = jest.fn();
+ const setElasticsearchEmailValue = jest.fn();
+ const handleRoleChange = jest.fn();
+ const handleUsernameSelectChange = jest.fn();
+
+ const mockValues = {
+ availableGroups: [],
+ singleUserRoleMapping: null,
+ userFormUserIsExisting: false,
+ elasticsearchUsers: [],
+ elasticsearchUser: {},
+ roleType: 'admin',
+ roleMappingErrors: [],
+ userCreated: false,
+ userFormIsNewUser: false,
+ };
+
+ beforeEach(() => {
+ setMockActions({
+ handleSaveUser,
+ closeUsersAndRolesFlyout,
+ setUserExistingRadioValue,
+ setElasticsearchUsernameValue,
+ setElasticsearchEmailValue,
+ handleRoleChange,
+ handleUsernameSelectChange,
+ });
+
+ setMockValues(mockValues);
+ });
+
+ it('renders', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(UserFlyout)).toHaveLength(1);
+ });
+
+ it('renders group assignment selector when groups present', () => {
+ setMockValues({ ...mockValues, availableGroups: groups });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(GroupAssignmentSelector)).toHaveLength(1);
+ });
+
+ it('renders userInvitationCallout', () => {
+ setMockValues({
+ ...mockValues,
+ singleUserRoleMapping: wsSingleUserRoleMapping,
+ });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(UserInvitationCallout)).toHaveLength(1);
+ });
+
+ it('renders user added info when user created', () => {
+ setMockValues({
+ ...mockValues,
+ singleUserRoleMapping: wsSingleUserRoleMapping,
+ userCreated: true,
+ });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(UserAddedInfo)).toHaveLength(1);
+ });
+
+ it('disables form when username value not present', () => {
+ setMockValues({
+ ...mockValues,
+ singleUserRoleMapping: wsSingleUserRoleMapping,
+ elasticsearchUsers,
+ elasticsearchUser: {
+ username: null,
+ email: 'email@user.com',
+ },
+ });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(UserFlyout).prop('disabled')).toEqual(true);
+ });
+
+ it('enables form when userFormUserIsExisting', () => {
+ setMockValues({
+ ...mockValues,
+ userFormUserIsExisting: true.valueOf,
+ singleUserRoleMapping: wsSingleUserRoleMapping,
+ elasticsearchUsers,
+ elasticsearchUser: {
+ username: null,
+ email: 'email@user.com',
+ },
+ });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(UserFlyout).prop('disabled')).toEqual(false);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx
new file mode 100644
index 0000000000000..bfb32ee31c121
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx
@@ -0,0 +1,103 @@
+/*
+ * 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 { useActions, useValues } from 'kea';
+
+import { EuiForm } from '@elastic/eui';
+
+import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url';
+import {
+ UserFlyout,
+ UserSelector,
+ UserAddedInfo,
+ UserInvitationCallout,
+} from '../../../shared/role_mapping';
+import { Role } from '../../types';
+
+import { GroupAssignmentSelector } from './group_assignment_selector';
+import { RoleMappingsLogic } from './role_mappings_logic';
+
+const roleTypes = (['admin', 'user'] as unknown) as Role[];
+
+export const User: React.FC = () => {
+ const {
+ handleSaveUser,
+ closeUsersAndRolesFlyout,
+ setUserExistingRadioValue,
+ setElasticsearchUsernameValue,
+ setElasticsearchEmailValue,
+ handleRoleChange,
+ handleUsernameSelectChange,
+ } = useActions(RoleMappingsLogic);
+
+ const {
+ availableGroups,
+ singleUserRoleMapping,
+ userFormUserIsExisting,
+ elasticsearchUsers,
+ elasticsearchUser,
+ roleType,
+ roleMappingErrors,
+ userCreated,
+ userFormIsNewUser,
+ } = useValues(RoleMappingsLogic);
+
+ const showGroupAssignmentSelector = availableGroups.length > 0;
+ const hasAvailableUsers = elasticsearchUsers.length > 0;
+ const flyoutDisabled =
+ (!userFormUserIsExisting || !hasAvailableUsers) && !elasticsearchUser.username;
+
+ const userAddedInfo = singleUserRoleMapping && (
+
+ );
+
+ const userInvitationCallout = singleUserRoleMapping?.invitation && (
+
+ );
+
+ const createUserForm = (
+ 0} error={roleMappingErrors}>
+
+ {showGroupAssignmentSelector && }
+
+ );
+
+ return (
+
+ {userCreated ? userAddedInfo : createUserForm}
+ {userInvitationCallout}
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts
index 7d9f08627516b..dfb9765f834b6 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts
@@ -11,6 +11,7 @@ import {
registerEnableRoleMappingsRoute,
registerRoleMappingsRoute,
registerRoleMappingRoute,
+ registerUserRoute,
} from './role_mappings';
const roleMappingBaseSchema = {
@@ -160,4 +161,52 @@ describe('role mappings routes', () => {
});
});
});
+
+ describe('POST /api/app_search/single_user_role_mapping', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'post',
+ path: '/api/app_search/single_user_role_mapping',
+ });
+
+ registerUserRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ describe('validates', () => {
+ it('correctly', () => {
+ const request = {
+ body: {
+ roleMapping: {
+ engines: ['foo', 'bar'],
+ roleType: 'admin',
+ accessAllEngines: true,
+ id: '123asf',
+ },
+ elasticsearchUser: {
+ username: 'user2@elastic.co',
+ email: 'user2',
+ },
+ },
+ };
+ mockRouter.shouldValidate(request);
+ });
+
+ it('missing required fields', () => {
+ const request = { body: {} };
+ mockRouter.shouldThrow(request);
+ });
+ });
+
+ it('creates a request handler', () => {
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/as/role_mappings/upsert_single_user_role_mapping',
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts
index da620be2ea950..d90a005cb2532 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts
@@ -93,8 +93,34 @@ export function registerRoleMappingRoute({
);
}
+export function registerUserRoute({ router, enterpriseSearchRequestHandler }: RouteDependencies) {
+ router.post(
+ {
+ path: '/api/app_search/single_user_role_mapping',
+ validate: {
+ body: schema.object({
+ roleMapping: schema.object({
+ engines: schema.arrayOf(schema.string()),
+ roleType: schema.string(),
+ accessAllEngines: schema.boolean(),
+ id: schema.maybe(schema.string()),
+ }),
+ elasticsearchUser: schema.object({
+ username: schema.string(),
+ email: schema.string(),
+ }),
+ }),
+ },
+ },
+ enterpriseSearchRequestHandler.createRequest({
+ path: '/as/role_mappings/upsert_single_user_role_mapping',
+ })
+ );
+}
+
export const registerRoleMappingsRoutes = (dependencies: RouteDependencies) => {
registerEnableRoleMappingsRoute(dependencies);
registerRoleMappingsRoute(dependencies);
registerRoleMappingRoute(dependencies);
+ registerUserRoute(dependencies);
};
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.test.ts
index aa0e9983166c0..ef8f1bd63f5d3 100644
--- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.test.ts
@@ -11,6 +11,7 @@ import {
registerOrgEnableRoleMappingsRoute,
registerOrgRoleMappingsRoute,
registerOrgRoleMappingRoute,
+ registerOrgUserRoute,
} from './role_mappings';
describe('role mappings routes', () => {
@@ -128,4 +129,52 @@ describe('role mappings routes', () => {
});
});
});
+
+ describe('POST /api/workplace_search/org/single_user_role_mapping', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'post',
+ path: '/api/workplace_search/org/single_user_role_mapping',
+ });
+
+ registerOrgUserRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ describe('validates', () => {
+ it('correctly', () => {
+ const request = {
+ body: {
+ roleMapping: {
+ groups: ['foo', 'bar'],
+ roleType: 'admin',
+ allGroups: true,
+ id: '123asf',
+ },
+ elasticsearchUser: {
+ username: 'user2@elastic.co',
+ email: 'user2',
+ },
+ },
+ };
+ mockRouter.shouldValidate(request);
+ });
+
+ it('missing required fields', () => {
+ const request = { body: {} };
+ mockRouter.shouldThrow(request);
+ });
+ });
+
+ it('creates a request handler', () => {
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/role_mappings/upsert_single_user_role_mapping',
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts
index cea7bcb311ce8..e6f4919ed2a2f 100644
--- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts
@@ -93,8 +93,37 @@ export function registerOrgRoleMappingRoute({
);
}
+export function registerOrgUserRoute({
+ router,
+ enterpriseSearchRequestHandler,
+}: RouteDependencies) {
+ router.post(
+ {
+ path: '/api/workplace_search/org/single_user_role_mapping',
+ validate: {
+ body: schema.object({
+ roleMapping: schema.object({
+ groups: schema.arrayOf(schema.string()),
+ roleType: schema.string(),
+ allGroups: schema.boolean(),
+ id: schema.maybe(schema.string()),
+ }),
+ elasticsearchUser: schema.object({
+ username: schema.string(),
+ email: schema.string(),
+ }),
+ }),
+ },
+ },
+ enterpriseSearchRequestHandler.createRequest({
+ path: '/ws/org/role_mappings/upsert_single_user_role_mapping',
+ })
+ );
+}
+
export const registerRoleMappingsRoutes = (dependencies: RouteDependencies) => {
registerOrgEnableRoleMappingsRoute(dependencies);
registerOrgRoleMappingsRoute(dependencies);
registerOrgRoleMappingRoute(dependencies);
+ registerOrgUserRoute(dependencies);
};
From 9b56549c6c26a1f86c44e709b12c4e295aaebda3 Mon Sep 17 00:00:00 2001
From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com>
Date: Thu, 24 Jun 2021 09:05:26 -0400
Subject: [PATCH 28/86] [Cases] Including owner when patching a comment Closes
#102732 (#103020)
* Including owner when patching a comment
* Fixing tests
---
.../cases/public/containers/api.test.tsx | 31 +++++----
x-pack/plugins/cases/public/containers/api.ts | 26 ++++---
.../containers/use_update_comment.test.tsx | 67 +++++++++----------
.../public/containers/use_update_comment.tsx | 13 ++--
4 files changed, 77 insertions(+), 60 deletions(-)
diff --git a/x-pack/plugins/cases/public/containers/api.test.tsx b/x-pack/plugins/cases/public/containers/api.test.tsx
index abdee387a2c42..30a76e28e7485 100644
--- a/x-pack/plugins/cases/public/containers/api.test.tsx
+++ b/x-pack/plugins/cases/public/containers/api.test.tsx
@@ -363,13 +363,14 @@ describe('Case Configuration API', () => {
});
test('check url, method, signal', async () => {
- await patchComment(
- basicCase.id,
- basicCase.comments[0].id,
- 'updated comment',
- basicCase.comments[0].version,
- abortCtrl.signal
- );
+ await patchComment({
+ caseId: basicCase.id,
+ commentId: basicCase.comments[0].id,
+ commentUpdate: 'updated comment',
+ version: basicCase.comments[0].version,
+ signal: abortCtrl.signal,
+ owner: SECURITY_SOLUTION_OWNER,
+ });
expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/${basicCase.id}/comments`, {
method: 'PATCH',
body: JSON.stringify({
@@ -377,19 +378,21 @@ describe('Case Configuration API', () => {
type: CommentType.user,
id: basicCase.comments[0].id,
version: basicCase.comments[0].version,
+ owner: SECURITY_SOLUTION_OWNER,
}),
signal: abortCtrl.signal,
});
});
test('happy path', async () => {
- const resp = await patchComment(
- basicCase.id,
- basicCase.comments[0].id,
- 'updated comment',
- basicCase.comments[0].version,
- abortCtrl.signal
- );
+ const resp = await patchComment({
+ caseId: basicCase.id,
+ commentId: basicCase.comments[0].id,
+ commentUpdate: 'updated comment',
+ version: basicCase.comments[0].version,
+ signal: abortCtrl.signal,
+ owner: SECURITY_SOLUTION_OWNER,
+ });
expect(resp).toEqual(basicCase);
});
});
diff --git a/x-pack/plugins/cases/public/containers/api.ts b/x-pack/plugins/cases/public/containers/api.ts
index 1a2a92850a4ad..b144a874cfc53 100644
--- a/x-pack/plugins/cases/public/containers/api.ts
+++ b/x-pack/plugins/cases/public/containers/api.ts
@@ -283,14 +283,23 @@ export const postComment = async (
return convertToCamelCase(decodeCaseResponse(response));
};
-export const patchComment = async (
- caseId: string,
- commentId: string,
- commentUpdate: string,
- version: string,
- signal: AbortSignal,
- subCaseId?: string
-): Promise => {
+export const patchComment = async ({
+ caseId,
+ commentId,
+ commentUpdate,
+ version,
+ signal,
+ owner,
+ subCaseId,
+}: {
+ caseId: string;
+ commentId: string;
+ commentUpdate: string;
+ version: string;
+ signal: AbortSignal;
+ owner: string;
+ subCaseId?: string;
+}): Promise => {
const response = await KibanaServices.get().http.fetch(getCaseCommentsUrl(caseId), {
method: 'PATCH',
body: JSON.stringify({
@@ -298,6 +307,7 @@ export const patchComment = async (
type: CommentType.user,
id: commentId,
version,
+ owner,
}),
...(subCaseId ? { query: { subCaseId } } : {}),
signal,
diff --git a/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx b/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx
index b936eb126f0d4..14cc4dfab3599 100644
--- a/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx
@@ -5,10 +5,13 @@
* 2.0.
*/
+import React from 'react';
import { renderHook, act } from '@testing-library/react-hooks';
import { useUpdateComment, UseUpdateComment } from './use_update_comment';
import { basicCase, basicCaseCommentPatch, basicSubCaseId } from './mock';
import * as api from './api';
+import { TestProviders } from '../common/mock';
+import { SECURITY_SOLUTION_OWNER } from '../../common';
jest.mock('./api');
jest.mock('../common/lib/kibana');
@@ -25,6 +28,12 @@ describe('useUpdateComment', () => {
updateCase,
version: basicCase.comments[0].version,
};
+
+ const renderHookUseUpdateComment = () =>
+ renderHook(() => useUpdateComment(), {
+ wrapper: ({ children }) => {children} ,
+ });
+
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
@@ -32,9 +41,7 @@ describe('useUpdateComment', () => {
it('init', async () => {
await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useUpdateComment()
- );
+ const { result, waitForNextUpdate } = renderHookUseUpdateComment();
await waitForNextUpdate();
expect(result.current).toEqual({
isLoadingIds: [],
@@ -48,21 +55,20 @@ describe('useUpdateComment', () => {
const spyOnPatchComment = jest.spyOn(api, 'patchComment');
await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useUpdateComment()
- );
+ const { result, waitForNextUpdate } = renderHookUseUpdateComment();
await waitForNextUpdate();
result.current.patchComment(sampleUpdate);
await waitForNextUpdate();
- expect(spyOnPatchComment).toBeCalledWith(
- basicCase.id,
- basicCase.comments[0].id,
- 'updated comment',
- basicCase.comments[0].version,
- abortCtrl.signal,
- undefined
- );
+ expect(spyOnPatchComment).toBeCalledWith({
+ caseId: basicCase.id,
+ commentId: basicCase.comments[0].id,
+ commentUpdate: 'updated comment',
+ version: basicCase.comments[0].version,
+ signal: abortCtrl.signal,
+ owner: SECURITY_SOLUTION_OWNER,
+ subCaseId: undefined,
+ });
});
});
@@ -70,29 +76,26 @@ describe('useUpdateComment', () => {
const spyOnPatchComment = jest.spyOn(api, 'patchComment');
await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useUpdateComment()
- );
+ const { result, waitForNextUpdate } = renderHookUseUpdateComment();
await waitForNextUpdate();
result.current.patchComment({ ...sampleUpdate, subCaseId: basicSubCaseId });
await waitForNextUpdate();
- expect(spyOnPatchComment).toBeCalledWith(
- basicCase.id,
- basicCase.comments[0].id,
- 'updated comment',
- basicCase.comments[0].version,
- abortCtrl.signal,
- basicSubCaseId
- );
+ expect(spyOnPatchComment).toBeCalledWith({
+ caseId: basicCase.id,
+ commentId: basicCase.comments[0].id,
+ commentUpdate: 'updated comment',
+ version: basicCase.comments[0].version,
+ signal: abortCtrl.signal,
+ owner: SECURITY_SOLUTION_OWNER,
+ subCaseId: basicSubCaseId,
+ });
});
});
it('patch comment', async () => {
await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useUpdateComment()
- );
+ const { result, waitForNextUpdate } = renderHookUseUpdateComment();
await waitForNextUpdate();
result.current.patchComment(sampleUpdate);
await waitForNextUpdate();
@@ -108,9 +111,7 @@ describe('useUpdateComment', () => {
it('set isLoading to true when posting case', async () => {
await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useUpdateComment()
- );
+ const { result, waitForNextUpdate } = renderHookUseUpdateComment();
await waitForNextUpdate();
result.current.patchComment(sampleUpdate);
@@ -125,9 +126,7 @@ describe('useUpdateComment', () => {
});
await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useUpdateComment()
- );
+ const { result, waitForNextUpdate } = renderHookUseUpdateComment();
await waitForNextUpdate();
result.current.patchComment(sampleUpdate);
diff --git a/x-pack/plugins/cases/public/containers/use_update_comment.tsx b/x-pack/plugins/cases/public/containers/use_update_comment.tsx
index 478d7ebf1fc32..3c307d86ac7bc 100644
--- a/x-pack/plugins/cases/public/containers/use_update_comment.tsx
+++ b/x-pack/plugins/cases/public/containers/use_update_comment.tsx
@@ -7,6 +7,7 @@
import { useReducer, useCallback, useRef, useEffect } from 'react';
import { useToasts } from '../common/lib/kibana';
+import { useOwnerContext } from '../components/owner_context/use_owner_context';
import { patchComment } from './api';
import * as i18n from './translations';
import { Case } from './types';
@@ -72,6 +73,9 @@ export const useUpdateComment = (): UseUpdateComment => {
const toasts = useToasts();
const isCancelledRef = useRef(false);
const abortCtrlRef = useRef(new AbortController());
+ // this hook guarantees that there will be at least one value in the owner array, we'll
+ // just use the first entry just in case there are more than one entry
+ const owner = useOwnerContext()[0];
const dispatchUpdateComment = useCallback(
async ({
@@ -89,14 +93,15 @@ export const useUpdateComment = (): UseUpdateComment => {
abortCtrlRef.current = new AbortController();
dispatch({ type: 'FETCH_INIT', payload: commentId });
- const response = await patchComment(
+ const response = await patchComment({
caseId,
commentId,
commentUpdate,
version,
- abortCtrlRef.current.signal,
- subCaseId
- );
+ signal: abortCtrlRef.current.signal,
+ subCaseId,
+ owner,
+ });
if (!isCancelledRef.current) {
updateCase(response);
From 1ef5a6aa05e9b4bd0fb96809b979877e7081f654 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?=
Date: Thu, 24 Jun 2021 15:06:07 +0200
Subject: [PATCH 29/86] [Fleet][Logs UI] Prevent double loading of entries in
` ` component. (#102980)
* Use better loading indicator for `useLogSource`
* Use clearer name for the loading entries flag
* Reuse query object if its value persists
---
.../public/components/log_stream/log_stream.tsx | 6 +++---
.../public/containers/logs/log_stream/index.ts | 13 ++++++++++---
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx b/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx
index 0087d559a42e6..ff9b749911c84 100644
--- a/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx
+++ b/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx
@@ -112,7 +112,7 @@ Read more at https://github.com/elastic/kibana/blob/master/src/plugins/kibana_re
const {
derivedIndexPattern,
- isLoadingSourceConfiguration,
+ isLoading: isLoadingSource,
loadSource,
sourceConfiguration,
} = useLogSource({
@@ -138,7 +138,7 @@ Read more at https://github.com/elastic/kibana/blob/master/src/plugins/kibana_re
hasMoreAfter,
hasMoreBefore,
isLoadingMore,
- isReloading,
+ isReloading: isLoadingEntries,
} = useLogStream({
sourceId,
startTimestamp,
@@ -198,7 +198,7 @@ Read more at https://github.com/elastic/kibana/blob/master/src/plugins/kibana_re
items={streamItems}
scale="medium"
wrap={true}
- isReloading={isLoadingSourceConfiguration || isReloading}
+ isReloading={isLoadingSource || isLoadingEntries}
isLoadingMore={isLoadingMore}
hasMoreBeforeStart={hasMoreBefore}
hasMoreAfterEnd={hasMoreAfter}
diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts
index 021aa8f79fe59..4cdeb678c432b 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts
@@ -5,8 +5,9 @@
* 2.0.
*/
+import { isEqual } from 'lodash';
import createContainer from 'constate';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import usePrevious from 'react-use/lib/usePrevious';
import useSetState from 'react-use/lib/useSetState';
import { esQuery } from '../../../../../../../src/plugins/data/public';
@@ -65,6 +66,12 @@ export function useLogStream({
const prevStartTimestamp = usePrevious(startTimestamp);
const prevEndTimestamp = usePrevious(endTimestamp);
+ const cachedQuery = useRef(query);
+
+ if (!isEqual(query, cachedQuery)) {
+ cachedQuery.current = query;
+ }
+
useEffect(() => {
if (prevStartTimestamp && prevStartTimestamp > startTimestamp) {
setState({ hasMoreBefore: true });
@@ -82,10 +89,10 @@ export function useLogStream({
sourceId,
startTimestamp,
endTimestamp,
- query,
+ query: cachedQuery.current,
columnOverrides: columns,
}),
- [columns, endTimestamp, query, sourceId, startTimestamp]
+ [columns, endTimestamp, cachedQuery, sourceId, startTimestamp]
);
const {
From 0a2042eed55100f46766faefd03aeab2dc3607c3 Mon Sep 17 00:00:00 2001
From: Tim Roes
Date: Thu, 24 Jun 2021 15:14:48 +0200
Subject: [PATCH 30/86] Prevent showing filter on unfilterable fields (#103241)
---
.../discover_grid/discover_grid_cell_actions.test.tsx | 9 ++++++++-
.../discover_grid/discover_grid_cell_actions.tsx | 2 +-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.test.tsx
index 965d3cb6a30c4..de3c55ad7a869 100644
--- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.test.tsx
+++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.test.tsx
@@ -9,14 +9,21 @@
import React from 'react';
import { mountWithIntl } from '@kbn/test/jest';
import { findTestSubject } from '@elastic/eui/lib/test';
-import { FilterInBtn, FilterOutBtn } from './discover_grid_cell_actions';
+import { FilterInBtn, FilterOutBtn, buildCellActions } from './discover_grid_cell_actions';
import { DiscoverGridContext } from './discover_grid_context';
import { indexPatternMock } from '../../../__mocks__/index_pattern';
import { esHits } from '../../../__mocks__/es_hits';
import { EuiButton } from '@elastic/eui';
+import { IndexPatternField } from 'src/plugins/data/common';
describe('Discover cell actions ', function () {
+ it('should not show cell actions for unfilterable fields', async () => {
+ expect(
+ buildCellActions({ name: 'foo', filterable: false } as IndexPatternField)
+ ).toBeUndefined();
+ });
+
it('triggers filter function when FilterInBtn is clicked', async () => {
const contextMock = {
expanded: undefined,
diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.tsx
index 4e9218f0881cd..ab80cd3e7b461 100644
--- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.tsx
+++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.tsx
@@ -79,7 +79,7 @@ export const FilterOutBtn = ({
};
export function buildCellActions(field: IndexPatternField) {
- if (!field.aggregatable && !field.searchable) {
+ if (!field.filterable) {
return undefined;
}
From 4e38dfee1430889667b5333ac25a00a8ef2ce89e Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Thu, 24 Jun 2021 14:15:44 +0100
Subject: [PATCH 31/86] skip flaky suite (#98240)
---
test/api_integration/apis/ui_counters/ui_counters.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/test/api_integration/apis/ui_counters/ui_counters.ts b/test/api_integration/apis/ui_counters/ui_counters.ts
index 2be6ea4341fb0..019dcfd621655 100644
--- a/test/api_integration/apis/ui_counters/ui_counters.ts
+++ b/test/api_integration/apis/ui_counters/ui_counters.ts
@@ -56,7 +56,8 @@ export default function ({ getService }: FtrProviderContext) {
return savedObject;
};
- describe('UI Counters API', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/98240
+ describe.skip('UI Counters API', () => {
const dayDate = moment().format('DDMMYYYY');
before(async () => await esArchiver.emptyKibanaIndex());
From 4266957a0df426cb88e9f8313e18d1f8c26b8652 Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Thu, 24 Jun 2021 15:20:28 +0200
Subject: [PATCH 32/86] fix filter input debouncing (#103087)
---
.../lens/public/indexpattern_datasource/query_input.tsx | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/query_input.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/query_input.tsx
index a67199a9d3432..1b418ee3b408f 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/query_input.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/query_input.tsx
@@ -7,6 +7,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
+import { isEqual } from 'lodash';
import { QueryStringInput, Query } from '../../../../../src/plugins/data/public';
import { useDebouncedValue } from '../shared_components';
@@ -36,7 +37,11 @@ export const QueryInput = ({
bubbleSubmitEvent={false}
indexPatterns={[indexPatternTitle]}
query={inputValue}
- onChange={handleInputChange}
+ onChange={(newQuery) => {
+ if (!isEqual(newQuery, inputValue)) {
+ handleInputChange(newQuery);
+ }
+ }}
onSubmit={() => {
if (inputValue.query) {
onSubmit();
From b1b182bdec007528722d2de5a12c2dde7a09c1d3 Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Thu, 24 Jun 2021 15:23:39 +0200
Subject: [PATCH 33/86] [Lens] Add new error case for mixed x axes (#102861)
---
.../xy_visualization/visualization.test.ts | 53 +++++++++++++++++++
.../public/xy_visualization/visualization.tsx | 36 +++++++++++--
2 files changed, 84 insertions(+), 5 deletions(-)
diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts
index f2840b6d3844b..dee0e5763dee4 100644
--- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts
@@ -872,6 +872,59 @@ describe('xy_visualization', () => {
},
]);
});
+
+ it('should return an error if string and date histogram xAccessors (multiple layers) are used together', () => {
+ // current incompatibility is only for date and numeric histograms as xAccessors
+ const datasourceLayers = {
+ first: mockDatasource.publicAPIMock,
+ second: createMockDatasource('testDatasource').publicAPIMock,
+ };
+ datasourceLayers.first.getOperationForColumnId = jest.fn((id: string) =>
+ id === 'a'
+ ? (({
+ dataType: 'date',
+ scale: 'interval',
+ } as unknown) as Operation)
+ : null
+ );
+ datasourceLayers.second.getOperationForColumnId = jest.fn((id: string) =>
+ id === 'e'
+ ? (({
+ dataType: 'string',
+ scale: 'ordinal',
+ } as unknown) as Operation)
+ : null
+ );
+ expect(
+ xyVisualization.getErrorMessages(
+ {
+ ...exampleState(),
+ layers: [
+ {
+ layerId: 'first',
+ seriesType: 'area',
+ splitAccessor: 'd',
+ xAccessor: 'a',
+ accessors: ['b'],
+ },
+ {
+ layerId: 'second',
+ seriesType: 'area',
+ splitAccessor: 'd',
+ xAccessor: 'e',
+ accessors: ['b'],
+ },
+ ],
+ },
+ datasourceLayers
+ )
+ ).toEqual([
+ {
+ shortMessage: 'Wrong data type for Horizontal axis.',
+ longMessage: 'Data type mismatch for the Horizontal axis, use a different function.',
+ },
+ ]);
+ });
});
describe('#getWarningMessages', () => {
diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx
index ad2c9fd713985..bd20ed300bf61 100644
--- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx
@@ -542,8 +542,15 @@ function checkXAccessorCompatibility(
datasourceLayers: Record
) {
const errors = [];
- const hasDateHistogramSet = state.layers.some(checkIntervalOperation('date', datasourceLayers));
- const hasNumberHistogram = state.layers.some(checkIntervalOperation('number', datasourceLayers));
+ const hasDateHistogramSet = state.layers.some(
+ checkScaleOperation('interval', 'date', datasourceLayers)
+ );
+ const hasNumberHistogram = state.layers.some(
+ checkScaleOperation('interval', 'number', datasourceLayers)
+ );
+ const hasOrdinalAxis = state.layers.some(
+ checkScaleOperation('ordinal', undefined, datasourceLayers)
+ );
if (state.layers.length > 1 && hasDateHistogramSet && hasNumberHistogram) {
errors.push({
shortMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXShort', {
@@ -560,11 +567,28 @@ function checkXAccessorCompatibility(
}),
});
}
+ if (state.layers.length > 1 && (hasDateHistogramSet || hasNumberHistogram) && hasOrdinalAxis) {
+ errors.push({
+ shortMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXShort', {
+ defaultMessage: `Wrong data type for {axis}.`,
+ values: {
+ axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }),
+ },
+ }),
+ longMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXOrdinalLong', {
+ defaultMessage: `Data type mismatch for the {axis}, use a different function.`,
+ values: {
+ axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }),
+ },
+ }),
+ });
+ }
return errors;
}
-function checkIntervalOperation(
- dataType: 'date' | 'number',
+function checkScaleOperation(
+ scaleType: 'ordinal' | 'interval' | 'ratio',
+ dataType: 'date' | 'number' | 'string' | undefined,
datasourceLayers: Record
) {
return (layer: XYLayerConfig) => {
@@ -573,6 +597,8 @@ function checkIntervalOperation(
return false;
}
const operation = datasourceAPI?.getOperationForColumnId(layer.xAccessor);
- return Boolean(operation?.dataType === dataType && operation.scale === 'interval');
+ return Boolean(
+ operation && (!dataType || operation.dataType === dataType) && operation.scale === scaleType
+ );
};
}
From b70b34f88417bf0efea5f8979a660bce2dc8dbcc Mon Sep 17 00:00:00 2001
From: Christos Nasikas
Date: Thu, 24 Jun 2021 16:31:18 +0300
Subject: [PATCH 34/86] [Cases] Fix push to external service error when
connector's mapping does not exists (#102894)
Co-authored-by: Jonathan Buttner