@@ -59,7 +59,7 @@ export const PersonalDashboardLayout: React.FC = ({ diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.test.tsx index 9fa4d4dd1b237..8f2ecf012e1ea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.test.tsx @@ -30,7 +30,7 @@ import { PrivateSourcesSidebar } from './private_sources_sidebar'; describe('PrivateSourcesSidebar', () => { const mockValues = { - account: { canCreatePersonalSources: true }, + account: { canCreatePrivateSources: true }, contentSource: {}, }; @@ -55,7 +55,7 @@ describe('PrivateSourcesSidebar', () => { }); it('uses correct title and description when private sources are disabled', () => { - setMockValues({ ...mockValues, account: { canCreatePersonalSources: false } }); + setMockValues({ ...mockValues, account: { canCreatePrivateSources: false } }); const wrapper = shallow(); expect(wrapper.find(ViewContentHeader).prop('title')).toEqual(PRIVATE_VIEW_ONLY_PAGE_TITLE); 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 ac497f5ae3a28..6cd7a10fc7ade 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 @@ -24,13 +24,13 @@ import { ViewContentHeader } from '../../shared/view_content_header'; export const PrivateSourcesSidebar = () => { const { - account: { canCreatePersonalSources }, + account: { canCreatePrivateSources }, } = useValues(AppLogic); - const PAGE_TITLE = canCreatePersonalSources + const PAGE_TITLE = canCreatePrivateSources ? PRIVATE_CAN_CREATE_PAGE_TITLE : PRIVATE_VIEW_ONLY_PAGE_TITLE; - const PAGE_DESCRIPTION = canCreatePersonalSources + const PAGE_DESCRIPTION = canCreatePrivateSources ? PRIVATE_CAN_CREATE_PAGE_DESCRIPTION : PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION; 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 26f82ca5371d6..1ed77ea0fb1fd 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 @@ -23,7 +23,7 @@ import { SOURCES_PATH, SOURCE_ADDED_PATH, OAUTH_AUTHORIZE_PATH, - PERSONAL_SOURCES_PATH, + PRIVATE_SOURCES_PATH, ORG_SETTINGS_PATH, USERS_AND_ROLES_PATH, SECURITY_PATH, @@ -94,8 +94,8 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { - - + + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx index b89a1451f7e57..b69303aae2106 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx @@ -19,7 +19,7 @@ import { getSourcesPath, GROUPS_PATH, SOURCES_PATH, - PERSONAL_SOURCES_PATH, + PRIVATE_SOURCES_PATH, SOURCE_DETAILS_PATH, } from './routes'; @@ -40,7 +40,7 @@ describe('getContentSourcePath', () => { const wrapper = shallow(); const path = wrapper.find(EuiLink).prop('href'); - expect(path).toEqual(`${PERSONAL_SOURCES_PATH}/123`); + expect(path).toEqual(`${PRIVATE_SOURCES_PATH}/123`); }); }); 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 edd33ac885569..cd699f7df86cb 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 @@ -60,7 +60,7 @@ export const GROUP_PATH = `${GROUPS_PATH}/:groupId`; export const GROUP_SOURCE_PRIORITIZATION_PATH = `${GROUPS_PATH}/:groupId/source_prioritization`; export const SOURCES_PATH = '/sources'; -export const PERSONAL_SOURCES_PATH = `${PERSONAL_PATH}${SOURCES_PATH}`; +export const PRIVATE_SOURCES_PATH = `${PERSONAL_PATH}${SOURCES_PATH}`; export const SOURCE_ADDED_PATH = `${SOURCES_PATH}/added`; export const ADD_SOURCE_PATH = `${SOURCES_PATH}/add`; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx index 165586dcc3903..32161046bdd51 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import { AppLogic } from '../../../../app_logic'; -import noSharedSourcesIcon from '../../../../assets/share_circle.svg'; +import noOrgSourcesIcon from '../../../../assets/share_circle.svg'; import { WorkplaceSearchPageTemplate, PersonalDashboardLayout, @@ -143,7 +143,7 @@ export const AddSourceList: React.FC = () => { {ADD_SOURCE_EMPTY_TITLE}} body={

{ADD_SOURCE_EMPTY_BODY}

} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts index 63eba575f0c37..93cbbef07f7d7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts @@ -23,7 +23,7 @@ import { AppLogic } from '../../../../app_logic'; import { ADD_GITHUB_PATH, SOURCES_PATH, - PERSONAL_SOURCES_PATH, + PRIVATE_SOURCES_PATH, getSourcesPath, } from '../../../../routes'; import { CustomSource } from '../../../../types'; @@ -321,7 +321,7 @@ describe('AddSourceLogic', () => { expect(navigateToUrl).toHaveBeenCalledWith(getSourcesPath(SOURCES_PATH, true)); }); - it('redirects to private dashboard when account context', async () => { + it('redirects to personal dashboard when account context', async () => { const accountQueryString = '?state=%7B%22action%22:%22create%22,%22context%22:%22account%22,%22service_type%22:%22gmail%22,%22csrf_token%22:%22token%3D%3D%22,%22index_permissions%22:false%7D&code=code'; @@ -379,7 +379,7 @@ describe('AddSourceLogic', () => { const githubQueryString = getGithubQueryString('account'); AddSourceLogic.actions.saveSourceParams(githubQueryString, errorParams, false); - expect(navigateToUrl).toHaveBeenCalledWith(PERSONAL_SOURCES_PATH); + expect(navigateToUrl).toHaveBeenCalledWith(PRIVATE_SOURCES_PATH); expect(setErrorMessage).toHaveBeenCalledWith( PERSONAL_DASHBOARD_SOURCE_ERROR(GITHUB_ERROR) ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts index e63e58adb38e2..300b2aa6a10ac 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts @@ -25,7 +25,7 @@ import { CUSTOM_SERVICE_TYPE, WORKPLACE_SEARCH_URL_PREFIX } from '../../../../co import { SOURCES_PATH, ADD_GITHUB_PATH, - PERSONAL_SOURCES_PATH, + PRIVATE_SOURCES_PATH, getSourcesPath, } from '../../../../routes'; import { CustomSource } from '../../../../types'; @@ -521,7 +521,7 @@ export const AddSourceLogic = kea { const mockValues = { - account: { canCreatePersonalSources: false, groups: [] }, + account: { canCreatePrivateSources: false, groups: [] }, dataLoading: false, contentSources: [], privateContentSources: [], @@ -42,15 +42,15 @@ describe('PrivateSources', () => { expect(wrapper.find(SourcesView)).toHaveLength(1); }); - it('renders only shared sources section when canCreatePersonalSources is false', () => { + it('renders only organizational sources section when canCreatePrivateSources is false', () => { setMockValues({ ...mockValues }); const wrapper = shallow(); expect(wrapper.find(ContentSection)).toHaveLength(1); }); - it('renders both shared and private sources sections when canCreatePersonalSources is true', () => { - setMockValues({ ...mockValues, account: { canCreatePersonalSources: true, groups: [] } }); + it('renders both organizational and private sources sections when canCreatePrivateSources is true', () => { + setMockValues({ ...mockValues, account: { canCreatePrivateSources: true, groups: [] } }); const wrapper = shallow(); expect(wrapper.find(ContentSection)).toHaveLength(2); @@ -61,7 +61,7 @@ describe('PrivateSources', () => { ...mockValues, privateContentSources: ['source1', 'source2'], hasPlatinumLicense: false, - account: { canCreatePersonalSources: true, groups: [] }, + account: { canCreatePrivateSources: true, groups: [] }, }); const wrapper = shallow(); @@ -71,7 +71,7 @@ describe('PrivateSources', () => { it('renders an action button when user can add private sources', () => { setMockValues({ ...mockValues, - account: { canCreatePersonalSources: true, groups: [] }, + account: { canCreatePrivateSources: true, groups: [] }, serviceTypes: [{ configured: true }], }); const wrapper = shallow(); @@ -82,7 +82,7 @@ describe('PrivateSources', () => { it('renders empty prompts if no sources are available', () => { setMockValues({ ...mockValues, - account: { canCreatePersonalSources: true, groups: [] }, + account: { canCreatePrivateSources: true, groups: [] }, }); const wrapper = shallow(); @@ -92,7 +92,7 @@ describe('PrivateSources', () => { it('renders SourcesTable if sources are available', () => { setMockValues({ ...mockValues, - account: { canCreatePersonalSources: true, groups: [] }, + account: { canCreatePrivateSources: true, groups: [] }, contentSources: ['1', '2'], privateContentSources: ['1', '2'], }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx index 57574ce14df67..83e690fdb2a95 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx @@ -15,7 +15,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { LicensingLogic } from '../../../shared/licensing'; import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { AppLogic } from '../../app_logic'; -import noSharedSourcesIcon from '../../assets/share_circle.svg'; +import noOrgSourcesIcon from '../../assets/share_circle.svg'; import { PersonalDashboardLayout } from '../../components/layout'; import { ContentSection } from '../../components/shared/content_section'; import { SourcesTable } from '../../components/shared/sources_table'; @@ -27,10 +27,10 @@ import { PRIVATE_LINK_TITLE, PRIVATE_HEADER_TITLE, PRIVATE_HEADER_DESCRIPTION, - PRIVATE_SHARED_SOURCES_TITLE, + PRIVATE_ORG_SOURCES_TITLE, PRIVATE_EMPTY_TITLE, - SHARED_EMPTY_TITLE, - SHARED_EMPTY_DESCRIPTION, + ORG_SOURCES_EMPTY_TITLE, + ORG_SOURCES_EMPTY_DESCRIPTION, LICENSE_CALLOUT_TITLE, LICENSE_CALLOUT_DESCRIPTION, } from './constants'; @@ -51,13 +51,13 @@ export const PrivateSources: React.FC = () => { ); const { - account: { canCreatePersonalSources, groups }, + account: { canCreatePrivateSources, groups }, } = useValues(AppLogic); const hasConfiguredConnectors = serviceTypes.some(({ configured }) => configured); - const canAddSources = canCreatePersonalSources && hasConfiguredConnectors; + const canAddSources = canCreatePrivateSources && hasConfiguredConnectors; const hasPrivateSources = privateContentSources?.length > 0; - const hasSharedSources = contentSources.length > 0; + const hasOrgSources = contentSources.length > 0; const licenseCallout = ( <> @@ -106,30 +106,30 @@ export const PrivateSources: React.FC = () => { ); - const sharedSourcesEmptyState = ( + const orgSourcesEmptyState = ( {SHARED_EMPTY_TITLE}} - body={

{SHARED_EMPTY_DESCRIPTION}

} + iconType={noOrgSourcesIcon} + title={

{ORG_SOURCES_EMPTY_TITLE}

} + body={

{ORG_SOURCES_EMPTY_DESCRIPTION}

} />
); - const sharedSourcesTable = ( + const orgSourcesTable = ( ); - const sharedSourcesSection = ( + const orgSourcesSection = ( { ) } > - {hasSharedSources ? sharedSourcesTable : sharedSourcesEmptyState} + {hasOrgSources ? orgSourcesTable : orgSourcesEmptyState} ); @@ -148,8 +148,8 @@ export const PrivateSources: React.FC = () => { {hasPrivateSources && !hasPlatinumLicense && licenseCallout} - {canCreatePersonalSources && privateSourcesSection} - {sharedSourcesSection} + {canCreatePrivateSources && privateSourcesSection} + {orgSourcesSection} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts index 94ec730ef256c..3c926e13fe52f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts @@ -202,7 +202,7 @@ describe('SourceLogic', () => { AppLogic.values.isOrganization = false; http.get.mockReturnValue(mock404); - SourceLogic.actions.initializeSource('404ing_personal_source'); + SourceLogic.actions.initializeSource('404ing_private_source'); await expectedAsyncError(mock404); expect(navigateToUrl).toHaveBeenCalledWith('/p/sources'); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts index 5c947b68f2333..c31eacda69515 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts @@ -19,7 +19,7 @@ import { import { HttpLogic } from '../../../shared/http'; import { KibanaLogic } from '../../../shared/kibana'; import { AppLogic } from '../../app_logic'; -import { PERSONAL_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; +import { PRIVATE_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; import { ContentSourceFullData, Meta, DocumentSummaryItem, SourceContentItem } from '../../types'; export interface SourceActions { @@ -169,7 +169,7 @@ export const SourceLogic = kea>({ } } catch (e) { if (e?.response?.status === 404) { - const redirect = isOrganization ? SOURCES_PATH : PERSONAL_SOURCES_PATH; + const redirect = isOrganization ? SOURCES_PATH : PRIVATE_SOURCES_PATH; KibanaLogic.values.navigateToUrl(redirect); setErrorMessage( i18n.translate('xpack.enterpriseSearch.workplaceSearch.sources.notFoundErrorMessage', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx index eb6a9949eb07c..00cf56001f73b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx @@ -14,14 +14,14 @@ import { Route, Switch, Redirect } from 'react-router-dom'; import { shallow } from 'enzyme'; -import { ADD_SOURCE_PATH, PERSONAL_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; +import { ADD_SOURCE_PATH, PRIVATE_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; import { SourcesRouter } from './sources_router'; describe('SourcesRouter', () => { const resetSourcesState = jest.fn(); const mockValues = { - account: { canCreatePersonalSources: true }, + account: { canCreatePrivateSources: true }, isOrganization: true, hasPlatinumLicense: true, }; @@ -50,17 +50,17 @@ describe('SourcesRouter', () => { }); it('redirects when cannot create sources', () => { - setMockValues({ ...mockValues, account: { canCreatePersonalSources: false } }); + setMockValues({ ...mockValues, account: { canCreatePrivateSources: false } }); const wrapper = shallow(); expect(wrapper.find(Redirect).last().prop('from')).toEqual( getSourcesPath(ADD_SOURCE_PATH, false) ); - expect(wrapper.find(Redirect).last().prop('to')).toEqual(PERSONAL_SOURCES_PATH); + expect(wrapper.find(Redirect).last().prop('to')).toEqual(PRIVATE_SOURCES_PATH); }); - it('does not render the router until canCreatePersonalSources is fetched', () => { - setMockValues({ ...mockValues, account: {} }); // canCreatePersonalSources is undefined + it('does not render the router until canCreatePrivateSources is fetched', () => { + setMockValues({ ...mockValues, account: {} }); // canCreatePrivateSources is undefined const wrapper = shallow(); expect(wrapper.html()).toEqual(null); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx index 0171717490c9b..2d47bab00810c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx @@ -16,7 +16,7 @@ import { AppLogic } from '../../app_logic'; import { ADD_SOURCE_PATH, SOURCE_DETAILS_PATH, - PERSONAL_SOURCES_PATH, + PRIVATE_SOURCES_PATH, SOURCES_PATH, getSourcesPath, } from '../../routes'; @@ -35,7 +35,7 @@ export const SourcesRouter: React.FC = () => { const { hasPlatinumLicense } = useValues(LicensingLogic); const { resetSourcesState } = useActions(SourcesLogic); const { - account: { canCreatePersonalSources }, + account: { canCreatePrivateSources }, isOrganization, } = useValues(AppLogic); @@ -49,18 +49,18 @@ export const SourcesRouter: React.FC = () => { /** * When opening `workplace_search/p/sources/add` as a bookmark or reloading this page, - * Sources router first get rendered *before* it receives the canCreatePersonalSources value. - * This results in canCreatePersonalSources always being undefined on the first render, + * Sources router first get rendered *before* it receives the canCreatePrivateSources value. + * This results in canCreatePrivateSources always being undefined on the first render, * and user always getting redirected to `workplace_search/p/sources`. * Here we check for this value being present before we render any routes. */ - if (canCreatePersonalSources === undefined) { + if (canCreatePrivateSources === undefined) { return null; } return ( - + @@ -93,12 +93,12 @@ export const SourcesRouter: React.FC = () => { ); })} - {canCreatePersonalSources ? ( + {canCreatePrivateSources ? ( ) : ( - + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/__mocks__/group_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/__mocks__/group_logic.mock.ts index d4c1ce9dc8a29..d4d66005ecdc5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/__mocks__/group_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/__mocks__/group_logic.mock.ts @@ -11,7 +11,7 @@ export const mockGroupValues = { group: {} as GroupDetails, dataLoading: true, managerModalFormErrors: [], - sharedSourcesModalVisible: false, + orgSourcesModalVisible: false, confirmDeleteModalVisible: false, groupNameInputValue: '', selectedGroupSources: [], diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.test.tsx index 5619698335b30..2b22823219410 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.test.tsx @@ -23,7 +23,7 @@ const saveItems = jest.fn(); const props = { children: <>, - label: 'shared content sources', + label: 'organizational content sources', allItems: [], numSelected: 1, hideModal, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx index 10d115c8e60ab..b5d23895e9efd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx @@ -26,7 +26,7 @@ import { import { i18n } from '@kbn/i18n'; import { EuiButtonTo } from '../../../../shared/react_router_helpers'; -import noSharedSourcesIcon from '../../../assets/share_circle.svg'; +import noOrgSourcesIcon from '../../../assets/share_circle.svg'; import { UPDATE_BUTTON, CANCEL_BUTTON } from '../../../constants'; import { SOURCES_PATH } from '../../../routes'; import { Group } from '../../../types'; @@ -36,7 +36,7 @@ import { GroupsLogic } from '../groups_logic'; const ADD_SOURCE_BUTTON_TEXT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.groupManagerUpdateAddSourceButton', { - defaultMessage: 'Add a Shared Source', + defaultMessage: 'Add an organizational source', } ); const EMPTY_STATE_TITLE = i18n.translate( @@ -48,7 +48,7 @@ const EMPTY_STATE_TITLE = i18n.translate( const EMPTY_STATE_BODY = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.body', { - defaultMessage: 'Looks like you have not added any shared content sources yet.', + defaultMessage: 'Looks like you have not added any organizational content sources yet.', } ); @@ -103,7 +103,7 @@ export const GroupManagerModal: React.FC = ({ {EMPTY_STATE_TITLE}} body={EMPTY_STATE_BODY} actions={sourcesButton} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx index ed0246702d7a5..e1ecc47f02669 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx @@ -20,7 +20,7 @@ import { SourcesTable } from '../../../components/shared/sources_table'; import { GroupOverview } from './group_overview'; const deleteGroup = jest.fn(); -const showSharedSourcesModal = jest.fn(); +const showOrgSourcesModal = jest.fn(); const showConfirmDeleteModal = jest.fn(); const hideConfirmDeleteModal = jest.fn(); const updateGroupName = jest.fn(); @@ -37,7 +37,7 @@ describe('GroupOverview', () => { beforeEach(() => { setMockActions({ deleteGroup, - showSharedSourcesModal, + showOrgSourcesModal, showConfirmDeleteModal, hideConfirmDeleteModal, updateGroupName, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx index 5714cc965827e..c5febf37e1fbb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx @@ -25,7 +25,7 @@ import { i18n } from '@kbn/i18n'; import { EuiButtonTo } from '../../../../shared/react_router_helpers'; import { TruncatedContent } from '../../../../shared/truncate'; -import noSharedSourcesIcon from '../../../assets/share_circle.svg'; +import noOrgSourcesIcon from '../../../assets/share_circle.svg'; import { WorkplaceSearchPageTemplate } from '../../../components/layout'; import { ContentSection } from '../../../components/shared/content_section'; import { SourcesTable } from '../../../components/shared/sources_table'; @@ -55,7 +55,7 @@ const GROUP_USERS_DESCRIPTION = i18n.translate( const MANAGE_SOURCES_BUTTON_TEXT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.overview.manageSourcesButtonText', { - defaultMessage: 'Manage shared content sources', + defaultMessage: 'Manage organizational content sources', } ); const MANAGE_USERS_BUTTON_TEXT = i18n.translate( @@ -110,7 +110,7 @@ const CONFIRM_TITLE_TEXT = i18n.translate( export const GroupOverview: React.FC = () => { const { deleteGroup, - showSharedSourcesModal, + showOrgSourcesModal, showConfirmDeleteModal, hideConfirmDeleteModal, updateGroupName, @@ -159,7 +159,7 @@ export const GroupOverview: React.FC = () => { const hasContentSources = contentSources?.length > 0; const manageSourcesButton = ( - + {MANAGE_SOURCES_BUTTON_TEXT} ); @@ -185,7 +185,7 @@ export const GroupOverview: React.FC = () => { <> {GROUP_SOURCES_TITLE}} body={

{EMPTY_SOURCES_DESCRIPTION}

} actions={manageSourcesButton} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx index 8d3c7dfa7622b..effacfa3aa4f8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx @@ -25,7 +25,7 @@ const DAYS_CUTOFF = 8; export const NO_SOURCES_MESSAGE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.noSourcesMessage', { - defaultMessage: 'No shared content sources', + defaultMessage: 'No organizational content sources', } ); export const NO_USERS_MESSAGE = i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row_sources_dropdown.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row_sources_dropdown.tsx index 77d7de91caf7c..aa25deff49e3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row_sources_dropdown.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row_sources_dropdown.tsx @@ -37,7 +37,7 @@ export const GroupRowSourcesDropdown: React.FC = ( const contentSourceCountHeading = ( {i18n.translate('xpack.enterpriseSearch.workplaceSearch.groups.contentSourceCountHeading', { - defaultMessage: '{numSources} shared content sources', + defaultMessage: '{numSources} organizational content sources', values: { numSources: groupSources.length }, })} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.test.tsx index ba667c6d8152d..49dc7223e54e6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.test.tsx @@ -18,7 +18,7 @@ import { GroupSourcePrioritization } from './group_source_prioritization'; const updatePriority = jest.fn(); const saveGroupSourcePrioritization = jest.fn(); -const showSharedSourcesModal = jest.fn(); +const showOrgSourcesModal = jest.fn(); const mockValues = { group: groups[0], @@ -36,7 +36,7 @@ describe('GroupSourcePrioritization', () => { setMockActions({ updatePriority, saveGroupSourcePrioritization, - showSharedSourcesModal, + showOrgSourcesModal, }); setMockValues(mockValues); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.tsx index 0fdaf2adde869..76549ba1e96dc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.tsx @@ -36,7 +36,7 @@ import { GroupLogic } from '../group_logic'; const HEADER_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.headerTitle', { - defaultMessage: 'Shared content source prioritization', + defaultMessage: 'Organizational content source prioritization', } ); const HEADER_DESCRIPTION = i18n.translate( @@ -54,7 +54,7 @@ const ZERO_STATE_TITLE = i18n.translate( const ZERO_STATE_BUTTON_TEXT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.zeroStateButtonText', { - defaultMessage: 'Add shared content sources', + defaultMessage: 'Add organizational content sources', } ); const SOURCE_TABLE_HEADER = i18n.translate( @@ -71,7 +71,7 @@ const PRIORITY_TABLE_HEADER = i18n.translate( ); export const GroupSourcePrioritization: React.FC = () => { - const { updatePriority, saveGroupSourcePrioritization, showSharedSourcesModal } = useActions( + const { updatePriority, saveGroupSourcePrioritization, showOrgSourcesModal } = useActions( GroupLogic ); @@ -118,7 +118,7 @@ export const GroupSourcePrioritization: React.FC = () => { )} } - actions={{ZERO_STATE_BUTTON_TEXT}} + actions={{ZERO_STATE_BUTTON_TEXT}} />
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/shared_sources_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/org_sources_modal.test.tsx similarity index 83% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/shared_sources_modal.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/org_sources_modal.test.tsx index 02f0b53af97cc..3059274686045 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/shared_sources_modal.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/org_sources_modal.test.tsx @@ -13,23 +13,23 @@ import React from 'react'; import { shallow } from 'enzyme'; import { GroupManagerModal } from './group_manager_modal'; -import { SharedSourcesModal } from './shared_sources_modal'; +import { OrgSourcesModal } from './org_sources_modal'; import { SourcesList } from './sources_list'; const group = groups[0]; const addGroupSource = jest.fn(); const selectAllSources = jest.fn(); -const hideSharedSourcesModal = jest.fn(); +const hideOrgSourcesModal = jest.fn(); const removeGroupSource = jest.fn(); const saveGroupSources = jest.fn(); -describe('SharedSourcesModal', () => { +describe('OrgSourcesModal', () => { it('renders', () => { setMockActions({ addGroupSource, selectAllSources, - hideSharedSourcesModal, + hideOrgSourcesModal, removeGroupSource, saveGroupSources, }); @@ -40,7 +40,7 @@ describe('SharedSourcesModal', () => { contentSources: group.contentSources, }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(SourcesList)).toHaveLength(1); expect(wrapper.find(GroupManagerModal)).toHaveLength(1); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/shared_sources_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/org_sources_modal.tsx similarity index 91% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/shared_sources_modal.tsx rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/org_sources_modal.tsx index 631c4f80f36b0..23598433d426b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/shared_sources_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/org_sources_modal.tsx @@ -21,15 +21,15 @@ import { SourcesList } from './sources_list'; const MODAL_LABEL = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.sourcesModalLabel', { - defaultMessage: 'shared content sources', + defaultMessage: 'organizational content sources', } ); -export const SharedSourcesModal: React.FC = () => { +export const OrgSourcesModal: React.FC = () => { const { addGroupSource, selectAllSources, - hideSharedSourcesModal, + hideOrgSourcesModal, removeGroupSource, saveGroupSources, } = useActions(GroupLogic); @@ -43,7 +43,7 @@ export const SharedSourcesModal: React.FC = () => { label={MODAL_LABEL} allItems={contentSources} numSelected={selectedGroupSources.length} - hideModal={hideSharedSourcesModal} + hideModal={hideOrgSourcesModal} selectAll={selectAllSources} saveItems={saveGroupSources} > diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts index 088936443ccd3..79b253dd64301 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts @@ -114,7 +114,7 @@ describe('GroupLogic', () => { GroupLogic.actions.onGroupSourcesSaved(group); expect(GroupLogic.values.group).toEqual(group); - expect(GroupLogic.values.sharedSourcesModalVisible).toEqual(false); + expect(GroupLogic.values.orgSourcesModalVisible).toEqual(false); expect(GroupLogic.values.selectedGroupSources).toEqual(sourceIds); expect(GroupLogic.values.cachedSourcePriorities).toEqual(sourcePriorities); expect(GroupLogic.values.activeSourcePriorities).toEqual(sourcePriorities); @@ -130,11 +130,11 @@ describe('GroupLogic', () => { }); }); - describe('hideSharedSourcesModal', () => { + describe('hideOrgSourcesModal', () => { it('sets reducers', () => { - GroupLogic.actions.hideSharedSourcesModal(group); + GroupLogic.actions.hideOrgSourcesModal(group); - expect(GroupLogic.values.sharedSourcesModalVisible).toEqual(false); + expect(GroupLogic.values.orgSourcesModalVisible).toEqual(false); expect(GroupLogic.values.selectedGroupSources).toEqual(sourceIds); }); }); @@ -284,7 +284,7 @@ describe('GroupLogic', () => { await nextTick(); expect(onGroupSourcesSavedSpy).toHaveBeenCalledWith(group); expect(flashSuccessToast).toHaveBeenCalledWith( - 'Successfully updated shared content sources.' + 'Successfully updated organizational content sources.' ); }); @@ -321,7 +321,7 @@ describe('GroupLogic', () => { await nextTick(); expect(flashSuccessToast).toHaveBeenCalledWith( - 'Successfully updated shared source prioritization.' + 'Successfully updated organizational source prioritization.' ); expect(onGroupPrioritiesChangedSpy).toHaveBeenCalledWith(group); }); @@ -345,11 +345,11 @@ describe('GroupLogic', () => { }); }); - describe('showSharedSourcesModal', () => { + describe('showOrgSourcesModal', () => { it('sets reducer and clears flash messages', () => { - GroupLogic.actions.showSharedSourcesModal(); + GroupLogic.actions.showOrgSourcesModal(); - expect(GroupLogic.values.sharedSourcesModalVisible).toEqual(true); + expect(GroupLogic.values.orgSourcesModalVisible).toEqual(true); expect(clearFlashMessages).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.ts index 96b9213d30afc..3ba7d68d0b3e9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.ts @@ -32,13 +32,13 @@ interface GroupActions { removeGroupSource(sourceId: string): string; onGroupSourcesSaved(group: GroupDetails): GroupDetails; setGroupModalErrors(errors: string[]): string[]; - hideSharedSourcesModal(group: GroupDetails): GroupDetails; + hideOrgSourcesModal(group: GroupDetails): GroupDetails; selectAllSources(contentSources: ContentSourceDetails[]): ContentSourceDetails[]; updatePriority(id: string, boost: number): { id: string; boost: number }; resetGroup(): void; showConfirmDeleteModal(): void; hideConfirmDeleteModal(): void; - showSharedSourcesModal(): void; + showOrgSourcesModal(): void; resetFlashMessages(): void; initializeGroup(groupId: string): { groupId: string }; deleteGroup(): void; @@ -51,7 +51,7 @@ interface GroupValues { group: GroupDetails; dataLoading: boolean; managerModalFormErrors: string[]; - sharedSourcesModalVisible: boolean; + orgSourcesModalVisible: boolean; confirmDeleteModalVisible: boolean; groupNameInputValue: string; selectedGroupSources: string[]; @@ -71,13 +71,13 @@ export const GroupLogic = kea>({ removeGroupSource: (sourceId) => sourceId, onGroupSourcesSaved: (group) => group, setGroupModalErrors: (errors) => errors, - hideSharedSourcesModal: (group) => group, + hideOrgSourcesModal: (group) => group, selectAllSources: (contentSources) => contentSources, updatePriority: (id, boost) => ({ id, boost }), resetGroup: () => true, showConfirmDeleteModal: () => true, hideConfirmDeleteModal: () => true, - showSharedSourcesModal: () => true, + showOrgSourcesModal: () => true, resetFlashMessages: () => true, initializeGroup: (groupId) => ({ groupId }), deleteGroup: () => true, @@ -109,11 +109,11 @@ export const GroupLogic = kea>({ setGroupModalErrors: (_, errors) => errors, }, ], - sharedSourcesModalVisible: [ + orgSourcesModalVisible: [ false, { - showSharedSourcesModal: () => true, - hideSharedSourcesModal: () => false, + showOrgSourcesModal: () => true, + hideOrgSourcesModal: () => false, onGroupSourcesSaved: () => false, }, ], @@ -138,7 +138,7 @@ export const GroupLogic = kea>({ onInitializeGroup: (_, { contentSources }) => contentSources.map(({ id }) => id), onGroupSourcesSaved: (_, { contentSources }) => contentSources.map(({ id }) => id), selectAllSources: (_, contentSources) => contentSources.map(({ id }) => id), - hideSharedSourcesModal: (_, { contentSources }) => contentSources.map(({ id }) => id), + hideOrgSourcesModal: (_, { contentSources }) => contentSources.map(({ id }) => id), addGroupSource: (state, sourceId) => [...state, sourceId].sort(), removeGroupSource: (state, sourceId) => state.filter((id) => id !== sourceId), }, @@ -257,7 +257,7 @@ export const GroupLogic = kea>({ const GROUP_SOURCES_UPDATED_MESSAGE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.groupSourcesUpdated', { - defaultMessage: 'Successfully updated shared content sources.', + defaultMessage: 'Successfully updated organizational content sources.', } ); flashSuccessToast(GROUP_SOURCES_UPDATED_MESSAGE); @@ -289,7 +289,7 @@ export const GroupLogic = kea>({ const GROUP_PRIORITIZATION_UPDATED_MESSAGE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.groupPrioritizationUpdated', { - defaultMessage: 'Successfully updated shared source prioritization.', + defaultMessage: 'Successfully updated organizational source prioritization.', } ); @@ -302,7 +302,7 @@ export const GroupLogic = kea>({ showConfirmDeleteModal: () => { clearFlashMessages(); }, - showSharedSourcesModal: () => { + showOrgSourcesModal: () => { clearFlashMessages(); }, resetFlashMessages: () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.test.tsx index 9ea0908a0f014..393b5a7e194de 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.test.tsx @@ -17,7 +17,7 @@ import { shallow } from 'enzyme'; import { GroupOverview } from './components/group_overview'; import { GroupSourcePrioritization } from './components/group_source_prioritization'; -import { SharedSourcesModal } from './components/shared_sources_modal'; +import { OrgSourcesModal } from './components/org_sources_modal'; import { GroupRouter } from './group_router'; describe('GroupRouter', () => { @@ -26,7 +26,7 @@ describe('GroupRouter', () => { beforeEach(() => { setMockValues({ - sharedSourcesModalVisible: false, + orgSourcesModalVisible: false, group: groups[0], }); @@ -47,12 +47,12 @@ describe('GroupRouter', () => { it('renders modal', () => { setMockValues({ - sharedSourcesModalVisible: true, + orgSourcesModalVisible: true, group: groups[0], }); const wrapper = shallow(); - expect(wrapper.find(SharedSourcesModal)).toHaveLength(1); + expect(wrapper.find(OrgSourcesModal)).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx index 341e20099c6c2..ffdfb9c378614 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx @@ -14,14 +14,14 @@ import { GROUP_SOURCE_PRIORITIZATION_PATH, GROUP_PATH } from '../../routes'; import { GroupOverview } from './components/group_overview'; import { GroupSourcePrioritization } from './components/group_source_prioritization'; -import { SharedSourcesModal } from './components/shared_sources_modal'; +import { OrgSourcesModal } from './components/org_sources_modal'; import { GroupLogic } from './group_logic'; export const GroupRouter: React.FC = () => { const { groupId } = useParams() as { groupId: string }; const { initializeGroup, resetGroup } = useActions(GroupLogic); - const { sharedSourcesModalVisible } = useValues(GroupLogic); + const { orgSourcesModalVisible } = useValues(GroupLogic); useEffect(() => { initializeGroup(groupId); @@ -38,7 +38,7 @@ export const GroupRouter: React.FC = () => {
- {sharedSourcesModalVisible && } + {orgSourcesModalVisible && } ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx index 1a4c4f51e93ea..5ecaef95c8be1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx @@ -112,7 +112,7 @@ export const Groups: React.FC = () => { }), description: i18n.translate('xpack.enterpriseSearch.workplaceSearch.groups.description', { defaultMessage: - 'Assign shared content sources and users to groups to create relevant search experiences for various internal teams.', + 'Assign organizational content sources and users to groups to create relevant search experiences for various internal teams.', }), rightSideItems: headerActions, }} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx index ef55668775513..e5a162baa2871 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx @@ -12,7 +12,7 @@ import { PageTemplateProps } from '../../../shared/layout'; import { NotFoundPrompt } from '../../../shared/not_found'; import { SendWorkplaceSearchTelemetry } from '../../../shared/telemetry'; import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../components/layout'; -import { PERSONAL_SOURCES_PATH } from '../../routes'; +import { PRIVATE_SOURCES_PATH } from '../../routes'; interface Props { isOrganization?: boolean; @@ -25,7 +25,7 @@ export const NotFound: React.FC = ({ isOrganization = true, pageChrome = diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts index 7e30826c5e7ec..72ad157dd06bf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts @@ -17,7 +17,7 @@ export const mockOverviewValues = { hasUsers: false, isOldAccount: false, pendingInvitationsCount: 0, - personalSourcesCount: 0, + privateSourcesCount: 0, sourcesCount: 0, dataLoading: true, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx index b62f022d85e38..f4f87f48f6395 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx @@ -21,19 +21,19 @@ import { OnboardingSteps, OrgNameOnboarding } from './onboarding_steps'; const account = { id: '1', isAdmin: true, - canCreatePersonalSources: true, + canCreatePrivateSources: true, groups: [], }; describe('OnboardingSteps', () => { - describe('Shared Sources', () => { + describe('Organizational Sources', () => { it('renders 0 sources state', () => { const wrapper = shallow(); expect(wrapper.find(OnboardingCard)).toHaveLength(2); expect(wrapper.find(OnboardingCard).first().prop('actionPath')).toBe(ADD_SOURCE_PATH); expect(wrapper.find(OnboardingCard).first().prop('description')).toBe( - 'Add shared sources for your organization to start searching.' + 'Add organizational sources for your organization to start searching.' ); }); @@ -42,7 +42,7 @@ describe('OnboardingSteps', () => { const wrapper = shallow(); expect(wrapper.find(OnboardingCard).first().prop('description')).toEqual( - 'You have added 2 shared sources. Happy searching.' + 'You have added 2 organizational sources. Happy searching.' ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx index b908211b83a43..13288bce98200 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx @@ -24,7 +24,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { TelemetryLogic } from '../../../shared/telemetry'; import { AppLogic } from '../../app_logic'; -import sharedSourcesIcon from '../../components/shared/assets/source_icons/share_circle.svg'; +import orgSourcesIcon from '../../components/shared/assets/source_icons/share_circle.svg'; import { ContentSection } from '../../components/shared/content_section'; import { ADD_SOURCE_PATH, USERS_AND_ROLES_PATH, ORG_SETTINGS_PATH } from '../../routes'; @@ -33,7 +33,7 @@ import { OverviewLogic } from './overview_logic'; const SOURCES_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingSourcesCard.title', - { defaultMessage: 'Shared sources' } + { defaultMessage: 'Organizational sources' } ); const USERS_TITLE = i18n.translate( @@ -53,7 +53,7 @@ const INVITE_MORE_USERS_BUTTON = i18n.translate( const ONBOARDING_SOURCES_CARD_DESCRIPTION = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingSourcesCard.description', - { defaultMessage: 'Add shared sources for your organization to start searching.' } + { defaultMessage: 'Add organizational sources for your organization to start searching.' } ); const ADD_FIRST_SOURCES_BUTTON = i18n.translate( @@ -87,7 +87,7 @@ export const OnboardingSteps: React.FC = () => { 'xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.description', { defaultMessage: - 'You have added {sourcesCount, number} shared {sourcesCount, plural, one {source} other {sources}}. Happy searching.', + 'You have added {sourcesCount, number} organizational {sourcesCount, plural, one {source} other {sources}}. Happy searching.', values: { sourcesCount }, } ); @@ -97,8 +97,8 @@ export const OnboardingSteps: React.FC = () => { { - const { sourcesCount, pendingInvitationsCount, accountsCount, personalSourcesCount } = useValues( + const { sourcesCount, pendingInvitationsCount, accountsCount, privateSourcesCount } = useValues( OverviewLogic ); @@ -37,8 +37,8 @@ export const OrganizationStats: React.FC = () => { { 'xpack.enterpriseSearch.workplaceSearch.organizationStats.privateSources', { defaultMessage: 'Private sources' } )} - count={personalSourcesCount} + count={privateSourcesCount} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts index f10918a1afe2d..64e87b77bacef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts @@ -33,7 +33,7 @@ describe('OverviewLogic', () => { hasUsers: true, isOldAccount: true, pendingInvitationsCount: 1, - personalSourcesCount: 1, + privateSourcesCount: 1, sourcesCount: 1, }; @@ -52,7 +52,7 @@ describe('OverviewLogic', () => { expect(OverviewLogic.values.sourcesCount).toEqual(1); expect(OverviewLogic.values.pendingInvitationsCount).toEqual(1); expect(OverviewLogic.values.accountsCount).toEqual(1); - expect(OverviewLogic.values.personalSourcesCount).toEqual(1); + expect(OverviewLogic.values.privateSourcesCount).toEqual(1); expect(OverviewLogic.values.activityFeed).toEqual(feed); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts index 196de02646804..1d79e66e778fb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts @@ -19,7 +19,7 @@ interface OverviewServerData { sourcesCount: number; pendingInvitationsCount: number; accountsCount: number; - personalSourcesCount: number; + privateSourcesCount: number; activityFeed: FeedActivity[]; } @@ -75,10 +75,10 @@ export const OverviewLogic = kea> setServerData: (_, { accountsCount }) => accountsCount, }, ], - personalSourcesCount: [ + privateSourcesCount: [ 0, { - setServerData: (_, { personalSourcesCount }) => personalSourcesCount, + setServerData: (_, { privateSourcesCount }) => privateSourcesCount, }, ], activityFeed: [ diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts index d656c0f889635..b5a8e205e5e1c 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts @@ -100,7 +100,7 @@ describe('callEnterpriseSearchConfigAPI', () => { id: 'some-id-string', groups: ['Default', 'Cats'], is_admin: true, - can_create_personal_sources: true, + can_create_private_sources: true, can_create_invitations: true, is_curated: false, viewed_onboarding_page: true, @@ -183,7 +183,7 @@ describe('callEnterpriseSearchConfigAPI', () => { id: undefined, groups: [], isAdmin: false, - canCreatePersonalSources: false, + canCreatePrivateSources: false, viewedOnboardingPage: false, }, }, diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts index fb3c3b14911a5..ba3a2e6a6480e 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts @@ -124,8 +124,8 @@ export const callEnterpriseSearchConfigAPI = async ({ id: data?.current_user?.workplace_search?.account?.id, groups: data?.current_user?.workplace_search?.account?.groups || [], isAdmin: !!data?.current_user?.workplace_search?.account?.is_admin, - canCreatePersonalSources: !!data?.current_user?.workplace_search?.account - ?.can_create_personal_sources, + canCreatePrivateSources: !!data?.current_user?.workplace_search?.account + ?.can_create_private_sources, viewedOnboardingPage: !!data?.current_user?.workplace_search?.account ?.viewed_onboarding_page, }, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 26869f8fea574..f0630f89c8984 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -79,6 +79,13 @@ const FlexItemWithMinWidth = styled(EuiFlexItem)` min-width: 0px; `; +// to limit size of iconpanel, making the header too big +const FlexItemWithMaxHeight = styled(EuiFlexItem)` + @media (min-width: 768px) { + max-height: 60px; + } +`; + function Breadcrumbs({ packageTitle }: { packageTitle: string }) { useBreadcrumbs('integration_details_overview', { pkgTitle: packageTitle }); return null; @@ -173,7 +180,7 @@ export function Detail() { - + {isLoading || !packageInfo ? ( ) : ( @@ -184,7 +191,7 @@ export function Detail() { icons={integrationInfo?.icons || packageInfo.icons} /> )} - + diff --git a/x-pack/plugins/infra/server/lib/sources/saved_object_references.test.ts b/x-pack/plugins/infra/server/lib/sources/saved_object_references.test.ts index 7d31f7342b05b..9f6f9cd284c67 100644 --- a/x-pack/plugins/infra/server/lib/sources/saved_object_references.test.ts +++ b/x-pack/plugins/infra/server/lib/sources/saved_object_references.test.ts @@ -17,8 +17,14 @@ describe('extractSavedObjectReferences function', () => { sourceConfigurationWithIndexPatternReference ); - expect(references).toMatchObject([{ id: 'INDEX_PATTERN_ID' }]); + expect(references).toMatchObject([ + { id: 'INDEX_PATTERN_ID' }, + { id: 'INVENTORY_DEFAULT_VIEW' }, + { id: 'METRICS_EXPLORER_DEFAULT_VIEW' }, + ]); expect(attributes).toHaveProperty(['logIndices', 'indexPatternId'], references[0].name); + expect(attributes).toHaveProperty(['inventoryDefaultView'], references[1].name); + expect(attributes).toHaveProperty(['metricsExplorerDefaultView'], references[2].name); }); it('ignores log index name references', () => { @@ -26,7 +32,29 @@ describe('extractSavedObjectReferences function', () => { sourceConfigurationWithIndexNameReference ); - expect(references).toHaveLength(0); + expect(references).toHaveLength(2); + expect(attributes).toHaveProperty(['logIndices', 'indexName'], 'INDEX_NAME'); + }); + + it('ignores default inventory view', () => { + const { attributes, references } = extractSavedObjectReferences({ + ...sourceConfigurationWithIndexNameReference, + inventoryDefaultView: '0', + }); + + expect(references).toHaveLength(1); + expect(references).toMatchObject([{ id: 'METRICS_EXPLORER_DEFAULT_VIEW' }]); + expect(attributes).toHaveProperty(['logIndices', 'indexName'], 'INDEX_NAME'); + }); + + it('ignores default metrics explorer view', () => { + const { attributes, references } = extractSavedObjectReferences({ + ...sourceConfigurationWithIndexNameReference, + metricsExplorerDefaultView: '0', + }); + + expect(references).toHaveLength(1); + expect(references).toMatchObject([{ id: 'INVENTORY_DEFAULT_VIEW' }]); expect(attributes).toHaveProperty(['logIndices', 'indexName'], 'INDEX_NAME'); }); }); diff --git a/x-pack/plugins/infra/server/lib/sources/saved_object_references.ts b/x-pack/plugins/infra/server/lib/sources/saved_object_references.ts index 31f36380cc23e..9ad8c9951d4f3 100644 --- a/x-pack/plugins/infra/server/lib/sources/saved_object_references.ts +++ b/x-pack/plugins/infra/server/lib/sources/saved_object_references.ts @@ -27,9 +27,11 @@ interface SavedObjectAttributesWithReferences { export const extractSavedObjectReferences = ( sourceConfiguration: InfraSourceConfiguration ): SavedObjectAttributesWithReferences => - [extractLogIndicesSavedObjectReferences].reduce< - SavedObjectAttributesWithReferences - >( + [ + extractLogIndicesSavedObjectReferences, + extractInventorySavedViewReferences, + extractMetricsExplorerSavedViewReferences, + ].reduce>( ({ attributes: accumulatedAttributes, references: accumulatedReferences }, extract) => { const { attributes, references } = extract(accumulatedAttributes); return { @@ -52,7 +54,11 @@ export const resolveSavedObjectReferences = ( attributes: InfraSavedSourceConfiguration, references: SavedObjectReference[] ): InfraSavedSourceConfiguration => - [resolveLogIndicesSavedObjectReferences].reduce( + [ + resolveLogIndicesSavedObjectReferences, + resolveInventoryViewSavedObjectReferences, + resolveMetricsExplorerSavedObjectReferences, + ].reduce( (accumulatedAttributes, resolve) => resolve(accumulatedAttributes, references), attributes ); @@ -85,6 +91,58 @@ const extractLogIndicesSavedObjectReferences = ( } }; +const extractInventorySavedViewReferences = ( + sourceConfiguration: InfraSourceConfiguration +): SavedObjectAttributesWithReferences => { + const { inventoryDefaultView } = sourceConfiguration; + if (inventoryDefaultView && inventoryDefaultView !== '0') { + const inventoryDefaultViewReference: SavedObjectReference = { + id: inventoryDefaultView, + type: 'inventory-view', + name: 'inventory-saved-view-0', + }; + const attributes: InfraSourceConfiguration = { + ...sourceConfiguration, + inventoryDefaultView: inventoryDefaultViewReference.name, + }; + return { + attributes, + references: [inventoryDefaultViewReference], + }; + } else { + return { + attributes: sourceConfiguration, + references: [], + }; + } +}; + +const extractMetricsExplorerSavedViewReferences = ( + sourceConfiguration: InfraSourceConfiguration +): SavedObjectAttributesWithReferences => { + const { metricsExplorerDefaultView } = sourceConfiguration; + if (metricsExplorerDefaultView && metricsExplorerDefaultView !== '0') { + const metricsExplorerDefaultViewReference: SavedObjectReference = { + id: metricsExplorerDefaultView, + type: 'metrics-explorer-view', + name: 'metrics-explorer-saved-view-0', + }; + const attributes: InfraSourceConfiguration = { + ...sourceConfiguration, + metricsExplorerDefaultView: metricsExplorerDefaultViewReference.name, + }; + return { + attributes, + references: [metricsExplorerDefaultViewReference], + }; + } else { + return { + attributes: sourceConfiguration, + references: [], + }; + } +}; + const resolveLogIndicesSavedObjectReferences = ( attributes: InfraSavedSourceConfiguration, references: SavedObjectReference[] @@ -111,3 +169,51 @@ const resolveLogIndicesSavedObjectReferences = ( return attributes; } }; + +const resolveInventoryViewSavedObjectReferences = ( + attributes: InfraSavedSourceConfiguration, + references: SavedObjectReference[] +): InfraSavedSourceConfiguration => { + if (attributes.inventoryDefaultView && attributes.inventoryDefaultView !== '0') { + const inventoryViewReference = references.find( + (reference) => reference.name === 'inventory-saved-view-0' + ); + + if (inventoryViewReference == null) { + throw new SavedObjectReferenceResolutionError( + 'Failed to resolve Inventory default view "inventory-saved-view-0".' + ); + } + + return { + ...attributes, + inventoryDefaultView: inventoryViewReference.id, + }; + } else { + return attributes; + } +}; + +const resolveMetricsExplorerSavedObjectReferences = ( + attributes: InfraSavedSourceConfiguration, + references: SavedObjectReference[] +): InfraSavedSourceConfiguration => { + if (attributes.metricsExplorerDefaultView && attributes.metricsExplorerDefaultView !== '0') { + const metricsExplorerViewReference = references.find( + (reference) => reference.name === 'metrics-explorer-saved-view-0' + ); + + if (metricsExplorerViewReference == null) { + throw new SavedObjectReferenceResolutionError( + 'Failed to resolve Metrics Explorer default view "metrics-explorer-saved-view-0".' + ); + } + + return { + ...attributes, + metricsExplorerDefaultView: metricsExplorerViewReference.id, + }; + } else { + return attributes; + } +}; diff --git a/x-pack/plugins/ingest_pipelines/public/locator.test.ts b/x-pack/plugins/ingest_pipelines/public/locator.test.ts index 47c7b13eb07ea..31558363513d9 100644 --- a/x-pack/plugins/ingest_pipelines/public/locator.test.ts +++ b/x-pack/plugins/ingest_pipelines/public/locator.test.ts @@ -7,12 +7,14 @@ import { ManagementAppLocatorDefinition } from 'src/plugins/management/common/locator'; import { IngestPipelinesLocatorDefinition, INGEST_PIPELINES_PAGES } from './locator'; +import { sharePluginMock } from '../../../../src/plugins/share/public/mocks'; describe('Ingest pipeline locator', () => { const setup = () => { const managementDefinition = new ManagementAppLocatorDefinition(); const definition = new IngestPipelinesLocatorDefinition({ managementAppLocator: { + ...sharePluginMock.createLocator(), getLocation: (params) => managementDefinition.getLocation(params), getUrl: async () => { throw new Error('not implemented'); @@ -21,10 +23,6 @@ describe('Ingest pipeline locator', () => { throw new Error('not implemented'); }, useUrl: () => '', - telemetry: jest.fn(), - extract: jest.fn(), - inject: jest.fn(), - migrations: {}, }, }); return { definition }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index 010e4d73c4791..28c0567d784ea 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -382,7 +382,7 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { 'xpack.lens.chartSwitch.dataLossDescription', { defaultMessage: - 'Selecting this visualization type will result in a partial loss of currently applied configuration selections.', + 'Selecting this visualization type will remove incompatible configuration options and multiple layers, if present', } )} iconProps={{ 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 c666d27e780b5..5d2207e93d491 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx @@ -125,7 +125,7 @@ export const HeatmapComponent: FC = ({ const tableId = Object.keys(data.tables)[0]; const table = data.tables[tableId]; - const paletteParams = args.palette?.params as CustomPaletteState; + const paletteParams = args.palette?.params; const xAxisColumnIndex = table.columns.findIndex((v) => v.id === args.xAccessor); const yAxisColumnIndex = table.columns.findIndex((v) => v.id === args.yAccessor); diff --git a/x-pack/plugins/lens/public/heatmap_visualization/types.ts b/x-pack/plugins/lens/public/heatmap_visualization/types.ts index 5515d77d1a8ab..d6b0e7ddc1d74 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/types.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { PaletteOutput } from '../../../../../src/plugins/charts/common'; +import type { CustomPaletteState, PaletteOutput } from '../../../../../src/plugins/charts/common'; import type { LensBrushEvent, LensFilterEvent } from '../types'; import type { LensMultiTable, FormatFactory, CustomPaletteParams, LayerType } from '../../common'; import type { HeatmapGridConfigResult, HeatmapLegendConfigResult } from '../../common/expressions'; @@ -36,7 +36,7 @@ export type HeatmapVisualizationState = HeatmapLayerState & { export type HeatmapExpressionArgs = SharedHeatmapLayerState & { title?: string; description?: string; - palette: PaletteOutput; + palette: PaletteOutput; }; export interface HeatmapRender { diff --git a/x-pack/plugins/lens/public/shared_components/coloring/utils.ts b/x-pack/plugins/lens/public/shared_components/coloring/utils.ts index 413e3708e9c9b..182e502563cd0 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/utils.ts +++ b/x-pack/plugins/lens/public/shared_components/coloring/utils.ts @@ -7,7 +7,7 @@ import chroma from 'chroma-js'; import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; -import { euiLightVars, euiDarkVars } from '@kbn/ui-shared-deps/theme'; +import { euiLightVars, euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import { isColorDark } from '@elastic/eui'; import type { Datatable } from 'src/plugins/expressions/public'; import { diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts index f2c13a81045ee..2412dd12199a3 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts @@ -8,11 +8,8 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ import type { Query } from 'src/plugins/data/common'; -import { SortDirection } from 'src/plugins/data/common/search'; -import { RENDER_AS, SCALING_TYPES } from '../constants'; import { MapExtent } from './map_descriptor'; import { Filter, TimeRange } from '../../../../../src/plugins/data/common'; -import { ESTermSourceDescriptor } from './source_descriptor_types'; export type Timeslice = { from: number; @@ -32,33 +29,6 @@ export type DataFilters = { isReadOnly: boolean; }; -export type ESSearchSourceSyncMeta = { - filterByMapBounds: boolean; - sortField: string; - sortOrder: SortDirection; - scalingType: SCALING_TYPES; - topHitsSplitField: string; - topHitsSize: number; -}; - -type ESGeoGridSourceSyncMeta = { - requestType: RENDER_AS; -}; - -type ESGeoLineSourceSyncMeta = { - splitField: string; - sortField: string; -}; - -export type ESTermSourceSyncMeta = Pick; - -export type VectorSourceSyncMeta = - | ESSearchSourceSyncMeta - | ESGeoGridSourceSyncMeta - | ESGeoLineSourceSyncMeta - | ESTermSourceSyncMeta - | null; - export type VectorSourceRequestMeta = DataFilters & { applyGlobalQuery: boolean; applyGlobalTime: boolean; @@ -67,7 +37,7 @@ export type VectorSourceRequestMeta = DataFilters & { geogridPrecision?: number; timesliceMaskField?: string; sourceQuery?: Query; - sourceMeta: VectorSourceSyncMeta; + sourceMeta: object | null; isForceRefresh: boolean; }; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx index e65e6ff4c463b..8a02066343914 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx @@ -45,7 +45,6 @@ import { ESGeoGridSourceDescriptor, MapExtent, VectorSourceRequestMeta, - VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { ImmutableSourceProperty, SourceEditorArgs } from '../source'; import { ISearchSource } from '../../../../../../../src/plugins/data/common/search/search_source'; @@ -54,6 +53,8 @@ import { Adapters } from '../../../../../../../src/plugins/inspector/common/adap import { isValidStringConfig } from '../../util/valid_string_config'; import { ITiledSingleLayerMvtParams } from '../tiled_single_layer_vector_source/tiled_single_layer_vector_source'; +type ESGeoGridSourceSyncMeta = Pick; + export const MAX_GEOTILE_LEVEL = 29; export const clustersTitle = i18n.translate('xpack.maps.source.esGridClustersTitle', { @@ -106,7 +107,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingle ); } - getSyncMeta(): VectorSourceSyncMeta { + getSyncMeta(): ESGeoGridSourceSyncMeta { return { requestType: this._descriptor.requestType, }; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx index 4755781147e5b..c9f99be886990 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx @@ -40,6 +40,8 @@ import { esFilters } from '../../../../../../../src/plugins/data/public'; import { getIsGoldPlus } from '../../../licensed_features'; import { LICENSED_FEATURES } from '../../../licensed_features'; +type ESGeoLineSourceSyncMeta = Pick; + const MAX_TRACKS = 250; export const geoLineTitle = i18n.translate('xpack.maps.source.esGeoLineTitle', { @@ -99,7 +101,7 @@ export class ESGeoLineSource extends AbstractESAggSource { ); } - getSyncMeta() { + getSyncMeta(): ESGeoLineSourceSyncMeta { return { splitField: this._descriptor.splitField, sortField: this._descriptor.sortField, diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 2b847d218434d..0b15e2a2e5399 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -52,7 +52,6 @@ import { ESSearchSourceDescriptor, Timeslice, VectorSourceRequestMeta, - VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { TimeRange } from '../../../../../../../src/plugins/data/common'; @@ -74,6 +73,16 @@ import { getMatchingIndexes, } from './util/feature_edit'; +type ESSearchSourceSyncMeta = Pick< + ESSearchSourceDescriptor, + | 'filterByMapBounds' + | 'sortField' + | 'sortOrder' + | 'scalingType' + | 'topHitsSplitField' + | 'topHitsSize' +>; + export function timerangeToTimeextent(timerange: TimeRange): Timeslice | undefined { const timeRangeBounds = getTimeFilter().calculateBounds(timerange); return timeRangeBounds.min !== undefined && timeRangeBounds.max !== undefined @@ -714,7 +723,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye }; } - getSyncMeta(): VectorSourceSyncMeta | null { + getSyncMeta(): ESSearchSourceSyncMeta { return { filterByMapBounds: this._descriptor.filterByMapBounds, sortField: this._descriptor.sortField, diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts index 93342d1167aeb..f7fc863eabb4a 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts @@ -25,18 +25,19 @@ import { } from '../../../../common/elasticsearch_util'; import { ESTermSourceDescriptor, - ESTermSourceSyncMeta, VectorJoinSourceRequestMeta, } from '../../../../common/descriptor_types'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { PropertiesMap } from '../../../../common/elasticsearch_util'; import { isValidStringConfig } from '../../util/valid_string_config'; -import { ITermJoinSource } from '../term_join_source/term_join_source'; +import { ITermJoinSource } from '../term_join_source'; import { IField } from '../../fields/field'; const TERMS_AGG_NAME = 'join'; const TERMS_BUCKET_KEYS_TO_IGNORE = ['key', 'doc_count']; +type ESTermSourceSyncMeta = Pick; + export function extractPropertiesMap(rawEsData: any, countPropertyName: string): PropertiesMap { const propertiesMap: PropertiesMap = new Map(); const buckets: any[] = _.get(rawEsData, ['aggregations', TERMS_AGG_NAME, 'buckets'], []); @@ -171,7 +172,7 @@ export class ESTermSource extends AbstractESAggSource implements ITermJoinSource return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName()); } - getSyncMeta(): ESTermSourceSyncMeta | null { + getSyncMeta(): ESTermSourceSyncMeta { return { indexPatternId: this._descriptor.indexPatternId, size: this._descriptor.size, diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx index d041e0d3ad5de..8a25fca6292cc 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx @@ -25,7 +25,6 @@ import { MapExtent, MVTFieldDescriptor, TiledSingleLayerVectorSourceDescriptor, - VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { MVTField } from '../../fields/mvt_field'; import { UpdateSourceEditor } from './update_source_editor'; @@ -196,7 +195,7 @@ export class MVTSingleLayerVectorSource return null; } - getSyncMeta(): VectorSourceSyncMeta { + getSyncMeta(): null { return null; } diff --git a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts index 62404cbe942e3..03476e30e7e41 100644 --- a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts @@ -5,13 +5,11 @@ * 2.0. */ -import type { Query } from 'src/plugins/data/common'; import { TableSource } from './table_source'; import { FIELD_ORIGIN } from '../../../../common/constants'; import { - DataFilters, VectorJoinSourceRequestMeta, - VectorSourceSyncMeta, + VectorSourceRequestMeta, } from '../../../../common/descriptor_types'; describe('TableSource', () => { @@ -178,14 +176,7 @@ describe('TableSource', () => { try { await tableSource.getGeoJsonWithMeta( 'foobar', - ({} as unknown) as DataFilters & { - applyGlobalQuery: boolean; - applyGlobalTime: boolean; - fieldNames: string[]; - geogridPrecision?: number; - sourceQuery?: Query; - sourceMeta: VectorSourceSyncMeta; - }, + ({} as unknown) as VectorSourceRequestMeta, () => {}, () => { return false; diff --git a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts index 8730ea7e3d02b..bb2b670ae9249 100644 --- a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts @@ -10,10 +10,9 @@ import type { Query } from 'src/plugins/data/common'; import { FIELD_ORIGIN, SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../common/constants'; import { MapExtent, - DataFilters, TableSourceDescriptor, VectorJoinSourceRequestMeta, - VectorSourceSyncMeta, + VectorSourceRequestMeta, } from '../../../../common/descriptor_types'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { ITermJoinSource } from '../term_join_source'; @@ -55,7 +54,7 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource return `table source ${uuid()}`; } - getSyncMeta(): VectorSourceSyncMeta | null { + getSyncMeta(): null { return null; } @@ -186,14 +185,7 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource // Could be useful to implement, e.g. to preview raw csv data async getGeoJsonWithMeta( layerName: string, - searchFilters: DataFilters & { - applyGlobalQuery: boolean; - applyGlobalTime: boolean; - fieldNames: string[]; - geogridPrecision?: number; - sourceQuery?: Query; - sourceMeta: VectorSourceSyncMeta; - }, + searchFilters: VectorSourceRequestMeta, registerCancelCallback: (callback: () => void) => void, isRequestStillActive: () => boolean ): Promise { diff --git a/x-pack/plugins/maps/public/classes/sources/term_join_source/term_join_source.ts b/x-pack/plugins/maps/public/classes/sources/term_join_source/term_join_source.ts index b9cd572cd8a19..8cf1a631bf223 100644 --- a/x-pack/plugins/maps/public/classes/sources/term_join_source/term_join_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/term_join_source/term_join_source.ts @@ -8,10 +8,7 @@ import { GeoJsonProperties } from 'geojson'; import { IField } from '../../fields/field'; import { Query } from '../../../../../../../src/plugins/data/common/query'; -import { - VectorJoinSourceRequestMeta, - VectorSourceSyncMeta, -} from '../../../../common/descriptor_types'; +import { VectorJoinSourceRequestMeta } from '../../../../common/descriptor_types'; import { PropertiesMap } from '../../../../common/elasticsearch_util'; import { ITooltipProperty } from '../../tooltips/tooltip_property'; import { ISource } from '../source'; @@ -26,7 +23,13 @@ export interface ITermJoinSource extends ISource { leftFieldName: string, registerCancelCallback: (callback: () => void) => void ): Promise; - getSyncMeta(): VectorSourceSyncMeta | null; + + /* + * Vector layer avoids unnecessarily re-fetching join data. + * Use getSyncMeta to expose fields that require join data re-fetch when changed. + */ + getSyncMeta(): object | null; + getId(): string; getRightFields(): IField[]; getTooltipProperties(properties: GeoJsonProperties): Promise; diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index bf0752d54c426..bc3807a8247b1 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -17,7 +17,6 @@ import { MapExtent, Timeslice, VectorSourceRequestMeta, - VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { DataRequest } from '../../util/data_request'; @@ -60,7 +59,13 @@ export interface IVectorSource extends ISource { getFields(): Promise; getFieldByName(fieldName: string): IField | null; getLeftJoinFields(): Promise; - getSyncMeta(): VectorSourceSyncMeta | null; + + /* + * Vector layer avoids unnecessarily re-fetching source data. + * Use getSyncMeta to expose fields that require source data re-fetch when changed. + */ + getSyncMeta(): object | null; + getFieldNames(): string[]; createField({ fieldName }: { fieldName: string }): IField; hasTooltipProperties(): boolean; @@ -159,7 +164,7 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc return { tooltipContent: null, areResultsTrimmed: false }; } - getSyncMeta(): VectorSourceSyncMeta | null { + getSyncMeta(): object | null { return null; } diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx index 33b95f4ca5816..cf138e0aadc60 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx @@ -20,7 +20,6 @@ import { import { ESSearchSource } from '../../../../../../classes/sources/es_search_source'; import { VectorLayer } from '../../../../../../classes/layers/vector_layer'; import { SCALING_TYPES, VECTOR_SHAPE_TYPE } from '../../../../../../../common/constants'; -import { ESSearchSourceSyncMeta } from '../../../../../../../common/descriptor_types'; export interface Props { cloneLayer: (layerId: string) => void; @@ -85,15 +84,13 @@ export class TOCEntryActionsPopover extends Component { async _getIsFeatureEditingEnabled(): Promise { const vectorLayer = this.props.layer as VectorLayer; - const layerSource = await this.props.layer.getSource(); + const layerSource = this.props.layer.getSource(); if (!(layerSource instanceof ESSearchSource)) { return false; } - const isClustered = - (layerSource?.getSyncMeta() as ESSearchSourceSyncMeta)?.scalingType === - SCALING_TYPES.CLUSTERS; + if ( - isClustered || + (layerSource as ESSearchSource).getSyncMeta().scalingType === SCALING_TYPES.CLUSTERS || (await vectorLayer.isFilteredByGlobalTime()) || vectorLayer.isPreviewLayer() || !vectorLayer.isVisible() || diff --git a/x-pack/plugins/maps/public/reducers/map/default_map_settings.ts b/x-pack/plugins/maps/public/reducers/map/default_map_settings.ts index c73bf46d4bc0c..ebde5481a13f5 100644 --- a/x-pack/plugins/maps/public/reducers/map/default_map_settings.ts +++ b/x-pack/plugins/maps/public/reducers/map/default_map_settings.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { euiThemeVars } from '@kbn/ui-shared-deps/theme'; +import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme'; import { INITIAL_LOCATION, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; import { MapSettings } from './types'; diff --git a/x-pack/plugins/ml/public/mocks.ts b/x-pack/plugins/ml/public/mocks.ts index c48e56974f488..6dc2253c94000 100644 --- a/x-pack/plugins/ml/public/mocks.ts +++ b/x-pack/plugins/ml/public/mocks.ts @@ -6,33 +6,17 @@ */ import { MlPluginSetup, MlPluginStart } from './plugin'; +import { sharePluginMock } from '../../../../src/plugins/share/public/mocks'; + const createSetupContract = (): jest.Mocked => { return { - locator: { - getLocation: jest.fn(), - getUrl: jest.fn(), - useUrl: jest.fn(), - navigate: jest.fn(), - extract: jest.fn(), - inject: jest.fn(), - telemetry: jest.fn(), - migrations: {}, - }, + locator: sharePluginMock.createLocator(), }; }; const createStartContract = (): jest.Mocked => { return { - locator: { - getLocation: jest.fn(), - getUrl: jest.fn(), - useUrl: jest.fn(), - navigate: jest.fn(), - extract: jest.fn(), - inject: jest.fn(), - telemetry: jest.fn(), - migrations: {}, - }, + locator: sharePluginMock.createLocator(), }; }; diff --git a/x-pack/plugins/monitoring/server/lib/metrics/__snapshots__/metrics.test.js.snap b/x-pack/plugins/monitoring/server/lib/metrics/__snapshots__/metrics.test.js.snap index 674e826b579e5..56d923da56202 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/__snapshots__/metrics.test.js.snap +++ b/x-pack/plugins/monitoring/server/lib/metrics/__snapshots__/metrics.test.js.snap @@ -3787,6 +3787,7 @@ Object { "format": "0,0.[00]", "getDateHistogramSubAggs": [Function], "label": "Pipeline Throughput", + "mbField": "logstash.node.stats.pipelines.events.out", "timestampField": "logstash_stats.timestamp", "units": "e/s", "uuidField": "logstash_stats.logstash.uuid", diff --git a/x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.js b/x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.js index cd518804eeb67..a140cd7bcc370 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.js @@ -439,6 +439,7 @@ export const metrics = { }), logstash_node_pipeline_throughput: new LogstashPipelineThroughputMetric({ uuidField: 'logstash_stats.logstash.uuid', // TODO: add comment explaining why + mbField: 'logstash.node.stats.pipelines.events.out', field: 'logstash_stats.pipelines.events.out', label: pipelineThroughputLabel, description: pipelineThroughputDescription, diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx index f713af9768229..aca29c4723688 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx @@ -71,6 +71,7 @@ export function FieldValueSelection({ selectedValue, excludedValue, compressed = true, + allowExclusions = true, onChange: onSelectionChange, }: FieldValueSelectionProps) { const [options, setOptions] = useState(() => @@ -142,7 +143,7 @@ export function FieldValueSelection({ .filter((opt) => opt?.checked === 'off') .map(({ label: labelN }) => labelN); - return isEqual(selectedValue ?? [], currSelected) && isEqual(excludedValue, currExcluded); + return isEqual(selectedValue ?? [], currSelected) && isEqual(excludedValue ?? [], currExcluded); }; return ( @@ -174,7 +175,7 @@ export function FieldValueSelection({ options={options} onChange={onChange} isLoading={loading && !query && options.length === 0} - allowExclusions={true} + allowExclusions={allowExclusions} > {(list, search) => (
@@ -190,6 +191,13 @@ export function FieldValueSelection({ )} ); } diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts index d857b39b074ac..046f98748cdf2 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts @@ -24,6 +24,9 @@ interface CommonProps { asFilterButton?: boolean; showCount?: boolean; allowAllValuesSelection?: boolean; + cardinalityField?: string; + required?: boolean; + allowExclusions?: boolean; } export type FieldValueSuggestionsProps = CommonProps & { diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts index 46d89a062f072..5aa7dd672cfda 100644 --- a/x-pack/plugins/observability/public/hooks/use_values_list.ts +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -18,6 +18,7 @@ export interface Props { filters?: ESFilter[]; time?: { from: string; to: string }; keepHistory?: boolean; + cardinalityField?: string; } export interface ListItem { @@ -32,6 +33,7 @@ export const useValuesList = ({ filters, time, keepHistory, + cardinalityField, }: Props): { values: ListItem[]; loading?: boolean } => { const [debouncedQuery, setDebounceQuery] = useState(query); const [values, setValues] = useState([]); @@ -93,9 +95,20 @@ export const useValuesList = ({ values: { terms: { field: sourceField, - size: 100, + size: 50, ...(query ? { include: includeClause } : {}), }, + ...(cardinalityField + ? { + aggs: { + count: { + cardinality: { + field: cardinalityField, + }, + }, + }, + } + : {}), }, }, }, @@ -105,10 +118,20 @@ export const useValuesList = ({ useEffect(() => { const newValues = - data?.aggregations?.values.buckets.map(({ key: value, doc_count: count }) => ({ - count, - label: String(value), - })) ?? []; + data?.aggregations?.values.buckets.map( + ({ key: value, doc_count: count, count: aggsCount }) => { + if (aggsCount) { + return { + count: aggsCount.value, + label: String(value), + }; + } + return { + count, + label: String(value), + }; + } + ) ?? []; if (keepHistory && query) { setValues((prevState) => { diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx index bca8c8095511e..9b6e9e249346e 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx @@ -277,6 +277,7 @@ function ObservabilityActions({ iconType="boxesHorizontal" aria-label="More" onClick={() => toggleActionsPopover(eventId)} + data-test-subj="alerts-table-row-action-more" /> } isOpen={openActionsPopoverId === eventId} diff --git a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx b/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx index cc16f1c5a44a1..f7441742ff387 100644 --- a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx @@ -28,7 +28,7 @@ describe('StatusFilter', () => { const props = { onChange, status }; const { getByTestId } = render(); - const button = getByTestId(`WorkflowStatusFilter ${status} button`); + const button = getByTestId(`workflow-status-filter-${status}-button`); const input = button.querySelector('input') as Element; Simulate.change(input); diff --git a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx b/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx index 475ba17a9d2f5..20073e9937b4f 100644 --- a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx @@ -21,7 +21,7 @@ const options: Array = label: i18n.translate('xpack.observability.alerts.workflowStatusFilter.openButtonLabel', { defaultMessage: 'Open', }), - 'data-test-subj': 'WorkflowStatusFilter open button', + 'data-test-subj': 'workflow-status-filter-open-button', }, { id: 'acknowledged', @@ -31,14 +31,14 @@ const options: Array = defaultMessage: 'Acknowledged', } ), - 'data-test-subj': 'WorkflowStatusFilter acknowledged button', + 'data-test-subj': 'workflow-status-filter-acknowledged-button', }, { id: 'closed', label: i18n.translate('xpack.observability.alerts.workflowStatusFilter.closedButtonLabel', { defaultMessage: 'Closed', }), - 'data-test-subj': 'WorkflowStatusFilter closed button', + 'data-test-subj': 'workflow-status-filter-closed-button', }, ]; diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 48f3a81a00af2..2799cd6b2c7d5 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -296,7 +296,7 @@ export const createLifecycleExecutor = ( const nextTrackedAlerts = Object.fromEntries( allEventsToIndex - .filter(({ event }) => event[ALERT_STATUS] !== 'closed') + .filter(({ event }) => event[ALERT_STATUS] !== ALERT_STATUS_RECOVERED) .map(({ event }) => { const alertId = event[ALERT_INSTANCE_ID]!; const alertUuid = event[ALERT_UUID]!; diff --git a/x-pack/plugins/security/public/components/token_field.tsx b/x-pack/plugins/security/public/components/token_field.tsx index 98eee9352937c..38a8e45cbb5b5 100644 --- a/x-pack/plugins/security/public/components/token_field.tsx +++ b/x-pack/plugins/security/public/components/token_field.tsx @@ -22,7 +22,7 @@ import type { FunctionComponent, ReactElement } from 'react'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { euiThemeVars } from '@kbn/ui-shared-deps/theme'; +import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme'; export interface TokenFieldProps extends Omit { value: string; diff --git a/x-pack/plugins/security/public/plugin.test.tsx b/x-pack/plugins/security/public/plugin.test.tsx index bcc69bbfbd853..258b0ef9ec6f5 100644 --- a/x-pack/plugins/security/public/plugin.test.tsx +++ b/x-pack/plugins/security/public/plugin.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import BroadcastChannel from 'broadcast-channel'; +import { enforceOptions } from 'broadcast-channel'; import { Observable } from 'rxjs'; import type { CoreSetup } from 'src/core/public'; @@ -22,10 +22,10 @@ import { SecurityPlugin } from './plugin'; describe('Security Plugin', () => { beforeAll(() => { - BroadcastChannel.enforceOptions({ type: 'simulate' }); + enforceOptions({ type: 'simulate' }); }); afterAll(() => { - BroadcastChannel.enforceOptions(null); + enforceOptions(null); }); describe('#setup', () => { 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 2ee2337fc9aeb..5ab79e72d7274 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`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; +exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; -exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; +exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; 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 2e7f3d49e478f..8e6d6a2677787 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`] = `"ElasticMockedFonts

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`] = `"ElasticMockedFonts

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 8d31770cd9385..97cfcd47ade8d 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`] = `"ElasticMockedFonts

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`] = `"ElasticMockedFonts

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/server/prompt_page.tsx b/x-pack/plugins/security/server/prompt_page.tsx index 338d39b29e534..eb26e1a4380ae 100644 --- a/x-pack/plugins/security/server/prompt_page.tsx +++ b/x-pack/plugins/security/server/prompt_page.tsx @@ -18,7 +18,8 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; -import * as UiSharedDeps from '@kbn/ui-shared-deps'; +import UiSharedDepsNpm from '@kbn/ui-shared-deps-npm'; +import UiSharedDepsSrc from '@kbn/ui-shared-deps-src'; import type { IBasePath } from 'src/core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -51,8 +52,8 @@ export function PromptPage({ const uiPublicURL = `${basePath.serverBasePath}/ui`; const regularBundlePath = `${basePath.serverBasePath}/${buildNumber}/bundles`; const styleSheetPaths = [ - `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, - `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`, + `${regularBundlePath}/kbn-ui-shared-deps-src/${UiSharedDepsSrc.cssDistFilename}`, + `${regularBundlePath}/kbn-ui-shared-deps-npm/${UiSharedDepsNpm.lightCssDistFilename}`, `${basePath.serverBasePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, `${basePath.serverBasePath}/ui/legacy_light_theme.css`, ]; diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts index 96f8c5ff02d24..59a2a38cd42f1 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts @@ -12,6 +12,7 @@ import type { SavedObject, SavedObjectReferenceWithContext, SavedObjectsClientContract, + SavedObjectsResolveResponse, SavedObjectsUpdateObjectsSpacesResponseObject, } from 'src/core/server'; import { httpServerMock, savedObjectsClientMock } from 'src/core/server/mocks'; @@ -465,6 +466,103 @@ describe('#bulkGet', () => { }); }); +describe('#bulkResolve', () => { + const obj1 = Object.freeze({ type: 'foo', id: 'foo-id' }); + const obj2 = Object.freeze({ type: 'bar', id: 'bar-id' }); + const namespace = 'some-ns'; + + test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { + const objects = [obj1]; + await expectGeneralError(client.bulkResolve, { objects }); + }); + + test(`throws decorated ForbiddenError when unauthorized`, async () => { + const objects = [obj1, obj2]; + const options = { namespace }; + await expectForbiddenError(client.bulkResolve, { objects, options }, 'bulk_resolve'); + }); + + test(`returns result of baseClient.bulkResolve when authorized`, async () => { + const apiCallReturnValue = { resolved_objects: [] }; + clientOpts.baseClient.bulkResolve.mockResolvedValue(apiCallReturnValue); + + const objects = [obj1, obj2]; + const options = { namespace }; + const result = await expectSuccess(client.bulkResolve, { objects, options }, 'bulk_resolve'); + expect(result).toEqual(apiCallReturnValue); + }); + + test(`checks privileges for user, actions, and namespace`, async () => { + const objects = [obj1, obj2]; + const options = { namespace }; + await expectPrivilegeCheck(client.bulkResolve, { objects, options }, namespace); + }); + + test(`filters namespaces that the user doesn't have access to`, async () => { + const objects = [obj1, obj2]; + const options = { namespace }; + + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementationOnce( + getMockCheckPrivilegesSuccess // privilege check for authorization + ); + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( + getMockCheckPrivilegesFailure // privilege check for namespace filtering + ); + + clientOpts.baseClient.bulkResolve.mockResolvedValue({ + resolved_objects: [ + // omit other fields from the SavedObjectsResolveResponse such as outcome, as they are not needed for this test case + ({ saved_object: { namespaces: ['*'] } } as unknown) as SavedObjectsResolveResponse, + ({ saved_object: { namespaces: [namespace] } } as unknown) as SavedObjectsResolveResponse, + ({ + saved_object: { namespaces: ['some-other-namespace', namespace] }, + } as unknown) as SavedObjectsResolveResponse, + ], + }); + + const result = await client.bulkResolve(objects, options); + expect(result).toEqual({ + resolved_objects: [ + { saved_object: { namespaces: ['*'] } }, + { saved_object: { namespaces: [namespace] } }, + { saved_object: { namespaces: [namespace, '?'] } }, + ], + }); + + expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes(2); + expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenLastCalledWith( + 'login:', + ['some-other-namespace'] + // when we check what namespaces to redact, we don't check privileges for '*', only actual space IDs + // we don't check privileges for authorizedNamespaces either, as that was already checked earlier in the operation + ); + }); + + test(`adds audit event when successful`, async () => { + const apiCallReturnValue = { + resolved_objects: [ + ({ saved_object: obj1 } as unknown) as SavedObjectsResolveResponse, + ({ saved_object: obj2 } as unknown) as SavedObjectsResolveResponse, + ], + }; + clientOpts.baseClient.bulkResolve.mockResolvedValue(apiCallReturnValue); + const objects = [obj1, obj2]; + const options = { namespace }; + await expectSuccess(client.bulkResolve, { objects, options }, 'bulk_resolve'); + expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); + expectAuditEvent('saved_object_resolve', 'success', obj1); + expectAuditEvent('saved_object_resolve', 'success', obj2); + }); + + test(`adds audit event when not successful`, async () => { + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); + await expect(() => client.bulkResolve([obj1, obj2], { namespace })).rejects.toThrow(); + expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); + expectAuditEvent('saved_object_resolve', 'failure', obj1); + expectAuditEvent('saved_object_resolve', 'failure', obj2); + }); +}); + describe('#bulkUpdate', () => { const obj1 = Object.freeze({ type: 'foo', id: 'foo-id', attributes: { some: 'attr' } }); const obj2 = Object.freeze({ type: 'bar', id: 'bar-id', attributes: { other: 'attr' } }); diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index 11eca287cd4f5..b0428be87a4f2 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -11,6 +11,7 @@ import type { SavedObjectsBaseOptions, SavedObjectsBulkCreateObject, SavedObjectsBulkGetObject, + SavedObjectsBulkResolveObject, SavedObjectsBulkUpdateObject, SavedObjectsCheckConflictsObject, SavedObjectsClientContract, @@ -356,6 +357,78 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra return await this.redactSavedObjectNamespaces(savedObject, [options.namespace]); } + public async bulkResolve( + objects: SavedObjectsBulkResolveObject[], + options: SavedObjectsBaseOptions = {} + ) { + try { + const args = { objects, options }; + await this.legacyEnsureAuthorized( + this.getUniqueObjectTypes(objects), + 'bulk_get', + options.namespace, + { args, auditAction: 'bulk_resolve' } + ); + } catch (error) { + objects.forEach(({ type, id }) => + this.auditLogger.log( + savedObjectEvent({ + action: SavedObjectAction.RESOLVE, + savedObject: { type, id }, + error, + }) + ) + ); + throw error; + } + + const response = await this.baseClient.bulkResolve(objects, options); + + response.resolved_objects.forEach(({ saved_object: { error, type, id } }) => { + if (!error) { + this.auditLogger.log( + savedObjectEvent({ + action: SavedObjectAction.RESOLVE, + savedObject: { type, id }, + }) + ); + } + }); + + // the generic redactSavedObjectsNamespaces function cannot be used here due to the nested structure of the + // resolved objects, so we handle redaction in a bespoke manner for bulkResolve + + if (this.getSpacesService() === undefined) { + return response; + } + + const previouslyAuthorizedSpaceIds = [ + this.getSpacesService()!.namespaceToSpaceId(options.namespace), + ]; + // all users can see the "all spaces" ID, and we don't need to recheck authorization for any namespaces that we just checked earlier + const namespaces = uniq( + response.resolved_objects.flatMap((resolved) => resolved.saved_object.namespaces || []) + ).filter((x) => x !== ALL_SPACES_ID && !previouslyAuthorizedSpaceIds.includes(x)); + + const privilegeMap = await this.getNamespacesPrivilegeMap( + namespaces, + previouslyAuthorizedSpaceIds + ); + + return { + ...response, + resolved_objects: response.resolved_objects.map((resolved) => ({ + ...resolved, + saved_object: { + ...resolved.saved_object, + namespaces: + resolved.saved_object.namespaces && + this.redactAndSortNamespaces(resolved.saved_object.namespaces, privilegeMap), + }, + })), + }; + } + public async resolve( type: string, id: string, @@ -1030,6 +1103,8 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra response: T, previouslyAuthorizedNamespaces: Array ): Promise { + // WARNING: the bulkResolve function has a bespoke implementation of this; any changes here should be applied there too. + if (this.getSpacesService() === undefined) { return response; } diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts index 85c6ce74763b2..20fbaa46028c8 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts @@ -93,6 +93,32 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; }); }); + describe('#bulkResolve', () => { + test(`throws error if options.namespace is specified`, async () => { + const { client } = createSpacesSavedObjectsClient(); + + await expect(client.bulkResolve([], { namespace: 'bar' })).rejects.toThrow( + ERROR_NAMESPACE_SPECIFIED + ); + }); + + test(`supplements options with the current namespace`, async () => { + const { client, baseClient } = createSpacesSavedObjectsClient(); + const expectedReturnValue = { resolved_objects: [] }; + baseClient.bulkResolve.mockReturnValue(Promise.resolve(expectedReturnValue)); + + const options = Object.freeze({ foo: 'bar' }); + // @ts-expect-error + const actualReturnValue = await client.bulkResolve([], options); + + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.bulkResolve).toHaveBeenCalledWith([], { + foo: 'bar', + namespace: currentSpace.expectedNamespace, + }); + }); + }); + describe('#resolve', () => { test(`throws error if options.namespace is specified`, async () => { const { client } = createSpacesSavedObjectsClient(); diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts index 6cfd784042317..e2e3e856f74f0 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts @@ -13,6 +13,7 @@ import type { SavedObjectsBaseOptions, SavedObjectsBulkCreateObject, SavedObjectsBulkGetObject, + SavedObjectsBulkResolveObject, SavedObjectsBulkUpdateObject, SavedObjectsCheckConflictsObject, SavedObjectsClientContract, @@ -92,14 +93,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { this.errors = baseClient.errors; } - /** - * Check what conflicts will result when creating a given array of saved objects. This includes "unresolvable conflicts", which are - * multi-namespace objects that exist in a different namespace; such conflicts cannot be resolved/overwritten. - * - * @param objects - * @param options - */ - public async checkConflicts( + async checkConflicts( objects: SavedObjectsCheckConflictsObject[] = [], options: SavedObjectsBaseOptions = {} ) { @@ -111,18 +105,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Persists an object - * - * @param {string} type - * @param {object} attributes - * @param {object} [options={}] - * @property {string} [options.id] - force id on creation, not recommended - * @property {boolean} [options.overwrite=false] - * @property {string} [options.namespace] - * @returns {promise} - { id, type, version, attributes } - */ - public async create( + async create( type: string, attributes: T = {} as T, options: SavedObjectsCreateOptions = {} @@ -135,16 +118,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Creates multiple documents at once - * - * @param {array} objects - [{ type, id, attributes }] - * @param {object} [options={}] - * @property {boolean} [options.overwrite=false] - overwrites existing documents - * @property {string} [options.namespace] - * @returns {promise} - { saved_objects: [{ id, type, version, attributes, error: { message } }]} - */ - public async bulkCreate( + async bulkCreate( objects: Array>, options: SavedObjectsBaseOptions = {} ) { @@ -156,16 +130,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Deletes an object - * - * @param {string} type - * @param {string} id - * @param {object} [options={}] - * @property {string} [options.namespace] - * @returns {promise} - */ - public async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}) { + async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}) { throwErrorIfNamespaceSpecified(options); return await this.client.delete(type, id, { @@ -174,23 +139,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * @param {object} [options={}] - * @property {(string|Array)} [options.type] - * @property {string} [options.search] - * @property {string} [options.defaultSearchOperator] - * @property {Array} [options.searchFields] - see Elasticsearch Simple Query String - * Query field argument for more information - * @property {integer} [options.page=1] - * @property {integer} [options.perPage=20] - * @property {string} [options.sortField] - * @property {string} [options.sortOrder] - * @property {Array} [options.fields] - * @property {string} [options.namespaces] - * @property {object} [options.hasReference] - { type, id } - * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } - */ - public async find(options: SavedObjectsFindOptions) { + async find(options: SavedObjectsFindOptions) { let namespaces: string[]; try { namespaces = await this.getSearchableSpaces(options.namespaces); @@ -215,21 +164,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Returns an array of objects by id - * - * @param {array} objects - an array ids, or an array of objects containing id and optionally type - * @param {object} [options={}] - * @property {string} [options.namespace] - * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } - * @example - * - * bulkGet([ - * { id: 'one', type: 'config' }, - * { id: 'foo', type: 'index-pattern' } - * ]) - */ - public async bulkGet( + async bulkGet( objects: SavedObjectsBulkGetObject[] = [], options: SavedObjectsBaseOptions = {} ) { @@ -292,16 +227,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }; } - /** - * Gets a single object - * - * @param {string} type - * @param {string} id - * @param {object} [options={}] - * @property {string} [options.namespace] - * @returns {promise} - { id, type, version, attributes } - */ - public async get(type: string, id: string, options: SavedObjectsBaseOptions = {}) { + async get(type: string, id: string, options: SavedObjectsBaseOptions = {}) { throwErrorIfNamespaceSpecified(options); return await this.client.get(type, id, { @@ -310,39 +236,28 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Resolves a single object, using any legacy URL alias if it exists - * - * @param type - The type of SavedObject to retrieve - * @param id - The ID of the SavedObject to retrieve - * @param {object} [options={}] - * @property {string} [options.namespace] - * @returns {promise} - { saved_object, outcome } - */ - public async resolve( - type: string, - id: string, + async bulkResolve( + objects: SavedObjectsBulkResolveObject[], options: SavedObjectsBaseOptions = {} ) { throwErrorIfNamespaceSpecified(options); + return await this.client.bulkResolve(objects, { + ...options, + namespace: spaceIdToNamespace(this.spaceId), + }); + } + + async resolve(type: string, id: string, options: SavedObjectsBaseOptions = {}) { + throwErrorIfNamespaceSpecified(options); + return await this.client.resolve(type, id, { ...options, namespace: spaceIdToNamespace(this.spaceId), }); } - /** - * Updates an object - * - * @param {string} type - * @param {string} id - * @param {object} [options={}] - * @property {string} options.version - ensures version matches that of persisted object - * @property {string} [options.namespace] - * @returns {promise} - */ - public async update( + async update( type: string, id: string, attributes: Partial, @@ -356,19 +271,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Updates an array of objects by id - * - * @param {array} objects - an array ids, or an array of objects containing id, type, attributes and optionally version, references and namespace - * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } - * @example - * - * bulkUpdate([ - * { id: 'one', type: 'config', attributes: { title: 'My new title'}, version: 'd7rhfk47d=' }, - * { id: 'foo', type: 'index-pattern', attributes: {} } - * ]) - */ - public async bulkUpdate( + async bulkUpdate( objects: Array> = [], options: SavedObjectsBaseOptions = {} ) { @@ -379,14 +282,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Remove outward references to given object. - * - * @param type - * @param id - * @param options - */ - public async removeReferencesTo( + async removeReferencesTo( type: string, id: string, options: SavedObjectsRemoveReferencesToOptions = {} @@ -398,13 +294,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Gets all references and transitive references of the listed objects. Ignores any object that is not a multi-namespace type. - * - * @param objects - * @param options - */ - public async collectMultiNamespaceReferences( + async collectMultiNamespaceReferences( objects: SavedObjectsCollectMultiNamespaceReferencesObject[], options: SavedObjectsCollectMultiNamespaceReferencesOptions = {} ): Promise { @@ -415,15 +305,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Updates one or more objects to add and/or remove them from specified spaces. - * - * @param objects - * @param spacesToAdd - * @param spacesToRemove - * @param options - */ - public async updateObjectsSpaces( + async updateObjectsSpaces( objects: SavedObjectsUpdateObjectsSpacesObject[], spacesToAdd: string[], spacesToRemove: string[], @@ -436,16 +318,6 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Opens a Point In Time (PIT) against the indices for the specified Saved Object types. - * The returned `id` can then be passed to `SavedObjects.find` to search against that PIT. - * - * @param {string|Array} type - * @param {object} [options] - {@link SavedObjectsOpenPointInTimeOptions} - * @property {string} [options.keepAlive] - * @property {string} [options.preference] - * @returns {promise} - { id: string } - */ async openPointInTimeForType( type: string | string[], options: SavedObjectsOpenPointInTimeOptions = {} @@ -471,15 +343,6 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Closes a Point In Time (PIT) by ID. This simply proxies the request to ES - * via the Elasticsearch client, and is included in the Saved Objects Client - * as a convenience for consumers who are using `openPointInTimeForType`. - * - * @param {string} id - ID returned from `openPointInTimeForType` - * @param {object} [options] - {@link SavedObjectsClosePointInTimeOptions} - * @returns {promise} - { succeeded: boolean; num_freed: number } - */ async closePointInTime(id: string, options: SavedObjectsClosePointInTimeOptions = {}) { throwErrorIfNamespaceSpecified(options); return await this.client.closePointInTime(id, { @@ -488,17 +351,6 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Returns a generator to help page through large sets of saved objects. - * - * The generator wraps calls to `SavedObjects.find` and iterates over - * multiple pages of results using `_pit` and `search_after`. This will - * open a new Point In Time (PIT), and continue paging until a set of - * results is received that's smaller than the designated `perPage`. - * - * @param {object} findOptions - {@link SavedObjectsCreatePointInTimeFinderOptions} - * @param {object} [dependencies] - {@link SavedObjectsCreatePointInTimeFinderDependencies} - */ createPointInTimeFinder( findOptions: SavedObjectsCreatePointInTimeFinderOptions, dependencies?: SavedObjectsCreatePointInTimeFinderDependencies diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx index 2b58487fce53a..eb185792c152f 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx @@ -17,7 +17,7 @@ import { addBuildingBlockStyle, } from './helpers'; -import { euiThemeVars } from '@kbn/ui-shared-deps/theme'; +import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme'; import { mockDnsEvent } from '../../../mock'; describe('helpers', () => { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e3fef367766b5..f49d2d674330f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10150,7 +10150,6 @@ "xpack.enterpriseSearch.workplaceSearch.organizationStats.activeUsers": "アクティブなユーザー", "xpack.enterpriseSearch.workplaceSearch.organizationStats.invitations": "招待", "xpack.enterpriseSearch.workplaceSearch.organizationStats.privateSources": "プライベートソース", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.sharedSources": "共有ソース", "xpack.enterpriseSearch.workplaceSearch.organizationStats.title": "使用統計情報", "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.buttonLabel": "組織名を指定", "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.description": "同僚を招待する前に、組織名を指定し、認識しやすくしてください。", @@ -10165,7 +10164,6 @@ "xpack.enterpriseSearch.workplaceSearch.overviewUsersCard.title": "検索できるように、同僚を招待しました。", "xpack.enterpriseSearch.workplaceSearch.personalDashboardSourceError": "ソースに接続できませんでした。ヘルプについては管理者に問い合わせてください。エラーメッセージ:{error}", "xpack.enterpriseSearch.workplaceSearch.platinumFeature": "プラチナ機能", - "xpack.enterpriseSearch.workplaceSearch.privateDashboard.readOnlyMode.warning": "現在、定期メンテナンス中のため、Workplace Searchは検索でのみ使用できます。詳細については、システム管理者に連絡してください。", "xpack.enterpriseSearch.workplaceSearch.privatePlatinumCallout.text": "非公開ソースにはプラチナライセンスが必要です。", "xpack.enterpriseSearch.workplaceSearch.privateSource.text": "非公開ソース", "xpack.enterpriseSearch.workplaceSearch.privateSources.text": "非公開ソース", @@ -10325,7 +10323,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.private.header.description": "非公開コンテンツソースは自分のみが使用できます。", "xpack.enterpriseSearch.workplaceSearch.sources.private.header.title": "自分の非公開コンテンツソース", "xpack.enterpriseSearch.workplaceSearch.sources.private.link": "非公開コンテンツソースを追加", - "xpack.enterpriseSearch.workplaceSearch.sources.private.privateShared.header.title": "共有コンテンツソース", "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.description": "グループと共有するすべてのソースのステータスを確認します。", "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.title": "グループソースの確認", "xpack.enterpriseSearch.workplaceSearch.sources.ready.text": "検索できます", @@ -10336,8 +10333,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.settings.heading": "設定", "xpack.enterpriseSearch.workplaceSearch.sources.settings.title": "コンテンツソース名", "xpack.enterpriseSearch.workplaceSearch.sources.settingsModal.text": "ソースドキュメントはWorkplace Searchから削除されます。{lineBreak}{name}を削除しますか?", - "xpack.enterpriseSearch.workplaceSearch.sources.shared.empty.description": "コンテンツソースが自分と共有されたら、ここに表示され、検索エクスペリエンスで使用できます。", - "xpack.enterpriseSearch.workplaceSearch.sources.shared.empty.title": "コンテンツソースがありません", "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.title": "ソースコンテンツ", "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.button": "プラチナライセンスの詳細", "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.description": "組織のライセンスレベルが変更されました。データは安全ですが、ドキュメントレベルのアクセス権はサポートされなくなり、このソースの検索は無効になっています。このソースを再有効化するには、プラチナライセンスにアップグレードしてください。", @@ -26159,10 +26154,6 @@ "xpack.uptime.filterBar.options.portLabel": "ポート", "xpack.uptime.filterBar.options.schemeLabel": "スキーム", "xpack.uptime.filterBar.options.tagsLabel": "タグ", - "xpack.uptime.filterPopout.loadingMessage": "読み込み中...", - "xpack.uptime.filterPopout.searchMessage": "{title} の検索", - "xpack.uptime.filterPopout.searchMessage.ariaLabel": "{title} を検索", - "xpack.uptime.filterPopover.filterItem.label": "{title} {item}でフィルタリングします。", "xpack.uptime.fleetIntegration.assets.description": "アップタイムでモニターを表示", "xpack.uptime.fleetIntegration.assets.name": "監視", "xpack.uptime.integrationLink.missingDataMessage": "この統合に必要なデータが見つかりませんでした。", @@ -26313,7 +26304,6 @@ "xpack.uptime.overview.alerts.enabled.failed": "ルールを有効にできません。", "xpack.uptime.overview.alerts.enabled.success": "ルールが正常に有効にされました。 ", "xpack.uptime.overview.alerts.enabled.success.description": "この監視が停止しているときには、メッセージが {actionConnectors} に送信されます。", - "xpack.uptime.overview.filterButton.label": "{title}フィルターのフィルターグループを展開", "xpack.uptime.overview.heading": "監視", "xpack.uptime.overview.pageHeader.syntheticsCallout.announcementLink": "お知らせを読む", "xpack.uptime.overview.pageHeader.syntheticsCallout.content": "アップタイムは、スクリプト化された複数ステップの可用性チェックのサポートをプレビューしています。つまり、単に単一のページのアップ/ダウンのチェックだけではなく、Webページの要素を操作したり、全体的な可用性を確認したりできます(購入やシステムへのサインインなど)。詳細については以下をクリックしてください。これらの機能を先駆けて使用したい場合は、プレビュー合成エージェントをダウンロードし、アップタイムでチェックを表示できます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 786eb5bef3286..85e11905e5eb7 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10251,7 +10251,6 @@ "xpack.enterpriseSearch.workplaceSearch.organizationStats.activeUsers": "活动用户", "xpack.enterpriseSearch.workplaceSearch.organizationStats.invitations": "邀请", "xpack.enterpriseSearch.workplaceSearch.organizationStats.privateSources": "专用源", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.sharedSources": "共享源", "xpack.enterpriseSearch.workplaceSearch.organizationStats.title": "使用统计", "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.buttonLabel": "命名您的组织", "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.description": "在邀请同事之前,请命名您的组织以提升辨识度。", @@ -10266,7 +10265,6 @@ "xpack.enterpriseSearch.workplaceSearch.overviewUsersCard.title": "很好,您已邀请同事一同搜索。", "xpack.enterpriseSearch.workplaceSearch.personalDashboardSourceError": "无法连接源,请联系管理员以获取帮助。错误消息:{error}", "xpack.enterpriseSearch.workplaceSearch.platinumFeature": "白金级功能", - "xpack.enterpriseSearch.workplaceSearch.privateDashboard.readOnlyMode.warning": "由于常规维护,Workplace Search 当前仅可供搜索。请与您的系统管理员联系,以获取更多信息。", "xpack.enterpriseSearch.workplaceSearch.privatePlatinumCallout.text": "专用源需要白金级许可证。", "xpack.enterpriseSearch.workplaceSearch.privateSource.text": "专用源", "xpack.enterpriseSearch.workplaceSearch.privateSources.text": "专用源", @@ -10426,8 +10424,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.private.header.description": "专用源仅供您使用。", "xpack.enterpriseSearch.workplaceSearch.sources.private.header.title": "我的专用内容源", "xpack.enterpriseSearch.workplaceSearch.sources.private.link": "添加专用内容源", - "xpack.enterpriseSearch.workplaceSearch.sources.private.privateShared.header.description": "您可以通过{groups, plural, other {组}} {groupsSentence}{newline}访问以下源。", - "xpack.enterpriseSearch.workplaceSearch.sources.private.privateShared.header.title": "共享内容源", "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.description": "查看与您的组共享的所有源的状态。", "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.title": "查看组源", "xpack.enterpriseSearch.workplaceSearch.sources.ready.text": "可供搜索", @@ -10438,8 +10434,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.settings.heading": "设置", "xpack.enterpriseSearch.workplaceSearch.sources.settings.title": "内容源名称", "xpack.enterpriseSearch.workplaceSearch.sources.settingsModal.text": "将从 Workplace Search 中删除您的源文档。{lineBreak}确定要移除 {name}?", - "xpack.enterpriseSearch.workplaceSearch.sources.shared.empty.description": "内容源共享给您后,其将显示在此处,并可通过搜索体验获取。", - "xpack.enterpriseSearch.workplaceSearch.sources.shared.empty.title": "没有可用的内容源", "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.title": "源内容", "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.button": "了解白金级许可证", "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.description": "您的组织的许可证级别已更改。您的数据是安全的,但不再支持文档级别权限,且已禁止搜索此源。升级到白金级许可证,以重新启用此源。", @@ -26595,10 +26589,6 @@ "xpack.uptime.filterBar.options.portLabel": "端口", "xpack.uptime.filterBar.options.schemeLabel": "方案", "xpack.uptime.filterBar.options.tagsLabel": "标签", - "xpack.uptime.filterPopout.loadingMessage": "正在加载……", - "xpack.uptime.filterPopout.searchMessage": "搜索 {title}", - "xpack.uptime.filterPopout.searchMessage.ariaLabel": "搜索 {title}", - "xpack.uptime.filterPopover.filterItem.label": "按 {title} {item} 筛选。", "xpack.uptime.fleetIntegration.assets.description": "在 Uptime 中查看监测", "xpack.uptime.fleetIntegration.assets.name": "监测", "xpack.uptime.integrationLink.missingDataMessage": "未找到此集成的所需数据。", @@ -26749,7 +26739,6 @@ "xpack.uptime.overview.alerts.enabled.failed": "无法启用规则!", "xpack.uptime.overview.alerts.enabled.success": "已成功启用规则 ", "xpack.uptime.overview.alerts.enabled.success.description": "此监测关闭时,将有消息发送到 {actionConnectors}。", - "xpack.uptime.overview.filterButton.label": "展开筛选 {title} 的筛选组", "xpack.uptime.overview.heading": "监测", "xpack.uptime.overview.pageHeader.syntheticsCallout.announcementLink": "阅读公告", "xpack.uptime.overview.pageHeader.syntheticsCallout.content": "Uptime 现在正在预览对脚本化多步骤可用性检查的支持。这意味着您可以与网页元素进行交互,并检查整个过程(例如购买或登录系统)的可用性,而不仅仅是简单的单个页面启动/关闭检查。请单击下面的内容以了解详情,如果您想率先使用这些功能,则可以下载我们的预览组合代理,并在 Uptime 中查看组合检查。", diff --git a/x-pack/plugins/uptime/common/constants/rest_api.ts b/x-pack/plugins/uptime/common/constants/rest_api.ts index 655f9629b848b..52b0620586eb4 100644 --- a/x-pack/plugins/uptime/common/constants/rest_api.ts +++ b/x-pack/plugins/uptime/common/constants/rest_api.ts @@ -6,7 +6,6 @@ */ export enum API_URLS { - INDEX_PATTERN = `/api/uptime/index_pattern`, INDEX_STATUS = '/api/uptime/index_status', MONITOR_LIST = `/api/uptime/monitor/list`, MONITOR_LOCATIONS = `/api/uptime/monitor/locations`, @@ -16,7 +15,6 @@ export enum API_URLS { PINGS = '/api/uptime/pings', PING_HISTOGRAM = `/api/uptime/ping/histogram`, SNAPSHOT_COUNT = `/api/uptime/snapshot/count`, - FILTERS = `/api/uptime/filters`, LOG_PAGE_VIEW = `/api/uptime/log_page_view`, ML_MODULE_JOBS = `/api/ml/modules/jobs_exist/`, diff --git a/x-pack/plugins/uptime/common/constants/ui.ts b/x-pack/plugins/uptime/common/constants/ui.ts index dcaf4bb310ad7..29df2614d0617 100644 --- a/x-pack/plugins/uptime/common/constants/ui.ts +++ b/x-pack/plugins/uptime/common/constants/ui.ts @@ -67,3 +67,10 @@ export enum CERT_STATUS { } export const KQL_SYNTAX_LOCAL_STORAGE = 'xpack.uptime.kql.syntax'; + +export const FILTER_FIELDS = { + TAGS: 'tags', + PORT: 'url.port', + LOCATION: 'observer.geo.name', + TYPE: 'monitor.type', +}; diff --git a/x-pack/plugins/uptime/common/runtime_types/index.ts b/x-pack/plugins/uptime/common/runtime_types/index.ts index 51dacd2f4e9b6..1c1b05cddcd44 100644 --- a/x-pack/plugins/uptime/common/runtime_types/index.ts +++ b/x-pack/plugins/uptime/common/runtime_types/index.ts @@ -10,7 +10,6 @@ export * from './certs'; export * from './common'; export * from './dynamic_settings'; export * from './monitor'; -export * from './overview_filters'; export * from './ping'; export * from './snapshot'; export * from './network_events'; diff --git a/x-pack/plugins/uptime/common/runtime_types/overview_filters/overview_filters.ts b/x-pack/plugins/uptime/common/runtime_types/overview_filters/overview_filters.ts deleted file mode 100644 index e3610a98f5ceb..0000000000000 --- a/x-pack/plugins/uptime/common/runtime_types/overview_filters/overview_filters.ts +++ /dev/null @@ -1,17 +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 * as t from 'io-ts'; - -export const OverviewFiltersType = t.partial({ - locations: t.array(t.string), - ports: t.array(t.number), - schemes: t.array(t.string), - tags: t.array(t.string), -}); - -export type OverviewFilters = t.TypeOf; diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts index d6875840a138c..d71e720f50e6e 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts @@ -304,6 +304,7 @@ export const GetPingsParamsType = t.intersection([ dateRange: DateRangeType, }), t.partial({ + excludedLocations: t.string, index: t.number, size: t.number, locations: t.string, diff --git a/x-pack/plugins/uptime/public/apps/uptime_app.tsx b/x-pack/plugins/uptime/public/apps/uptime_app.tsx index b31dd068ebb08..f82a312ef91f5 100644 --- a/x-pack/plugins/uptime/public/apps/uptime_app.tsx +++ b/x-pack/plugins/uptime/public/apps/uptime_app.tsx @@ -32,6 +32,7 @@ import { kibanaService } from '../state/kibana_service'; import { ActionMenu } from '../components/common/header/action_menu'; import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; +import { UptimeIndexPatternContextProvider } from '../contexts/uptime_index_pattern_context'; export interface UptimeAppColors { danger: string; @@ -119,16 +120,20 @@ const Application = (props: UptimeAppProps) => { -
- - - - - -
+ +
+ +
+ + + +
+
+
+
diff --git a/x-pack/plugins/uptime/public/components/common/monitor_tags.tsx b/x-pack/plugins/uptime/public/components/common/monitor_tags.tsx index 892271f9ef8c5..793c27a031546 100644 --- a/x-pack/plugins/uptime/public/components/common/monitor_tags.tsx +++ b/x-pack/plugins/uptime/public/components/common/monitor_tags.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { EuiBadge, EuiBadgeGroup, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useHistory } from 'react-router-dom'; @@ -69,9 +69,14 @@ export const MonitorTags = ({ ping, summary }: Props) => { const currFilters = parseCurrentFilters(params.filters); - const [filterType, setFilterType] = useState(currFilters.get('tags') ?? []); + const [tagFilters, setTagFilters] = useState(currFilters.get('tags') ?? []); - useFilterUpdate('tags', filterType); + const excludedTagFilters = useMemo(() => { + const currExcludedFilters = parseCurrentFilters(params.excludedFilters); + return currExcludedFilters.get('tags') ?? []; + }, [params.excludedFilters]); + + useFilterUpdate('tags', tagFilters, excludedTagFilters); if (tags.length === 0) { return summary ? null : ( @@ -93,7 +98,7 @@ export const MonitorTags = ({ ping, summary }: Props) => { key={tag} title={getFilterLabel(tag)} onClick={() => { - setFilterType([tag]); + setTagFilters([tag]); }} onClickAriaLabel={getFilterLabel(tag)} color="hollow" diff --git a/x-pack/plugins/uptime/public/components/common/uptime_date_picker.test.tsx b/x-pack/plugins/uptime/public/components/common/uptime_date_picker.test.tsx index 4bfe7de33cba5..51909527c51e2 100644 --- a/x-pack/plugins/uptime/public/components/common/uptime_date_picker.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/uptime_date_picker.test.tsx @@ -56,7 +56,7 @@ describe('UptimeDatePicker component', () => { expect(customHistory.push).toHaveBeenCalledWith({ pathname: '/', - search: 'dateRangeStart=now-30m&dateRangeEnd=now-15m', + search: 'dateRangeEnd=now-15m&dateRangeStart=now-30m', }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list_header.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list_header.tsx index 8e599cba6e97e..0284211d6259c 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list_header.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list_header.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { StatusFilter } from '../../overview/monitor_list/status_filter'; -import { FilterGroup } from '../../overview/filter_group'; +import { FilterGroup } from '../../overview/filter_group/filter_group'; export const PingListHeader = () => { return ( @@ -27,7 +27,7 @@ export const PingListHeader = () => { - + diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/use_pings.ts b/x-pack/plugins/uptime/public/components/monitor/ping_list/use_pings.ts index 9a8b1aa0e46f4..7e62b087ae671 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/use_pings.ts +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/use_pings.ts @@ -35,7 +35,7 @@ export const usePingsList = ({ pageSize, pageIndex }: Props) => { const { statusFilter } = useGetUrlParams(); - const { selectedLocations } = useSelectedFilters(); + const selectedFilters = useSelectedFilters(); const dispatch = useDispatch(); @@ -45,6 +45,9 @@ export const usePingsList = ({ pageSize, pageIndex }: Props) => { dispatch, ]); + const locations = JSON.stringify(selectedFilters.selectedLocations); + const excludedLocations = JSON.stringify(selectedFilters.excludedLocations); + useEffect(() => { getPings({ monitorId, @@ -52,7 +55,8 @@ export const usePingsList = ({ pageSize, pageIndex }: Props) => { from, to, }, - locations: JSON.stringify(selectedLocations), + excludedLocations, + locations, index: pageIndex, size: pageSize, status: statusFilter !== 'all' ? statusFilter : '', @@ -66,7 +70,8 @@ export const usePingsList = ({ pageSize, pageIndex }: Props) => { pageIndex, pageSize, statusFilter, - selectedLocations, + locations, + excludedLocations, ]); const { data } = useFetcher(() => { diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alert_query_bar/query_bar.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alert_query_bar/query_bar.tsx index 0a0bbadb6216f..4c6072a018642 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alert_query_bar/query_bar.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alert_query_bar/query_bar.tsx @@ -9,9 +9,9 @@ import React, { useEffect, useState } from 'react'; import { EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { QueryStringInput } from '../../../../../../../../src/plugins/data/public'; -import { useIndexPattern } from '../../query_bar/use_index_pattern'; import { isValidKuery } from '../../query_bar/query_bar'; import * as labels from '../translations'; +import { useIndexPattern } from '../../../../hooks'; interface Props { query: string; @@ -19,7 +19,7 @@ interface Props { } export const AlertQueryBar = ({ query = '', onChange }: Props) => { - const { index_pattern: indexPattern } = useIndexPattern(); + const indexPattern = useIndexPattern(); const [inputVal, setInputVal] = useState(query); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx index ff2ef4d2359a8..39d19464caa2e 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx @@ -8,15 +8,18 @@ import React, { useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { isRight } from 'fp-ts/lib/Either'; -import { overviewFiltersSelector, selectedFiltersSelector } from '../../../../state/selectors'; +import { selectedFiltersSelector } from '../../../../state/selectors'; import { AlertMonitorStatusComponent } from '../monitor_status_alert/alert_monitor_status'; -import { fetchOverviewFilters, setSearchTextAction } from '../../../../state/actions'; +import { setSearchTextAction } from '../../../../state/actions'; import { AtomicStatusCheckParamsType, GetMonitorAvailabilityParamsType, } from '../../../../../common/runtime_types'; import { useSnapShotCount } from './use_snap_shot'; +import { FILTER_FIELDS } from '../../../../../common/constants'; + +const { TYPE, TAGS, LOCATION, PORT } = FILTER_FIELDS; interface Props { alertParams: { [key: string]: any }; @@ -37,23 +40,6 @@ export const AlertMonitorStatus: React.FC = ({ alertParams, }) => { const dispatch = useDispatch(); - useEffect(() => { - if (!window.location.pathname.includes('/app/uptime')) { - // filters inside uptime app already loaded - dispatch( - fetchOverviewFilters({ - dateRangeStart: 'now-24h', - dateRangeEnd: 'now', - locations: alertParams.filters?.['observer.geo.name'] ?? [], - ports: alertParams.filters?.['url.port'] ?? [], - tags: alertParams.filters?.tags ?? [], - schemes: alertParams.filters?.['monitor.type'] ?? [], - }) - ); - } - }, [alertParams, dispatch]); - - const overviewFilters = useSelector(overviewFiltersSelector); useEffect(() => { if (alertParams.search) { @@ -78,14 +64,10 @@ export const AlertMonitorStatus: React.FC = ({ useEffect(() => { if (!alertParams.filters && selectedFilters !== null) { setAlertParams('filters', { - // @ts-ignore - 'url.port': selectedFilters?.ports ?? [], - // @ts-ignore - 'observer.geo.name': selectedFilters?.locations ?? [], - // @ts-ignore - 'monitor.type': selectedFilters?.schemes ?? [], - // @ts-ignore - tags: selectedFilters?.tags ?? [], + [PORT]: selectedFilters?.ports ?? [], + [LOCATION]: selectedFilters?.locations ?? [], + [TYPE]: selectedFilters?.schemes ?? [], + [TAGS]: selectedFilters?.tags ?? [], }); } }, [alertParams, setAlertParams, selectedFilters]); @@ -94,7 +76,6 @@ export const AlertMonitorStatus: React.FC = ({ void; + hasFilters: boolean; } const TimeRangeOptions: TimeRangeOption[] = [ @@ -55,6 +56,7 @@ export const AvailabilityExpressionSelect: React.FC = ({ alertParams, isOldAlert, setAlertParams, + hasFilters, }) => { const [range, setRange] = useState(alertParams?.availability?.range ?? DEFAULT_RANGE); const [rangeUnit, setRangeUnit] = useState( @@ -114,7 +116,11 @@ export const AvailabilityExpressionSelect: React.FC = ({ /> } data-test-subj="xpack.uptime.alerts.monitorStatus.availability.threshold" - description={labels.ENTER_AVAILABILITY_THRESHOLD_DESCRIPTION} + description={ + hasFilters + ? labels.ENTER_AVAILABILITY_THRESHOLD_DESCRIPTION + : labels.ENTER_ANY_AVAILABILITY_THRESHOLD_DESCRIPTION + } id="threshold" isEnabled={isEnabled} isInvalid={thresholdIsInvalid} diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.test.tsx index c0bf73d6c5308..6aa829adc4544 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.test.tsx @@ -6,12 +6,11 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; import { fireEvent, waitFor } from '@testing-library/react'; import { FiltersExpressionsSelect } from './filters_expression_select'; import { render } from '../../../../lib/helper/rtl_helpers'; import { filterAriaLabels as aria } from './translations'; -import { filterLabels } from '../../filter_group/translations'; +import * as Hooks from '../../../../../../observability/public/hooks/use_values_list'; describe('FiltersExpressionSelect', () => { const LOCATION_FIELD_NAME = 'observer.geo.name'; @@ -19,23 +18,22 @@ describe('FiltersExpressionSelect', () => { const SCHEME_FIELD_NAME = 'monitor.type'; const TAG_FIELD_NAME = 'tags'; - it('is empty when no filters available', () => { - const component = shallowWithIntl( + it('is empty when no filters available', async () => { + const { queryByLabelText } = render( ); - expect(component).toMatchInlineSnapshot(``); + + await waitFor(() => { + for (const label of Object.values(aria)) { + expect(queryByLabelText(label)).toBeNull(); + } + }); }); it.each([ @@ -51,24 +49,20 @@ describe('FiltersExpressionSelect', () => { [aria.LOCATION, aria.TAG], ], [[TAG_FIELD_NAME], [aria.TAG], [aria.LOCATION, aria.PORT, aria.SCHEME]], - ])('contains provided new filter values', (newFilters, expectedLabels, absentLabels) => { + ])('contains provided new filter values', async (newFilters, expectedLabels, absentLabels) => { const { getByLabelText, queryByLabelText } = render( ); - expectedLabels.forEach((label) => expect(getByLabelText(label))); - absentLabels.forEach((label) => expect(queryByLabelText(label)).toBeNull()); + await waitFor(() => { + expectedLabels.forEach((label) => expect(getByLabelText(label))); + absentLabels.forEach((label) => expect(queryByLabelText(label)).toBeNull()); + }); }); it.each([ @@ -84,12 +78,6 @@ describe('FiltersExpressionSelect', () => { alertParams={{}} newFilters={[LOCATION_FIELD_NAME, SCHEME_FIELD_NAME, PORT_FIELD_NAME, TAG_FIELD_NAME]} onRemoveFilter={onRemoveFilterMock} - filters={{ - tags: ['prod'], - ports: [5601], - schemes: ['http'], - locations: ['nyc'], - }} setAlertParams={setAlertParamsMock} shouldUpdateUrl={false} /> @@ -108,60 +96,11 @@ describe('FiltersExpressionSelect', () => { }); }); - const TEST_TAGS = ['foo', 'bar']; - const TEST_PORTS = [5601, 9200]; - const TEST_SCHEMES = ['http', 'tcp']; - const TEST_LOCATIONS = ['nyc', 'fairbanks']; - it.each([ - [ - { - tags: TEST_TAGS, - ports: [5601, 9200], - schemes: ['http', 'tcp'], - locations: ['nyc', 'fairbanks'], - }, - [TAG_FIELD_NAME], - aria.TAG, - filterLabels.TAG, - TEST_TAGS, - ], - [ - { - tags: [], - ports: TEST_PORTS, - schemes: [], - locations: [], - }, - [PORT_FIELD_NAME], - aria.PORT, - filterLabels.PORT, - TEST_PORTS, - ], - [ - { - tags: [], - ports: [], - schemes: TEST_SCHEMES, - locations: [], - }, - [SCHEME_FIELD_NAME], - aria.SCHEME, - filterLabels.SCHEME, - TEST_SCHEMES, - ], - [ - { - tags: [], - ports: [], - schemes: [], - locations: TEST_LOCATIONS, - }, - [LOCATION_FIELD_NAME], - aria.LOCATION, - filterLabels.LOCATION, - TEST_LOCATIONS, - ], + [[TAG_FIELD_NAME], aria.TAG], + [[PORT_FIELD_NAME], aria.PORT], + [[SCHEME_FIELD_NAME], aria.SCHEME], + [[LOCATION_FIELD_NAME], aria.LOCATION], ])( 'applies accessible label to filter expressions, and contains selected filters', /** @@ -171,19 +110,14 @@ describe('FiltersExpressionSelect', () => { * @param filterLabel the name of the filter label expected in each item's aria-label * @param expectedFilterItems the set of filter options the component should render */ - async ( - filters, - newFilters, - expectedFilterButtonAriaLabel, - filterLabel, - expectedFilterItems - ) => { - const { getByLabelText } = render( + async (newFilters, expectedFilterButtonAriaLabel) => { + const spy = jest.spyOn(Hooks, 'useValuesList'); + spy.mockReturnValue({ loading: false, values: [{ label: 'test-label', count: 3 }] }); + const { getByLabelText, getByText } = render( @@ -194,9 +128,7 @@ describe('FiltersExpressionSelect', () => { fireEvent.click(filterButton); await waitFor(() => { - expectedFilterItems.forEach((filterItem: string | number) => - expect(getByLabelText(`Filter by ${filterLabel} ${filterItem}.`)) - ); + expect(getByText('Apply')); }); } ); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx index b09d44488e803..cd0a78a42c5f7 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx @@ -7,31 +7,38 @@ import React, { useState } from 'react'; import { EuiButtonIcon, EuiExpression, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { FilterPopover } from '../../filter_group/filter_popover'; import { filterLabels } from '../../filter_group/translations'; import { alertFilterLabels, filterAriaLabels } from './translations'; -import { FilterExpressionsSelectProps } from './filters_expression_select_container'; -import { OverviewFiltersState } from '../../../../state/reducers/overview_filters'; +import { FieldValueSuggestions } from '../../../../../../observability/public'; +import { useIndexPattern } from '../../../../contexts/uptime_index_pattern_context'; +import { FILTER_FIELDS } from '../../../../../common/constants'; +import { useGetUrlParams } from '../../../../hooks'; -type Props = FilterExpressionsSelectProps & Pick; +export interface FilterExpressionsSelectProps { + alertParams: { [key: string]: any }; + newFilters: string[]; + onRemoveFilter: (val: string) => void; + setAlertParams: (key: string, value: any) => void; + shouldUpdateUrl: boolean; +} -export const FiltersExpressionsSelect: React.FC = ({ +const { TYPE, TAGS, LOCATION, PORT } = FILTER_FIELDS; + +export const FiltersExpressionsSelect: React.FC = ({ alertParams, - filters: overviewFilters, newFilters, onRemoveFilter, setAlertParams, }) => { - const { tags, ports, schemes, locations } = overviewFilters; - const alertFilters = alertParams?.filters; - const selectedPorts = alertFilters?.['url.port'] ?? []; - const selectedLocations = alertFilters?.['observer.geo.name'] ?? []; - const selectedSchemes = alertFilters?.['monitor.type'] ?? []; - const selectedTags = alertFilters?.tags ?? []; + const selectedPorts = alertFilters?.[PORT] ?? []; + const selectedLocations = alertFilters?.[LOCATION] ?? []; + const selectedSchemes = alertFilters?.[TYPE] ?? []; + const selectedTags = alertFilters?.[TAGS] ?? []; - const onFilterFieldChange = (fieldName: string, values: string[]) => { + const { dateRangeStart: from, dateRangeEnd: to } = useGetUrlParams(); + const onFilterFieldChange = (fieldName: string, values?: string[]) => { // the `filters` field is no longer a string if (alertParams.filters && typeof alertParams.filters !== 'string') { setAlertParams('filters', { ...alertParams.filters, [fieldName]: values }); @@ -41,12 +48,12 @@ export const FiltersExpressionsSelect: React.FC = ({ Object.assign( {}, { - tags: [], - 'url.port': [], - 'observer.geo.name': [], - 'monitor.type': [], + [TAGS]: [], + [PORT]: [], + [LOCATION]: [], + [TYPE]: [], }, - { [fieldName]: values } + { [fieldName]: values ?? [] } ) ); } @@ -54,13 +61,11 @@ export const FiltersExpressionsSelect: React.FC = ({ const monitorFilters = [ { - 'aria-label': filterAriaLabels.PORT, + ariaLabel: filterAriaLabels.PORT, onFilterFieldChange, loading: false, fieldName: 'url.port', id: 'filter_port', - disabled: ports?.length === 0, - items: ports?.map((p: number) => p.toString()) ?? [], selectedItems: selectedPorts, title: filterLabels.PORT, description: @@ -68,39 +73,33 @@ export const FiltersExpressionsSelect: React.FC = ({ value: selectedPorts.length === 0 ? alertFilterLabels.ANY_PORT : selectedPorts?.join(','), }, { - 'aria-label': filterAriaLabels.TAG, + ariaLabel: filterAriaLabels.TAG, onFilterFieldChange, loading: false, fieldName: 'tags', id: 'filter_tags', - disabled: tags?.length === 0, - items: tags ?? [], selectedItems: selectedTags, title: filterLabels.TAG, description: selectedTags.length === 0 ? alertFilterLabels.WITH : alertFilterLabels.WITH_TAG, value: selectedTags.length === 0 ? alertFilterLabels.ANY_TAG : selectedTags?.join(','), }, { - 'aria-label': filterAriaLabels.SCHEME, + ariaLabel: filterAriaLabels.SCHEME, onFilterFieldChange, loading: false, fieldName: 'monitor.type', id: 'filter_scheme', - disabled: schemes?.length === 0, - items: schemes ?? [], selectedItems: selectedSchemes, title: filterLabels.SCHEME, description: selectedSchemes.length === 0 ? alertFilterLabels.OF : alertFilterLabels.OF_TYPE, value: selectedSchemes.length === 0 ? alertFilterLabels.ANY_TYPE : selectedSchemes?.join(','), }, { - 'aria-label': filterAriaLabels.LOCATION, + ariaLabel: filterAriaLabels.LOCATION, onFilterFieldChange, loading: false, fieldName: 'observer.geo.name', id: 'filter_location', - disabled: locations?.length === 0, - items: locations ?? [], selectedItems: selectedLocations, title: filterLabels.LOCATION, description: @@ -123,43 +122,61 @@ export const FiltersExpressionsSelect: React.FC = ({ (curr) => curr.selectedItems.length > 0 || newFilters?.includes(curr.fieldName) ); + const indexPattern = useIndexPattern(); + return ( <> - {filtersToDisplay.map(({ description, value, ...item }) => ( - - - setIsOpen({ ...isOpen, [item.id]: !isOpen[item.id] })} + {filtersToDisplay.map( + ({ description, id, title, value, fieldName, ariaLabel, selectedItems }) => ( + + + {indexPattern && ( + { + onFilterFieldChange(fieldName, vals); + }} + selectedValue={selectedItems} + button={ + setIsOpen({ ...isOpen, [id]: !isOpen[id] })} + /> + } + forceOpen={isOpen[id]} + setForceOpen={() => { + setIsOpen({ ...isOpen, [id]: !isOpen[id] }); + }} + asCombobox={false} + cardinalityField="monitor.id" + time={{ from, to }} + allowExclusions={false} /> - } - forceOpen={isOpen[item.id]} - setForceOpen={() => { - setIsOpen({ ...isOpen, [item.id]: !isOpen[item.id] }); - }} - /> - - - { - onRemoveFilter(item.fieldName); - onFilterFieldChange(item.fieldName, []); - }} - /> - - - - ))} + )} + + + { + onRemoveFilter(fieldName); + onFilterFieldChange(fieldName, []); + }} + /> + + + + ) + )} ); }; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select_container.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select_container.tsx deleted file mode 100644 index 0c03d55ba38f5..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select_container.tsx +++ /dev/null @@ -1,25 +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 React from 'react'; -import { useSelector } from 'react-redux'; -import { FiltersExpressionsSelect } from './filters_expression_select'; -import { overviewFiltersSelector } from '../../../../state/selectors'; - -export interface FilterExpressionsSelectProps { - alertParams: { [key: string]: any }; - newFilters: string[]; - onRemoveFilter: (val: string) => void; - setAlertParams: (key: string, value: any) => void; - shouldUpdateUrl: boolean; -} - -export const FiltersExpressionSelectContainer: React.FC = (props) => { - const overviewFilters = useSelector(overviewFiltersSelector); - - return ; -}; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts index 85d0e82471e5c..6797517116ccd 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts @@ -7,6 +7,5 @@ export { DownNoExpressionSelect } from './down_number_select'; export { FiltersExpressionsSelect } from './filters_expression_select'; -export { FiltersExpressionSelectContainer } from './filters_expression_select_container'; export { TimeExpressionSelect } from './time_expression_select'; export { StatusExpressionSelect } from './status_expression_select'; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx index e161727b46b1b..b339410ef2409 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx @@ -7,10 +7,45 @@ import React from 'react'; import { screen } from '@testing-library/dom'; -import { AlertMonitorStatusComponent, AlertMonitorStatusProps } from './alert_monitor_status'; +import { + AlertMonitorStatusComponent, + AlertMonitorStatusProps, + hasFilters, +} from './alert_monitor_status'; import { render } from '../../../../lib/helper/rtl_helpers'; describe('alert monitor status component', () => { + describe('hasFilters', () => { + const EMPTY_FILTERS = { + tags: [], + 'url.port': [], + 'observer.geo.name': [], + 'monitor.type': [], + }; + + it('returns false when filters are empty', () => { + expect(hasFilters({})).toBe(false); + }); + + it('returns false when all fields are empty', () => { + expect(hasFilters(EMPTY_FILTERS)).toBe(false); + }); + + it.each([ + { tags: ['prod'] }, + { 'url.port': ['5678'] }, + { 'observer.geo.name': ['Fairbanks'] }, + { 'monitor.type': ['HTTP'] }, + ])('returns true if a filter has a field', (testObj) => { + expect( + hasFilters({ + ...EMPTY_FILTERS, + ...testObj, + }) + ).toBe(true); + }); + }); + describe('AlertMonitorStatus', () => { const defaultProps: AlertMonitorStatusProps = { alertParams: { @@ -20,7 +55,6 @@ describe('alert monitor status component', () => { timerangeCount: 21, }, enabled: true, - hasFilters: false, isOldAlert: true, snapshotCount: 0, snapshotLoading: false, @@ -38,7 +72,7 @@ describe('alert monitor status component', () => { expect(await screen.findByText('Add filter')).toBeInTheDocument(); expect(await screen.findByText('Availability')).toBeInTheDocument(); expect(await screen.findByText('Status check')).toBeInTheDocument(); - expect(await screen.findByText('matching monitors are up in')).toBeInTheDocument(); + expect(await screen.findByText('any monitor is up in')).toBeInTheDocument(); expect(await screen.findByText('days')).toBeInTheDocument(); expect(await screen.findByText('hours')).toBeInTheDocument(); expect(await screen.findByText('within the last')).toBeInTheDocument(); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.tsx index eaae1650b02ed..8ed4b8f7a0032 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.tsx @@ -8,17 +8,17 @@ import React, { useCallback, useEffect, useState } from 'react'; import { EuiCallOut, EuiSpacer, EuiHorizontalRule, EuiLoadingSpinner } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { FiltersExpressionSelectContainer, StatusExpressionSelect } from '../monitor_expressions'; +import { FiltersExpressionsSelect, StatusExpressionSelect } from '../monitor_expressions'; import { AddFilterButton } from './add_filter_btn'; import { OldAlertCallOut } from './old_alert_call_out'; import { AvailabilityExpressionSelect } from '../monitor_expressions/availability_expression_select'; import { AlertQueryBar } from '../alert_query_bar/query_bar'; import { useGetUrlParams } from '../../../../hooks'; +import { FILTER_FIELDS } from '../../../../../common/constants'; export interface AlertMonitorStatusProps { alertParams: { [key: string]: any }; enabled: boolean; - hasFilters: boolean; isOldAlert: boolean; snapshotCount: number; snapshotLoading?: boolean; @@ -30,15 +30,16 @@ export interface AlertMonitorStatusProps { }; } +export const hasFilters = (filters?: { [key: string]: string[] }) => { + if (!filters || Object.keys(filters).length === 0) { + return false; + } + + return Object.values(FILTER_FIELDS).some((f) => filters[f].length); +}; + export const AlertMonitorStatusComponent: React.FC = (props) => { - const { - alertParams, - hasFilters, - isOldAlert, - setAlertParams, - snapshotCount, - snapshotLoading, - } = props; + const { alertParams, isOldAlert, setAlertParams, snapshotCount, snapshotLoading } = props; const alertFilters = alertParams?.filters ?? {}; const [newFilters, setNewFilters] = useState( @@ -94,7 +95,7 @@ export const AlertMonitorStatusComponent: React.FC = (p }} /> - { @@ -110,8 +111,8 @@ export const AlertMonitorStatusComponent: React.FC = (p @@ -120,6 +121,7 @@ export const AlertMonitorStatusComponent: React.FC = (p alertParams={alertParams} isOldAlert={isOldAlert} setAlertParams={setAlertParams} + hasFilters={hasFilters(alertParams?.filters)} /> diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts b/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts index 7cfcdabe5562b..76d8ebaea3719 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts +++ b/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts @@ -197,6 +197,15 @@ export const ENTER_AVAILABILITY_THRESHOLD_DESCRIPTION = i18n.translate( } ); +export const ENTER_ANY_AVAILABILITY_THRESHOLD_DESCRIPTION = i18n.translate( + 'xpack.uptime.alerts.monitorStatus.availability.threshold.anyMonitorDescription', + { + defaultMessage: 'any monitor is up in', + description: + 'This fragment explains that an alert will fire for monitors matching user-specified criteria', + } +); + export const ENTER_AVAILABILITY_THRESHOLD_VALUE = (value: string) => i18n.translate('xpack.uptime.alerts.monitorStatus.availability.threshold.value', { defaultMessage: '< {value}% of checks', diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/__snapshots__/parse_filter_map.test.ts.snap b/x-pack/plugins/uptime/public/components/overview/filter_group/__snapshots__/parse_filter_map.test.ts.snap deleted file mode 100644 index cabad2818fa90..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/__snapshots__/parse_filter_map.test.ts.snap +++ /dev/null @@ -1,20 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`parseFiltersMap provides values from valid filter string 1`] = ` -Object { - "locations": Array [ - "us-east-2", - ], - "ports": Array [ - "5601", - "80", - ], - "schemes": Array [ - "http", - "tcp", - ], - "tags": Array [], -} -`; - -exports[`parseFiltersMap returns an empty object for invalid filter 1`] = `"Unable to parse invalid filter string"`; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.test.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.test.tsx index 807f95c22bc61..cdc399a750756 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.test.tsx @@ -8,67 +8,103 @@ import React from 'react'; import { fireEvent, waitFor } from '@testing-library/react'; import { render } from '../../../lib/helper/rtl_helpers'; -import { FilterGroupComponent } from './filter_group'; +import { FilterGroup } from './filter_group'; +import * as Hooks from '../../../../../observability/public/hooks/use_values_list'; -describe('FilterGroupComponent', () => { - const overviewFilters = { - locations: ['nyc', 'fairbanks'], - ports: [5601, 9200], - schemes: ['http', 'tcp'], - tags: ['prod', 'dev'], - }; +describe('FilterGroup', () => { it.each([ - ['expands filter group for Location filter', 'Search for location'], - ['expands filter group for Port filter', 'Search for port'], - ['expands filter group for Scheme filter', 'Search for scheme'], - ['expands filter group for Tag filter', 'Search for tag'], - ])('handles loading', async (popoverButtonLabel, searchInputLabel) => { - const { getByLabelText } = render( - - ); + ['expands filter group for Location filter'], + ['expands filter group for Port filter'], + ['expands filter group for Scheme filter'], + ['expands filter group for Tag filter'], + ])('handles loading', async (popoverButtonLabel) => { + jest.spyOn(Hooks, 'useValuesList').mockReturnValue({ + values: [], + loading: true, + }); + const { getByLabelText, getByText } = render(); - const popoverButton = getByLabelText(popoverButtonLabel); - fireEvent.click(popoverButton); await waitFor(() => { - const searchInput = getByLabelText(searchInputLabel); - expect(searchInput).toHaveAttribute('placeholder', 'Loading...'); + const popoverButton = getByLabelText(popoverButtonLabel); + fireEvent.click(popoverButton); + }); + await waitFor(() => { + expect(getByText('Loading options')); }); }); it.each([ [ 'expands filter group for Location filter', - 'Search for location', - ['Filter by Location nyc.', 'Filter by Location fairbanks.'], + [ + [ + { + label: 'Fairbanks', + count: 10, + }, + { + label: 'NYC', + count: 2, + }, + ], + [], + [], + [], + ], ], [ 'expands filter group for Port filter', - 'Search for port', - ['Filter by Port 5601.', 'Filter by Port 9200.'], + [ + [], + [ + { label: '80', count: 12 }, + { label: '443', count: 8 }, + ], + [], + [], + ], ], [ 'expands filter group for Scheme filter', - 'Search for scheme', - ['Filter by Scheme http.', 'Filter by Scheme tcp.'], + [ + [], + [], + [ + { label: 'HTTP', count: 15 }, + { label: 'TCP', count: 10 }, + ], + [], + ], ], [ 'expands filter group for Tag filter', - 'Search for tag', - ['Filter by Tag prod.', 'Filter by Tag dev.'], + [ + [], + [], + [], + [ + { label: 'test', count: 23 }, + { label: 'prod', count: 10 }, + ], + ], ], - ])( - 'displays filter items when clicked', - async (popoverButtonLabel, searchInputLabel, filterItemButtonLabels) => { - const { getByLabelText } = render( - - ); + ])('displays filter item counts when clicked', async (popoverButtonLabel, values) => { + const spy = jest.spyOn(Hooks, 'useValuesList'); + for (let i = 0; i < 4; i++) { + spy.mockReturnValueOnce({ + values: values[i], + loading: false, + }); + } + const { getByLabelText, getAllByLabelText } = render(); + + await waitFor(() => { const popoverButton = getByLabelText(popoverButtonLabel); fireEvent.click(popoverButton); - await waitFor(() => { - expect(getByLabelText(searchInputLabel)); - filterItemButtonLabels.forEach((itemLabel) => expect(getByLabelText(itemLabel))); - }); - } - ); + }); + + expect(getByLabelText('2 available filters')); + expect(getAllByLabelText('0 available filters')).toHaveLength(3); + }); }); diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.tsx index b60d2b3050f5c..3980b4bf9d3da 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.tsx +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.tsx @@ -5,100 +5,72 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { EuiFilterGroup } from '@elastic/eui'; import styled from 'styled-components'; -import { useRouteMatch } from 'react-router-dom'; -import { FilterPopoverProps, FilterPopover } from './filter_popover'; -import { OverviewFilters } from '../../../../common/runtime_types/overview_filters'; -import { filterLabels } from './translations'; import { useFilterUpdate } from '../../../hooks/use_filter_update'; -import { MONITOR_ROUTE } from '../../../../common/constants'; import { useSelectedFilters } from '../../../hooks/use_selected_filters'; - -interface Props { - loading: boolean; - overviewFilters: OverviewFilters; -} +import { FieldValueSuggestions } from '../../../../../observability/public'; +import { SelectedFilters } from './selected_filters'; +import { useIndexPattern } from '../../../contexts/uptime_index_pattern_context'; +import { useGetUrlParams } from '../../../hooks'; const Container = styled(EuiFilterGroup)` margin-bottom: 10px; `; -function isDisabled(array?: T[]) { - return array ? array.length === 0 : true; -} - -export const FilterGroupComponent: React.FC = ({ overviewFilters, loading }) => { - const { locations, ports, schemes, tags } = overviewFilters; - +export const FilterGroup = () => { const [updatedFieldValues, setUpdatedFieldValues] = useState<{ fieldName: string; values: string[]; - }>({ fieldName: '', values: [] }); + notValues: string[]; + }>({ fieldName: '', values: [], notValues: [] }); - useFilterUpdate(updatedFieldValues.fieldName, updatedFieldValues.values); + useFilterUpdate( + updatedFieldValues.fieldName, + updatedFieldValues.values, + updatedFieldValues.notValues + ); - const { selectedLocations, selectedPorts, selectedSchemes, selectedTags } = useSelectedFilters(); + const { dateRangeStart, dateRangeEnd } = useGetUrlParams(); - const onFilterFieldChange = (fieldName: string, values: string[]) => { - setUpdatedFieldValues({ fieldName, values }); - }; + const { filtersList } = useSelectedFilters(); - const isMonitorPage = useRouteMatch(MONITOR_ROUTE); + const indexPattern = useIndexPattern(); - const filterPopoverProps: FilterPopoverProps[] = [ - { - loading, - onFilterFieldChange, - fieldName: 'observer.geo.name', - id: 'location', - items: locations || [], - selectedItems: selectedLocations, - title: filterLabels.LOCATION, + const onFilterFieldChange = useCallback( + (fieldName: string, values: string[], notValues: string[]) => { + setUpdatedFieldValues({ fieldName, values, notValues }); }, - // on monitor page we only display location filter in ping list - ...(!isMonitorPage - ? [ - { - loading, - onFilterFieldChange, - fieldName: 'url.port', - id: 'port', - disabled: isDisabled(ports), - items: ports?.map((p: number) => p.toString()) ?? [], - selectedItems: selectedPorts, - title: filterLabels.PORT, - }, - { - loading, - onFilterFieldChange, - fieldName: 'monitor.type', - id: 'scheme', - disabled: isDisabled(schemes), - items: schemes ?? [], - selectedItems: selectedSchemes, - title: filterLabels.SCHEME, - }, - { - loading, - onFilterFieldChange, - fieldName: 'tags', - id: 'tags', - disabled: isDisabled(tags), - items: tags ?? [], - selectedItems: selectedTags, - title: filterLabels.TAG, - }, - ] - : []), - ]; + [] + ); return ( - - {filterPopoverProps.map((item) => ( - - ))} - + <> + + {indexPattern && + filtersList.map(({ field, label, selectedItems, excludedItems }) => ( + + onFilterFieldChange(field, values ?? [], notValues ?? []) + } + asCombobox={false} + asFilterButton={true} + forceOpen={false} + filters={[]} + cardinalityField="monitor.id" + time={{ from: dateRangeStart, to: dateRangeEnd }} + /> + ))} + + + ); }; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx deleted file mode 100644 index db1892526a1e6..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx +++ /dev/null @@ -1,57 +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 React, { useContext, useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useGetUrlParams } from '../../../hooks'; -import { parseFiltersMap } from './parse_filter_map'; -import { fetchOverviewFilters } from '../../../state/actions'; -import { FilterGroupComponent } from './index'; -import { UptimeRefreshContext } from '../../../contexts'; -import { esKuerySelector, overviewFiltersSelector } from '../../../state/selectors'; - -interface Props { - esFilters?: string; -} - -export const FilterGroup: React.FC = ({ esFilters }: Props) => { - const { lastRefresh } = useContext(UptimeRefreshContext); - - const { filters: overviewFilters, loading } = useSelector(overviewFiltersSelector); - const esKuery = useSelector(esKuerySelector); - - const { dateRangeStart, dateRangeEnd, statusFilter, filters: urlFilters } = useGetUrlParams(); - - const dispatch = useDispatch(); - - useEffect(() => { - const filterSelections = parseFiltersMap(urlFilters); - dispatch( - fetchOverviewFilters({ - dateRangeStart, - dateRangeEnd, - locations: filterSelections.locations ?? [], - ports: filterSelections.ports ?? [], - schemes: filterSelections.schemes ?? [], - search: esKuery, - statusFilter, - tags: filterSelections.tags ?? [], - }) - ); - }, [ - lastRefresh, - dateRangeStart, - dateRangeEnd, - esKuery, - esFilters, - statusFilter, - urlFilters, - dispatch, - ]); - - return ; -}; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.test.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.test.tsx deleted file mode 100644 index bccebb21718bf..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.test.tsx +++ /dev/null @@ -1,96 +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 React from 'react'; -import { fireEvent, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; -import { FilterPopoverProps, FilterPopover } from './filter_popover'; -import { render } from '../../../lib/helper/rtl_helpers'; - -describe('FilterPopover component', () => { - let props: FilterPopoverProps; - - beforeEach(() => { - props = { - fieldName: 'test-fieldName', - id: 'test', - loading: false, - items: ['first', 'second', 'third', 'fourth'], - onFilterFieldChange: jest.fn(), - selectedItems: ['first', 'third'], - title: 'test-title', - }; - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('expands on button click', () => { - const { getByRole, getByLabelText, getByText, queryByLabelText, queryByText } = render( - - ); - - const screenReaderOnlyText = 'You are in a dialog. To close this dialog, hit escape.'; - - expect(queryByText(screenReaderOnlyText)).toBeNull(); - expect(queryByLabelText('Filter by bar fourth.')).toBeNull(); - - fireEvent.click(getByRole('button')); - - expect(getByText(screenReaderOnlyText)); - expect(getByLabelText('Filter by test-title fourth.')); - }); - - it('does not show item list when loading, and displays placeholder', async () => { - props.loading = true; - const { getByRole, queryByText, getByLabelText } = render(); - - fireEvent.click(getByRole('button')); - - await waitFor(() => { - const search = getByLabelText('Search for test-title'); - expect(search).toHaveAttribute('placeholder', 'Loading...'); - }); - - expect(queryByText('Filter by test-title second.')).toBeNull(); - }); - - it.each([ - [[], ['third'], ['third']], - [['first', 'third'], ['first'], ['third']], - [['fourth'], ['first', 'second'], ['first', 'second', 'fourth']], - ])( - 'returns selected items on popover close', - async (selectedPropsItems, expectedSelections, itemsToClick) => { - if (itemsToClick.length < 1) { - throw new Error('This test assumes at least one item will be clicked'); - } - props.selectedItems = selectedPropsItems; - - const { getByLabelText, queryByLabelText } = render(); - - const uptimeFilterButton = getByLabelText(`expands filter group for ${props.title} filter`); - - fireEvent.click(uptimeFilterButton); - - const generateLabelText = (item: string) => `Filter by ${props.title} ${item}.`; - - itemsToClick.forEach((item) => { - const optionButtonLabelText = generateLabelText(item); - const optionButton = getByLabelText(optionButtonLabelText); - fireEvent.click(optionButton); - }); - - fireEvent.click(uptimeFilterButton); - - await waitForElementToBeRemoved(() => queryByLabelText(generateLabelText(itemsToClick[0]))); - - expect(props.onFilterFieldChange).toHaveBeenCalledTimes(1); - expect(props.onFilterFieldChange).toHaveBeenCalledWith(props.fieldName, expectedSelections); - } - ); -}); diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx deleted file mode 100644 index 23e17802a6835..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx +++ /dev/null @@ -1,146 +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 { EuiFieldSearch, EuiFilterSelectItem, EuiPopover, EuiPopoverTitle } from '@elastic/eui'; -import React, { useState, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; -import { UptimeFilterButton } from './uptime_filter_button'; -import { toggleSelectedItems } from './toggle_selected_item'; -import { LocationLink } from '../monitor_list'; - -export interface FilterPopoverProps { - fieldName: string; - id: string; - loading: boolean; - disabled?: boolean; - items: string[]; - onFilterFieldChange: (fieldName: string, values: string[]) => void; - selectedItems: string[]; - title: string; - btnContent?: JSX.Element; - forceOpen?: boolean; - setForceOpen?: (val: boolean) => void; -} - -const isItemSelected = (selectedItems: string[], item: string): 'on' | undefined => - selectedItems.find((selected) => selected === item) ? 'on' : undefined; - -export const FilterPopover = ({ - fieldName, - id, - disabled, - loading, - items: allItems, - onFilterFieldChange, - selectedItems, - title, - btnContent, - forceOpen, - setForceOpen, -}: FilterPopoverProps) => { - const [isOpen, setIsOpen] = useState(false); - const [itemsToDisplay, setItemsToDisplay] = useState([]); - const [searchQuery, setSearchQuery] = useState(''); - const [tempSelectedItems, setTempSelectedItems] = useState(selectedItems); - - const [items, setItems] = useState([]); - - useEffect(() => { - // Merge incoming items with selected items, to enable deselection - - const mItems = selectedItems.concat(allItems ?? []); - const newItems = mItems.filter((item, index) => mItems.indexOf(item) === index); - setItems(newItems); - setTempSelectedItems(selectedItems); - }, [allItems, selectedItems]); - - useEffect(() => { - if (searchQuery !== '') { - const toDisplay = items.filter((item) => item.indexOf(searchQuery) >= 0); - setItemsToDisplay(toDisplay); - } else { - setItemsToDisplay(items); - } - }, [searchQuery, items]); - - return ( - 0} - numFilters={items.length} - numActiveFilters={isOpen ? tempSelectedItems.length : selectedItems.length} - onClick={() => { - if (isOpen) { - // only update these values on close - onFilterFieldChange(fieldName, tempSelectedItems); - } - setIsOpen(!isOpen); - }} - title={title} - /> - ) - } - closePopover={() => { - setIsOpen(false); - onFilterFieldChange(fieldName, tempSelectedItems); - if (setForceOpen) { - setForceOpen(false); - } - }} - data-test-subj={`filter-popover_${id}`} - id={id} - isOpen={isOpen || forceOpen} - ownFocus={true} - zIndex={10000} - > - - setSearchQuery(query)} - aria-label={i18n.translate('xpack.uptime.filterPopout.searchMessage.ariaLabel', { - defaultMessage: 'Search for {title}', - values: { - title: title.toLowerCase(), - }, - })} - placeholder={ - loading - ? i18n.translate('xpack.uptime.filterPopout.loadingMessage', { - defaultMessage: 'Loading...', - }) - : i18n.translate('xpack.uptime.filterPopout.searchMessage', { - defaultMessage: 'Search {title}', - values: { - title: title.toLowerCase(), - }, - }) - } - /> - - {!loading && - itemsToDisplay.map((item) => ( - toggleSelectedItems(item, tempSelectedItems, setTempSelectedItems)} - > - {item} - - ))} - {id === 'location' && items.length === 0 && } - - ); -}; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/parse_filter_map.test.ts b/x-pack/plugins/uptime/public/components/overview/filter_group/parse_filter_map.test.ts deleted file mode 100644 index d06af65cee73d..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/parse_filter_map.test.ts +++ /dev/null @@ -1,22 +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 { parseFiltersMap } from './parse_filter_map'; - -describe('parseFiltersMap', () => { - it('provides values from valid filter string', () => { - expect( - parseFiltersMap( - '[["url.port",["5601","80"]],["observer.geo.name",["us-east-2"]],["monitor.type",["http","tcp"]]]' - ) - ).toMatchSnapshot(); - }); - - it('returns an empty object for invalid filter', () => { - expect(() => parseFiltersMap('some invalid string')).toThrowErrorMatchingSnapshot(); - }); -}); diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/parse_filter_map.ts b/x-pack/plugins/uptime/public/components/overview/filter_group/parse_filter_map.ts deleted file mode 100644 index 5d08847b6b713..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/parse_filter_map.ts +++ /dev/null @@ -1,39 +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. - */ - -interface FilterField { - name: string; - fieldName: string; -} - -/** - * These are the only filter fields we are looking to catch at the moment. - * If your code needs to support custom fields, introduce a second parameter to - * `parseFiltersMap` to take a list of FilterField objects. - */ -const filterAllowList: FilterField[] = [ - { name: 'ports', fieldName: 'url.port' }, - { name: 'locations', fieldName: 'observer.geo.name' }, - { name: 'tags', fieldName: 'tags' }, - { name: 'schemes', fieldName: 'monitor.type' }, -]; - -export const parseFiltersMap = (filterMapString: string) => { - if (!filterMapString) { - return {}; - } - const filterSlices: { [key: string]: any } = {}; - try { - const map = new Map(JSON.parse(filterMapString)); - filterAllowList.forEach(({ name, fieldName }) => { - filterSlices[name] = map.get(fieldName) ?? []; - }); - return filterSlices; - } catch { - throw new Error('Unable to parse invalid filter string'); - } -}; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/selected_filters.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/selected_filters.tsx new file mode 100644 index 0000000000000..7e70673016d2c --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/selected_filters.tsx @@ -0,0 +1,79 @@ +/* + * 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 } from '@elastic/eui'; +import { FilterValueLabel } from '../../../../../observability/public'; +import { useIndexPattern } from '../../../contexts/uptime_index_pattern_context'; +import { useSelectedFilters } from '../../../hooks/use_selected_filters'; + +interface Props { + onChange: (fieldName: string, values: string[], notValues: string[]) => void; +} +export const SelectedFilters = ({ onChange }: Props) => { + const indexPattern = useIndexPattern(); + const { filtersList } = useSelectedFilters(); + + if (!indexPattern) return null; + + return ( + + {filtersList.map(({ field, selectedItems, excludedItems, label }) => [ + ...selectedItems.map((value) => ( + + { + onChange( + field, + selectedItems.filter((valT) => valT !== value), + excludedItems + ); + }} + invertFilter={(val) => { + onChange( + field, + selectedItems.filter((valT) => valT !== value), + [...excludedItems, value] + ); + }} + field={field} + value={value} + negate={false} + label={label} + /> + + )), + ...excludedItems.map((value) => ( + + { + onChange( + field, + selectedItems, + excludedItems.filter((valT) => valT !== value) + ); + }} + invertFilter={(val) => { + onChange( + field, + [...selectedItems, value], + excludedItems.filter((valT) => valT !== value) + ); + }} + field={field} + value={value} + negate={true} + label={label} + /> + + )), + ])} + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/toggle_selected_item.test.ts b/x-pack/plugins/uptime/public/components/overview/filter_group/toggle_selected_item.test.ts deleted file mode 100644 index 0a80f2062320d..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/toggle_selected_item.test.ts +++ /dev/null @@ -1,24 +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 { toggleSelectedItems } from './toggle_selected_item'; - -describe('toggleSelectedItems', () => { - it(`adds the item if it's not in the list`, () => { - const mock = jest.fn(); - toggleSelectedItems('abc', ['aba', 'abd'], mock); - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenCalledWith(['aba', 'abd', 'abc']); - }); - - it(`removes the item if it's already in the list`, () => { - const mock = jest.fn(); - toggleSelectedItems('abc', ['aba', 'abc', 'abd'], mock); - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenCalledWith(['aba', 'abd']); - }); -}); diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/toggle_selected_item.ts b/x-pack/plugins/uptime/public/components/overview/filter_group/toggle_selected_item.ts deleted file mode 100644 index 08b031f936dc5..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/toggle_selected_item.ts +++ /dev/null @@ -1,23 +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 { Dispatch, SetStateAction } from 'react'; - -export const toggleSelectedItems = ( - item: string, - tempSelectedItems: string[], - setTempSelectedItems: Dispatch> -) => { - const index = tempSelectedItems.indexOf(item); - const nextSelectedItems = [...tempSelectedItems]; - if (index >= 0) { - nextSelectedItems.splice(index, 1); - } else { - nextSelectedItems.push(item); - } - setTempSelectedItems(nextSelectedItems); -}; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/uptime_filter_button.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/uptime_filter_button.tsx deleted file mode 100644 index 326ad7a292455..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/uptime_filter_button.tsx +++ /dev/null @@ -1,44 +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 { EuiFilterButton } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; - -interface UptimeFilterButtonProps { - isDisabled?: boolean; - isSelected: boolean; - numFilters: number; - numActiveFilters: number; - onClick: () => void; - title: string; -} - -export const UptimeFilterButton = ({ - isDisabled, - isSelected, - numFilters, - numActiveFilters, - onClick, - title, -}: UptimeFilterButtonProps) => ( - - {title} - -); diff --git a/x-pack/plugins/uptime/public/components/overview/index.ts b/x-pack/plugins/uptime/public/components/overview/index.ts index 729db44aaa964..d647c38cee1ca 100644 --- a/x-pack/plugins/uptime/public/components/overview/index.ts +++ b/x-pack/plugins/uptime/public/components/overview/index.ts @@ -7,6 +7,5 @@ export * from './monitor_list'; export * from './empty_state'; -export * from './filter_group'; export * from './alerts'; export * from './snapshot'; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_name_col.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_name_col.tsx index 7adf248d37fc5..65bbaf45b63a2 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_name_col.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_name_col.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiText } from '@elastic/eui'; import { MonitorPageLink } from '../../../common/monitor_page_link'; @@ -44,7 +44,12 @@ export const MonitorNameColumn = ({ summary }: Props) => { const [filterType, setFilterType] = useState(currFilters.get('monitor.type') ?? []); - useFilterUpdate('monitor.type', filterType); + const excludedTypeFilters = useMemo(() => { + const currExcludedFilters = parseCurrentFilters(params.excludedFilters); + return currExcludedFilters.get('monitor.type') ?? []; + }, [params.excludedFilters]); + + useFilterUpdate('monitor.type', filterType, excludedTypeFilters); const filterLabel = i18n.translate('xpack.uptime.monitorList.monitorType.filter', { defaultMessage: 'Filter all monitors with type {type}', diff --git a/x-pack/plugins/uptime/public/components/overview/query_bar/query_bar.tsx b/x-pack/plugins/uptime/public/components/overview/query_bar/query_bar.tsx index 9436f420f7740..3c8c0599efa69 100644 --- a/x-pack/plugins/uptime/public/components/overview/query_bar/query_bar.tsx +++ b/x-pack/plugins/uptime/public/components/overview/query_bar/query_bar.tsx @@ -9,10 +9,9 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexItem } from '@elastic/eui'; import { QueryStringInput } from '../../../../../../../src/plugins/data/public/'; -import { useIndexPattern } from './use_index_pattern'; import { SyntaxType, useQueryBar } from './use_query_bar'; import { KQL_PLACE_HOLDER, SIMPLE_SEARCH_PLACEHOLDER } from './translations'; -import { useGetUrlParams } from '../../../hooks'; +import { useGetUrlParams, useIndexPattern } from '../../../hooks'; const SYNTAX_STORAGE = 'uptime:queryBarSyntax'; @@ -36,7 +35,7 @@ export const QueryBar = () => { const { query, setQuery, submitImmediately } = useQueryBar(); - const { index_pattern: indexPattern } = useIndexPattern(); + const indexPattern = useIndexPattern(); const [inputVal, setInputVal] = useState(query.query as string); diff --git a/x-pack/plugins/uptime/public/components/overview/query_bar/use_index_pattern.ts b/x-pack/plugins/uptime/public/components/overview/query_bar/use_index_pattern.ts deleted file mode 100644 index b0e567c40ed73..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/query_bar/use_index_pattern.ts +++ /dev/null @@ -1,25 +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 { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { getIndexPattern } from '../../../state/actions'; -import { selectIndexPattern } from '../../../state/selectors'; - -export const useIndexPattern = () => { - const dispatch = useDispatch(); - const indexPattern = useSelector(selectIndexPattern); - - useEffect(() => { - // we only use index pattern for kql queries - if (!indexPattern.index_pattern) { - dispatch(getIndexPattern()); - } - }, [indexPattern.index_pattern, dispatch]); - - return indexPattern; -}; diff --git a/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx b/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx new file mode 100644 index 0000000000000..e4c57dab0ffcf --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx @@ -0,0 +1,126 @@ +/* + * 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 { waitFor } from '@testing-library/react'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { MockRouter, MockKibanaProvider } from '../../../lib/helper/rtl_helpers'; +import { SyntaxType, useQueryBar, DEBOUNCE_INTERVAL } from './use_query_bar'; +import { MountWithReduxProvider } from '../../../lib'; +import * as URL from '../../../hooks/use_url_params'; +import * as ES_FILTERS from '../../../hooks/update_kuery_string'; +import { UptimeUrlParams } from '../../../lib/helper/url_params'; + +const SAMPLE_ES_FILTERS = `{"bool":{"should":[{"match_phrase":{"monitor.id":"NodeServer"}}],"minimum_should_match":1}}`; + +describe('useQueryBar', () => { + let DEFAULT_URL_PARAMS: UptimeUrlParams; + let wrapper: any; + let useUrlParamsSpy: jest.SpyInstance<[URL.GetUrlParams, URL.UpdateUrlParams]>; + let useGetUrlParamsSpy: jest.SpyInstance; + let updateUrlParamsMock: jest.Mock; + let useUpdateKueryStringSpy: jest.SpyInstance; + + beforeEach(() => { + DEFAULT_URL_PARAMS = { + absoluteDateRangeStart: 100, + absoluteDateRangeEnd: 200, + autorefreshInterval: 10000, + autorefreshIsPaused: true, + dateRangeStart: 'now-15m', + dateRangeEnd: 'now', + excludedFilters: '', + filters: '', + query: '', + search: 'monitor.id: "My-Monitor"', + statusFilter: '', + }; + wrapper = ({ children }: any) => ( + + + {children} + + + ); + useUrlParamsSpy = jest.spyOn(URL, 'useUrlParams'); + useGetUrlParamsSpy = jest.spyOn(URL, 'useGetUrlParams'); + useUpdateKueryStringSpy = jest.spyOn(ES_FILTERS, 'useUpdateKueryString'); + updateUrlParamsMock = jest.fn(); + + useUrlParamsSpy.mockImplementation(() => [jest.fn(), updateUrlParamsMock]); + useGetUrlParamsSpy.mockReturnValue(DEFAULT_URL_PARAMS); + useUpdateKueryStringSpy.mockReturnValue([SAMPLE_ES_FILTERS]); + }); + + it.each([ + [SyntaxType.text, undefined, SAMPLE_ES_FILTERS, '', 'monitor.id: "My-Other-Monitor"', false, 0], + [ + SyntaxType.kuery, + new Error('there was a problem'), + SAMPLE_ES_FILTERS, + '', + 'monitor.id: "My-Other-Monitor"', + false, + 0, + ], + [SyntaxType.kuery, undefined, undefined, '', 'monitor.id: "My-Other-Monitor"', false, 0], + [SyntaxType.text, undefined, undefined, '', 'monitor.id: "My-Other-Monitor"', false, 0], + [SyntaxType.text, undefined, undefined, 'my-search', 'monitor.id: "My-Other-Monitor"', true, 1], + [SyntaxType.kuery, undefined, undefined, 'my-search', '', true, 1], + [ + SyntaxType.kuery, + undefined, + SAMPLE_ES_FILTERS, + 'my-search', + 'monitor.id: "My-Monitor"', + true, + 1, + ], + ])( + 'updates URL only when conditions are appropriate', + /** + * This test is designed to prevent massive duplication of boilerplate; each set of parameters should trigger + * a different response from the hook. At the end, we wait for the debounce interval to elapse and then check + * whether the URL was updated. + * + * @param language the query syntax + * @param error an error resulting from parsing es filters + * @param esFilters the AST string generated from parsing kuery syntax + * @param search the simple text search + * @param query the new kuery entered by the user + * @param shouldExpectCall boolean denoting whether or not the test should expect the url to be updated + * @param calledTimes the number of times the test should expect the url to be updated + */ + async (language, error, esFilters, search, query, shouldExpectCall, calledTimes) => { + const { + result: { current }, + } = renderHook(() => useQueryBar(), { wrapper }); + + useUpdateKueryStringSpy.mockReturnValue([esFilters, error]); + useGetUrlParamsSpy.mockReturnValue({ + ...DEFAULT_URL_PARAMS, + search, + }); + + act(() => { + current.setQuery({ + query, + language, + }); + }); + + await waitFor(async () => { + await new Promise((r) => setInterval(r, DEBOUNCE_INTERVAL + 50)); + if (shouldExpectCall) { + expect(updateUrlParamsMock).toHaveBeenCalledTimes(calledTimes); + } else { + expect(updateUrlParamsMock).not.toHaveBeenCalled(); + } + }); + } + ); +}); diff --git a/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.ts b/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.ts index 2f2d8bf092ddf..4dd431e9617a3 100644 --- a/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.ts +++ b/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.ts @@ -9,9 +9,13 @@ import React, { useCallback, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; import { useDispatch } from 'react-redux'; import { Query } from 'src/plugins/data/common'; -import { useGetUrlParams, useUpdateKueryString, useUrlParams } from '../../../hooks'; +import { + useGetUrlParams, + useIndexPattern, + useUpdateKueryString, + useUrlParams, +} from '../../../hooks'; import { setEsKueryString } from '../../../state/actions'; -import { useIndexPattern } from './use_index_pattern'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { UptimePluginServices } from '../../../apps/plugin'; @@ -35,6 +39,8 @@ interface UseQueryBarUtils { submitImmediately: () => void; } +export const DEBOUNCE_INTERVAL = 250; + /** * Provides state management and automatic dispatching of a Query object. * @@ -44,7 +50,7 @@ export const useQueryBar = (): UseQueryBarUtils => { const dispatch = useDispatch(); const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useGetUrlParams(); - const { search, query: queryParam, filters: paramFilters } = params; + const { search, query: queryParam, filters: paramFilters, excludedFilters } = params; const { services: { storage }, @@ -64,14 +70,15 @@ export const useQueryBar = (): UseQueryBarUtils => { } ); - const { index_pattern: indexPattern } = useIndexPattern(); + const indexPattern = useIndexPattern(); - const updateUrlParams = useUrlParams()[1]; + const [, updateUrlParams] = useUrlParams(); const [esFilters, error] = useUpdateKueryString( indexPattern, query.language === SyntaxType.kuery ? (query.query as string) : undefined, - paramFilters + paramFilters, + excludedFilters ); const setEsKueryFilters = useCallback( @@ -92,7 +99,7 @@ export const useQueryBar = (): UseQueryBarUtils => { if (query.language === SyntaxType.text && queryParam !== query.query) { updateUrlParams({ query: query.query as string }); } - if (query.language === SyntaxType.kuery) { + if (query.language === SyntaxType.kuery && queryParam !== '') { updateUrlParams({ query: '' }); } }, [query.language, query.query, queryParam, updateUrlParams]); @@ -112,17 +119,18 @@ export const useQueryBar = (): UseQueryBarUtils => { useDebounce( () => { - if (query.language === SyntaxType.kuery && !error && esFilters) { + if (query.language === SyntaxType.kuery && !error && esFilters && search !== query.query) { updateUrlParams({ search: query.query as string }); } - if (query.language === SyntaxType.text) { + if (query.language === SyntaxType.text && search !== '') { updateUrlParams({ search: '' }); } - if (query.language === SyntaxType.kuery && query.query === '') { + // this calls when it probably doesn't need to + if (query.language === SyntaxType.kuery && query.query === '' && search !== '') { updateUrlParams({ search: '' }); } }, - 250, + DEBOUNCE_INTERVAL, [esFilters, error] ); diff --git a/x-pack/plugins/uptime/public/contexts/uptime_index_pattern_context.tsx b/x-pack/plugins/uptime/public/contexts/uptime_index_pattern_context.tsx new file mode 100644 index 0000000000000..580160bac4012 --- /dev/null +++ b/x-pack/plugins/uptime/public/contexts/uptime_index_pattern_context.tsx @@ -0,0 +1,42 @@ +/* + * 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, { createContext, useContext, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useFetcher } from '../../../observability/public'; +import { DataPublicPluginStart, IndexPattern } from '../../../../../src/plugins/data/public'; +import { selectDynamicSettings } from '../state/selectors'; +import { getDynamicSettings } from '../state/actions/dynamic_settings'; + +export const UptimeIndexPatternContext = createContext({} as IndexPattern); + +export const UptimeIndexPatternContextProvider: React.FC<{ data: DataPublicPluginStart }> = ({ + children, + data: { indexPatterns }, +}) => { + const { settings } = useSelector(selectDynamicSettings); + const dispatch = useDispatch(); + + useEffect(() => { + if (typeof settings === 'undefined') { + dispatch(getDynamicSettings()); + } + }, [dispatch, settings]); + + const heartbeatIndices = settings?.heartbeatIndices || ''; + + const { data } = useFetcher>(async () => { + if (heartbeatIndices) { + // this only creates an index pattern in memory, not as saved object + return indexPatterns.create({ title: heartbeatIndices }); + } + }, [heartbeatIndices]); + + return ; +}; + +export const useIndexPattern = () => useContext(UptimeIndexPatternContext); diff --git a/x-pack/plugins/uptime/public/hooks/__snapshots__/use_url_params.test.tsx.snap b/x-pack/plugins/uptime/public/hooks/__snapshots__/use_url_params.test.tsx.snap index 31b78a8f810f0..d8b148675dc62 100644 --- a/x-pack/plugins/uptime/public/hooks/__snapshots__/use_url_params.test.tsx.snap +++ b/x-pack/plugins/uptime/public/hooks/__snapshots__/use_url_params.test.tsx.snap @@ -210,7 +210,7 @@ exports[`useUrlParams deletes keys that do not have truthy values 1`] = ` } >
- {"pagination":"foo","absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-12","dateRangeEnd":"now","filters":"","search":"","statusFilter":"","focusConnectorField":false,"query":""} + {"pagination":"foo","absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-12","dateRangeEnd":"now","filters":"","excludedFilters":"","search":"","statusFilter":"","focusConnectorField":false,"query":""}