From 21c8d91a24bcecfc04e0430c428bf9415b7d7dea Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Mon, 13 Mar 2023 10:50:41 -0400 Subject: [PATCH] [8.7] [Synthetics] handle onboarding for onprem deployments (#152048) (#153163) # Backport This will backport the following commits from `main` to `8.7`: - [[Synthetics] handle onboarding for onprem deployments (#152048)](https://github.com/elastic/kibana/pull/152048) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/synthetics/common/constants/ui.ts | 2 + .../form_fields/service_locations.tsx | 2 +- .../getting_started_page.test.tsx | 175 +++++++++++++++ .../getting_started/getting_started_page.tsx | 211 +++++++++++++++--- .../getting_started/simple_monitor_form.tsx | 2 +- .../private_locations/add_location_flyout.tsx | 56 +++-- .../private_locations/empty_locations.tsx | 45 ++-- .../hooks/use_locations_api.test.tsx | 12 +- .../hooks/use_locations_api.ts | 2 +- .../private_locations/location_form.tsx | 132 +++++------ .../private_locations/manage_empty_state.tsx | 17 +- .../manage_private_locations.test.tsx | 8 +- .../manage_private_locations.tsx | 25 ++- .../private_locations/policy_name.tsx | 14 +- .../hooks/use_location_name.test.tsx | 2 - .../public/apps/synthetics/state/index.ts | 1 + .../synthetics/utils/testing/rtl_helpers.tsx | 8 +- .../hooks/use_locations_api.test.tsx | 33 ++- .../hooks/use_locations_api.ts | 2 +- .../manage_locations/location_form.tsx | 2 +- .../manage_locations_flyout.tsx | 4 +- 21 files changed, 581 insertions(+), 174 deletions(-) create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx diff --git a/x-pack/plugins/synthetics/common/constants/ui.ts b/x-pack/plugins/synthetics/common/constants/ui.ts index 57737c2cef192..40c1d26c58cbc 100644 --- a/x-pack/plugins/synthetics/common/constants/ui.ts +++ b/x-pack/plugins/synthetics/common/constants/ui.ts @@ -27,6 +27,8 @@ export const GETTING_STARTED_ROUTE = '/monitors/getting-started'; export const SETTINGS_ROUTE = '/settings'; +export const PRIVATE_LOCATIOSN_ROUTE = '/settings/private-locations'; + export const SYNTHETICS_SETTINGS_ROUTE = '/settings/:tabId'; export const CERTIFICATES_ROUTE = '/certificates'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/form_fields/service_locations.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/form_fields/service_locations.tsx index 9f720268e5e09..5c6d242f87b37 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/form_fields/service_locations.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/form_fields/service_locations.tsx @@ -66,7 +66,7 @@ export const ServiceLocationsField = ({ const SELECT_ONE_OR_MORE_LOCATIONS = i18n.translate( 'xpack.synthetics.monitorManagement.selectOneOrMoreLocations', { - defaultMessage: 'Select one or more locations', + defaultMessage: 'Select one or more locations.', } ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx new file mode 100644 index 0000000000000..811a2b676503e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx @@ -0,0 +1,175 @@ +/* + * 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 * as permissionsHooks from '../../hooks/use_fleet_permissions'; +import { render } from '../../utils/testing/rtl_helpers'; +import { GettingStartedPage } from './getting_started_page'; +import * as privateLocationsHooks from '../settings/private_locations/hooks/use_locations_api'; + +describe('GettingStartedPage', () => { + beforeEach(() => { + jest.spyOn(privateLocationsHooks, 'usePrivateLocationsAPI').mockReturnValue({ + loading: false, + privateLocations: [], + deleteLoading: false, + onSubmit: jest.fn(), + onDelete: jest.fn(), + formData: undefined, + }); + jest.spyOn(permissionsHooks, 'useCanManagePrivateLocation').mockReturnValue(true); + }); + it('works with cloud locations', () => { + const { getByText } = render(, { + state: { + serviceLocations: { + locations: [ + { + id: 'us_central', + label: 'Us Central', + }, + { + id: 'us_east', + label: 'US East', + }, + ], + locationsLoaded: true, + loading: false, + }, + agentPolicies: { + loading: false, + }, + }, + }); + + // page is loaded + expect(getByText('Create a single page browser monitor')).toBeInTheDocument(); + }); + + it('serves on prem getting started experience when locations are not available', () => { + const { getByText } = render(, { + state: { + serviceLocations: { + locations: [], + locationsLoaded: true, + loading: false, + }, + }, + }); + + // page is loaded + expect(getByText('Get started with synthetic monitoring')).toBeInTheDocument(); + }); + + it('shows need agent flyout when isAddingNewPrivateLocation is true and agentPolicies.length === 0', async () => { + const { getByText, getByRole, queryByLabelText } = render(, { + state: { + serviceLocations: { + locations: [], + locationsLoaded: true, + loading: false, + }, + agentPolicies: { + data: { + total: 0, + }, + isAddingNewPrivateLocation: true, + }, + }, + }); + + // page is loaded + expect(getByText('Get started with synthetic monitoring')).toBeInTheDocument(); + + expect(getByRole('heading', { name: 'Create private location', level: 2 })); + expect(getByText('No agent policies found')).toBeInTheDocument(); + expect(getByRole('link', { name: 'Create agent policy' })).toBeEnabled(); + expect(queryByLabelText('Location name')).not.toBeInTheDocument(); + expect(queryByLabelText('Agent policy')).not.toBeInTheDocument(); + }); + + it('shows add location flyout when isAddingNewPrivateLocation is true and agentPolicies.length > 0', async () => { + const { getByText, getByRole, getByLabelText, queryByText } = render(, { + state: { + serviceLocations: { + locations: [], + locationsLoaded: true, + loading: false, + }, + agentPolicies: { + data: { + total: 1, + items: [{}], + }, + isAddingNewPrivateLocation: true, + }, + }, + }); + + // page is loaded + expect(getByText('Get started with synthetic monitoring')).toBeInTheDocument(); + + expect(getByRole('heading', { name: 'Create private location', level: 2 })); + expect(queryByText('No agent policies found')).not.toBeInTheDocument(); + expect(getByLabelText('Location name')).toBeInTheDocument(); + expect(getByLabelText('Agent policy')).toBeInTheDocument(); + }); + + it('shows permissions callout and hides form when agent policies are available but the user does not have permissions', async () => { + jest.spyOn(permissionsHooks, 'useCanManagePrivateLocation').mockReturnValue(false); + const { getByText, getByRole, queryByLabelText, queryByRole } = render(, { + state: { + serviceLocations: { + locations: [], + locationsLoaded: true, + loading: false, + }, + agentPolicies: { + data: { + total: 1, + items: [{}], + }, + isAddingNewPrivateLocation: true, + }, + }, + }); + + // page is loaded + expect(getByText('Get started with synthetic monitoring')).toBeInTheDocument(); + + expect(getByRole('heading', { name: 'Create private location', level: 2 })); + expect(queryByLabelText('Location name')).not.toBeInTheDocument(); + expect(queryByLabelText('Agent policy')).not.toBeInTheDocument(); + expect(queryByRole('button', { name: 'Save' })).not.toBeInTheDocument(); + expect(getByText("You're missing some Kibana privileges to manage private locations")); + }); + + it('shows permissions callout when agent policy is needed but the user does not have permissions', async () => { + jest.spyOn(permissionsHooks, 'useCanManagePrivateLocation').mockReturnValue(false); + const { getByText, getByRole, queryByLabelText } = render(, { + state: { + serviceLocations: { + locations: [], + locationsLoaded: true, + loading: false, + }, + agentPolicies: { + data: undefined, // data will be undefined when user does not have permissions + isAddingNewPrivateLocation: true, + }, + }, + }); + + // page is loaded + expect(getByText('Get started with synthetic monitoring')).toBeInTheDocument(); + + expect(getByRole('heading', { name: 'Create private location', level: 2 })); + expect(queryByLabelText('Location name')).not.toBeInTheDocument(); + expect(queryByLabelText('Agent policy')).not.toBeInTheDocument(); + expect(getByText("You're missing some Kibana privileges to manage private locations")); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx index abcdd2c405465..a41439e1288ab 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx @@ -5,54 +5,152 @@ * 2.0. */ -import React, { useEffect } from 'react'; -import { EuiEmptyPrompt, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; -import { useDispatch } from 'react-redux'; +import React, { useEffect, useCallback } from 'react'; +import { + EuiEmptyPrompt, + EuiLink, + EuiSpacer, + EuiText, + EuiButton, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; -import { useBreadcrumbs } from '../../hooks'; -import { getServiceLocations } from '../../state'; +import { useBreadcrumbs, useLocations, useFleetPermissions } from '../../hooks'; +import { usePrivateLocationsAPI } from '../settings/private_locations/hooks/use_locations_api'; +import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_flyout'; +import { + getServiceLocations, + selectAddingNewPrivateLocation, + setAddingNewPrivateLocation, + getAgentPoliciesAction, + selectAgentPolicies, +} from '../../state'; import { MONITOR_ADD_ROUTE } from '../../../../../common/constants/ui'; +import { PrivateLocation } from '../../../../../common/runtime_types'; import { SimpleMonitorForm } from './simple_monitor_form'; +import { AddLocationFlyout } from '../settings/private_locations/add_location_flyout'; export const GettingStartedPage = () => { const dispatch = useDispatch(); const history = useHistory(); + const { canReadAgentPolicies } = useFleetPermissions(); + useEffect(() => { dispatch(getServiceLocations()); - }, [dispatch]); + if (canReadAgentPolicies) { + dispatch(getAgentPoliciesAction.get()); + } + }, [canReadAgentPolicies, dispatch]); useBreadcrumbs([{ text: MONITORING_OVERVIEW_LABEL }]); // No extra breadcrumbs on overview - return ( + const { locations, loading: allLocationsLoading } = useLocations(); + const { loading: agentPoliciesLoading } = useSelector(selectAgentPolicies); + const loading = allLocationsLoading || agentPoliciesLoading; + + const hasNoLocations = !allLocationsLoading && locations.length === 0; + + return !loading ? ( + {hasNoLocations ? ( + + ) : ( + {CREATE_SINGLE_PAGE_LABEL}} + layout="horizontal" + color="plain" + body={ + <> + + {OR_LABEL}{' '} + + {SELECT_DIFFERENT_MONITOR} + + {i18n.translate('xpack.synthetics.gettingStarted.createSingle.description', { + defaultMessage: ' to get started with Elastic Synthetics Monitoring.', + })} + + + + + } + /> + )} + + ) : ( + + ); +}; + +export const GettingStartedOnPrem = () => { + const dispatch = useDispatch(); + + useBreadcrumbs([{ text: MONITORING_OVERVIEW_LABEL }]); // No extra breadcrumbs on overview + + const isAddingNewLocation = useSelector(selectAddingNewPrivateLocation); + + const setIsAddingNewLocation = useCallback( + (val: boolean) => dispatch(setAddingNewPrivateLocation(val)), + [dispatch] + ); + + const { onSubmit, privateLocations, loading } = usePrivateLocationsAPI(); + + const handleSubmit = (formData: PrivateLocation) => { + onSubmit(formData); + }; + + // make sure flyout is closed when first visiting the page + useEffect(() => { + setIsAddingNewLocation(false); + }, [setIsAddingNewLocation]); + + return ( + <> {CREATE_SINGLE_PAGE_LABEL}} + title={

{GET_STARTED_LABEL}

} layout="horizontal" color="plain" body={ - <> - - {OR_LABEL}{' '} - + + {CREATE_LOCATION_DESCRIPTION} + + {PUBLIC_LOCATION_DESCRIPTION} + + + setIsAddingNewLocation(true)} > - {SELECT_DIFFERENT_MONITOR} - - {i18n.translate('xpack.synthetics.gettingStarted.createSingle.description', { - defaultMessage: ' to get started with Elastic Synthetics Monitoring', - })} - - - - + {CREATE_LOCATION_LABEL} + + + } /> - + + {isAddingNewLocation ? ( + + ) : null} + ); }; @@ -72,6 +170,69 @@ const CREATE_SINGLE_PAGE_LABEL = i18n.translate( } ); +const GET_STARTED_LABEL = i18n.translate('xpack.synthetics.gettingStarted.createLocationHeading', { + defaultMessage: 'Get started with synthetic monitoring', +}); + +const PRIVATE_LOCATION_LABEL = i18n.translate( + 'xpack.synthetics.gettingStarted.privateLocationLabel', + { + defaultMessage: 'private location', + } +); + +const CREATE_LOCATION_LABEL = i18n.translate( + 'xpack.synthetics.gettingStarted.createLocationLabel', + { + defaultMessage: 'Create location', + } +); + +const CREATE_LOCATION_DESCRIPTION = ( + + {PRIVATE_LOCATION_LABEL} + + ), + }} + /> +); +const PUBLIC_LOCATION_DESCRIPTION = ( + + {i18n.translate( + 'xpack.synthetics.gettingStarted.gettingStartedLabel.elasticManagedLink', + { + defaultMessage: 'Elastic’s global managed testing infrastructure', + } + )} + + ), + link: ( + + {i18n.translate( + 'xpack.synthetics.gettingStarted.gettingStartedLabel.elasticCloudDeployments', + { + defaultMessage: 'Elastic Cloud', + } + )} + + ), + }} + /> +); + const SELECT_DIFFERENT_MONITOR = i18n.translate( 'xpack.synthetics.gettingStarted.gettingStartedLabel.selectDifferentMonitor', { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx index 114be36195215..049d213249cb9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx @@ -126,7 +126,7 @@ export const WEBSITE_URL_PLACEHOLDER = i18n.translate( export const WEBSITE_URL_HELP_TEXT = i18n.translate( 'xpack.synthetics.monitorManagement.websiteUrlHelpText', { - defaultMessage: `For example, your company's homepage or https://elastic.co`, + defaultMessage: `For example, your company's homepage or https://elastic.co.`, } ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx index 51e4cce0f2fb0..4a9dcdfec47d8 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx @@ -24,6 +24,7 @@ import { useFormWrapped } from '../../../../../hooks/use_form_wrapped'; import { PrivateLocation } from '../../../../../../common/runtime_types'; import { FleetPermissionsCallout } from '../../common/components/permissions'; import { LocationForm } from './location_form'; +import { ManageEmptyState } from './manage_empty_state'; export const AddLocationFlyout = ({ onSubmit, @@ -69,32 +70,39 @@ export const AddLocationFlyout = ({ - {!canManagePrivateLocation && } - - + hasFleetPermissions={canManagePrivateLocation} + showEmptyLocations={false} + > + {!canManagePrivateLocation && } + + - - - - - {CANCEL_LABEL} - - - - - {SAVE_LABEL} - - - - + {canManagePrivateLocation && ( + + + + + {CANCEL_LABEL} + + + + + {SAVE_LABEL} + + + + + )} ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/empty_locations.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/empty_locations.tsx index 9bee7b8c2e222..6f20a5c03a5d1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/empty_locations.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/empty_locations.tsx @@ -6,21 +6,26 @@ */ import React from 'react'; +import { useHistory } from 'react-router-dom'; import { EuiEmptyPrompt, EuiButton, EuiLink, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useDispatch } from 'react-redux'; +import { PRIVATE_LOCATIOSN_ROUTE } from '../../../../../../common/constants'; import { setAddingNewPrivateLocation, setManageFlyoutOpen } from '../../../state/private_locations'; export const EmptyLocations = ({ inFlyout = true, setIsAddingNew, disabled, + redirectToSettings, }: { inFlyout?: boolean; disabled?: boolean; setIsAddingNew?: (val: boolean) => void; + redirectToSettings?: boolean; }) => { const dispatch = useDispatch(); + const history = useHistory(); return ( } actions={ - { - setIsAddingNew?.(true); - dispatch(setManageFlyoutOpen(true)); - dispatch(setAddingNewPrivateLocation(true)); - }} - > - {ADD_LOCATION} - + redirectToSettings ? ( + + {ADD_LOCATION} + + ) : ( + { + setIsAddingNew?.(true); + dispatch(setManageFlyoutOpen(true)); + dispatch(setAddingNewPrivateLocation(true)); + }} + > + {ADD_LOCATION} + + ) } footer={ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.test.tsx index ce80c7d97e71e..95093c4c00562 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.test.tsx @@ -9,11 +9,11 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { WrappedHelper } from '../../../../utils/testing'; import { getServiceLocations } from '../../../../state/service_locations'; import { setAddingNewPrivateLocation } from '../../../../state/private_locations'; -import { useLocationsAPI } from './use_locations_api'; +import { usePrivateLocationsAPI } from './use_locations_api'; import * as locationAPI from '../../../../state/private_locations/api'; import * as reduxHooks from 'react-redux'; -describe('useLocationsAPI', () => { +describe('usePrivateLocationsAPI', () => { const dispatch = jest.fn(); const addAPI = jest.spyOn(locationAPI, 'addSyntheticsPrivateLocations').mockResolvedValue({ locations: [], @@ -25,7 +25,7 @@ describe('useLocationsAPI', () => { jest.spyOn(reduxHooks, 'useDispatch').mockReturnValue(dispatch); it('returns expected results', () => { - const { result } = renderHook(() => useLocationsAPI(), { + const { result } = renderHook(() => usePrivateLocationsAPI(), { wrapper: WrappedHelper, }); @@ -46,7 +46,7 @@ describe('useLocationsAPI', () => { ], }); it('returns expected results after data', async () => { - const { result, waitForNextUpdate } = renderHook(() => useLocationsAPI(), { + const { result, waitForNextUpdate } = renderHook(() => usePrivateLocationsAPI(), { wrapper: WrappedHelper, }); @@ -73,7 +73,7 @@ describe('useLocationsAPI', () => { }); it('adds location on submit', async () => { - const { result, waitForNextUpdate } = renderHook(() => useLocationsAPI(), { + const { result, waitForNextUpdate } = renderHook(() => usePrivateLocationsAPI(), { wrapper: WrappedHelper, }); @@ -109,7 +109,7 @@ describe('useLocationsAPI', () => { }); it('deletes location on delete', async () => { - const { result, waitForNextUpdate } = renderHook(() => useLocationsAPI(), { + const { result, waitForNextUpdate } = renderHook(() => usePrivateLocationsAPI(), { wrapper: WrappedHelper, }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts index 8678328445a62..64b1060e43006 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts @@ -17,7 +17,7 @@ import { } from '../../../../state/private_locations/api'; import { PrivateLocation } from '../../../../../../../common/runtime_types'; -export const useLocationsAPI = () => { +export const usePrivateLocationsAPI = () => { const [formData, setFormData] = useState(); const [deleteId, setDeleteId] = useState(); const [privateLocations, setPrivateLocations] = useState([]); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx index a001c503697b1..f3bdb8d2fa1df 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx @@ -46,78 +46,47 @@ export const LocationForm = ({ return ( <> {data?.items.length === 0 && } - - - + { - return privateLocations.some((loc) => loc.label === val) - ? NAME_ALREADY_EXISTS - : undefined; - }, - })} - /> - - - - - - - -

- { - elastic-agent-complete, - link: ( - - - - ), - }} - /> - } -

-
- - - {selectedPolicy?.agents === 0 && ( - + { + return privateLocations.some((loc) => loc.label === val) + ? NAME_ALREADY_EXISTS + : undefined; + }, + })} + /> +
+ + + + + +

{ elastic-agent-complete, link: ( - )} - + + + {selectedPolicy?.agents === 0 && ( + +

+ { + + + + ), + }} + /> + } +

+
+ )} +
+ ) : null} ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx index 92768f48a83ef..216ae53b04286 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx @@ -15,15 +15,24 @@ import { selectAgentPolicies } from '../../../state/private_locations'; export const ManageEmptyState: FC<{ privateLocations: PrivateLocation[]; hasFleetPermissions: boolean; - setIsAddingNew: (val: boolean) => void; -}> = ({ children, privateLocations, setIsAddingNew, hasFleetPermissions }) => { + setIsAddingNew?: (val: boolean) => void; + showNeedAgentPolicy?: boolean; + showEmptyLocations?: boolean; +}> = ({ + children, + privateLocations, + setIsAddingNew, + hasFleetPermissions, + showNeedAgentPolicy = true, + showEmptyLocations = true, +}) => { const { data: agentPolicies } = useSelector(selectAgentPolicies); - if (agentPolicies?.total === 0) { + if (agentPolicies?.total === 0 && showNeedAgentPolicy) { return ; } - if (privateLocations.length === 0) { + if (privateLocations.length === 0 && showEmptyLocations) { return ; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx index b4e406353f2a1..484157d3e28a9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx @@ -21,7 +21,11 @@ jest.mock('../../../contexts/synthetics_settings_context'); describe('', () => { beforeEach(() => { jest.spyOn(permissionsHooks, 'useCanManagePrivateLocation').mockReturnValue(true); - jest.spyOn(locationHooks, 'useLocationsAPI').mockReturnValue({ + jest.spyOn(permissionsHooks, 'useFleetPermissions').mockReturnValue({ + canReadAgentPolicies: true, + canSaveIntegrations: false, + }); + jest.spyOn(locationHooks, 'usePrivateLocationsAPI').mockReturnValue({ formData: {} as PrivateLocation, loading: false, onSubmit: jest.fn(), @@ -120,7 +124,7 @@ describe('', () => { canSaveIntegrations: hasFleetPermissions, canReadAgentPolicies: hasFleetPermissions, }); - jest.spyOn(locationHooks, 'useLocationsAPI').mockReturnValue({ + jest.spyOn(locationHooks, 'usePrivateLocationsAPI').mockReturnValue({ formData: {} as PrivateLocation, loading: false, onSubmit: jest.fn(), diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx index e8246aa13221e..fc199eb4eb235 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx @@ -4,15 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { EuiSpacer } from '@elastic/eui'; import { LoadingState } from '../../monitors_page/overview/overview/monitor_detail_flyout'; import { PrivateLocationsTable } from './locations_table'; -import { useCanManagePrivateLocation } from '../../../hooks'; +import { useCanManagePrivateLocation, useFleetPermissions } from '../../../hooks'; import { ManageEmptyState } from './manage_empty_state'; import { AddLocationFlyout } from './add_location_flyout'; -import { useLocationsAPI } from './hooks/use_locations_api'; +import { usePrivateLocationsAPI } from './hooks/use_locations_api'; import { getAgentPoliciesAction, selectAddingNewPrivateLocation, @@ -26,16 +26,27 @@ export const ManagePrivateLocations = () => { const dispatch = useDispatch(); const isAddingNew = useSelector(selectAddingNewPrivateLocation); - const setIsAddingNew = (val: boolean) => dispatch(setAddingNewPrivateLocation(val)); + const setIsAddingNew = useCallback( + (val: boolean) => dispatch(setAddingNewPrivateLocation(val)), + [dispatch] + ); - const { onSubmit, loading, privateLocations, onDelete, deleteLoading } = useLocationsAPI(); + const { onSubmit, loading, privateLocations, onDelete, deleteLoading } = usePrivateLocationsAPI(); + const { canReadAgentPolicies } = useFleetPermissions(); const canManagePrivateLocation = useCanManagePrivateLocation(); + // make sure flyout is closed when first visiting the page + useEffect(() => { + setIsAddingNew(false); + }, [setIsAddingNew]); + useEffect(() => { - dispatch(getAgentPoliciesAction.get()); + if (canReadAgentPolicies) { + dispatch(getAgentPoliciesAction.get()); + } dispatch(getServiceLocations()); - }, [dispatch]); + }, [dispatch, canReadAgentPolicies]); const handleSubmit = (formData: PrivateLocation) => { onSubmit(formData); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/policy_name.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/policy_name.tsx index 2c44bc80fed36..b176b14619d6e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/policy_name.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/policy_name.tsx @@ -43,11 +43,15 @@ export const PolicyName = ({ agentPolicyId }: { agentPolicyId: string }) => { ) : ( agentPolicyId )} -     - - {AGENTS_LABEL} - {policy?.agents} - + {canReadAgentPolicies && ( + <> +     + + {AGENTS_LABEL} + {policy?.agents} + + + )}
); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_location_name.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_location_name.test.tsx index 2cc4002910de1..d03b70d94768c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_location_name.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_location_name.test.tsx @@ -52,11 +52,9 @@ describe('useLocationName', () => { { wrapper: WrapperWithState } ); expect(result.current).toEqual({ - geo: { lat: 41.25, lon: -95.86 }, id: 'us_central', isServiceManaged: true, label: 'US Central', - status: 'ga', url: 'mockUrl', }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index.ts index 84d11cdfa1b75..09af66556532e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index.ts @@ -18,3 +18,4 @@ export * from './monitor_details'; export * from './overview'; export * from './browser_journey'; export * from './ping_status'; +export * from './private_locations'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx index d8e2206693392..dda125b640603 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx @@ -14,7 +14,7 @@ import { RenderOptions, } from '@testing-library/react'; import { Router, Route } from 'react-router-dom'; -import { merge } from 'lodash'; +import { merge, mergeWith } from 'lodash'; import { createMemoryHistory, History } from 'history'; import { CoreStart } from '@kbn/core/public'; import { I18nProvider } from '@kbn/i18n-react'; @@ -225,7 +225,11 @@ export function WrappedHelper({ path, history = createMemoryHistory(), }: RenderRouterOptions & { children: ReactElement; useRealStore?: boolean }) { - const testState: AppState = merge({}, mockState, state); + const testState: AppState = mergeWith({}, mockState, state, (objValue, srcValue) => { + if (Array.isArray(objValue)) { + return srcValue; + } + }); if (url) { history = getHistoryFromUrl(url); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.test.tsx index 736c987a37351..91ed400d3bf80 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.test.tsx @@ -8,11 +8,11 @@ import { renderHook } from '@testing-library/react-hooks'; import { defaultCore, WrappedHelper } from '../../../../../apps/synthetics/utils/testing'; -import { useLocationsAPI } from './use_locations_api'; +import { usePrivateLocationsAPI } from './use_locations_api'; -describe('useLocationsAPI', () => { +describe('usePrivateLocationsAPI', () => { it('returns expected results', () => { - const { result } = renderHook(() => useLocationsAPI({ isOpen: false }), { + const { result } = renderHook(() => usePrivateLocationsAPI({ isOpen: false }), { wrapper: WrappedHelper, }); @@ -38,9 +38,12 @@ describe('useLocationsAPI', () => { }, }); it('returns expected results after data', async () => { - const { result, waitForNextUpdate } = renderHook(() => useLocationsAPI({ isOpen: true }), { - wrapper: WrappedHelper, - }); + const { result, waitForNextUpdate } = renderHook( + () => usePrivateLocationsAPI({ isOpen: true }), + { + wrapper: WrappedHelper, + } + ); expect(result.current).toEqual( expect.objectContaining({ @@ -65,9 +68,12 @@ describe('useLocationsAPI', () => { }); it('adds location on submit', async () => { - const { result, waitForNextUpdate } = renderHook(() => useLocationsAPI({ isOpen: true }), { - wrapper: WrappedHelper, - }); + const { result, waitForNextUpdate } = renderHook( + () => usePrivateLocationsAPI({ isOpen: true }), + { + wrapper: WrappedHelper, + } + ); await waitForNextUpdate(); @@ -121,9 +127,12 @@ describe('useLocationsAPI', () => { }, }); - const { result, waitForNextUpdate } = renderHook(() => useLocationsAPI({ isOpen: true }), { - wrapper: WrappedHelper, - }); + const { result, waitForNextUpdate } = renderHook( + () => usePrivateLocationsAPI({ isOpen: true }), + { + wrapper: WrappedHelper, + } + ); await waitForNextUpdate(); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts index c3dfa118a0bc6..caa603f5db6b5 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts @@ -14,7 +14,7 @@ import { getSyntheticsPrivateLocations, } from '../../../../state/private_locations/api'; -export const useLocationsAPI = ({ isOpen }: { isOpen: boolean }) => { +export const usePrivateLocationsAPI = ({ isOpen }: { isOpen: boolean }) => { const [formData, setFormData] = useState(); const [deleteId, setDeleteId] = useState(); const [privateLocations, setPrivateLocations] = useState([]); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/location_form.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/location_form.tsx index a15629a99e3d9..99aa2f6f8e1d5 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/location_form.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/location_form.tsx @@ -67,7 +67,7 @@ export const LocationForm = ({ { elastic-agent-complete, link: ( diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx index 805097312d4e8..870f96190c9f1 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx @@ -27,7 +27,7 @@ import { AddLocationFlyout } from './add_location_flyout'; import { ClientPluginsStart } from '../../../../plugin'; import { getServiceLocations } from '../../../state/actions'; import { PrivateLocationsList } from './locations_list'; -import { useLocationsAPI } from './hooks/use_locations_api'; +import { usePrivateLocationsAPI } from './hooks/use_locations_api'; import { getAgentPoliciesAction, selectAddingNewPrivateLocation, @@ -49,7 +49,7 @@ export const ManageLocationsFlyout = () => { const setIsAddingNew = (val: boolean) => dispatch(setAddingNewPrivateLocation(val)); - const { onSubmit, loading, privateLocations, onDelete } = useLocationsAPI({ + const { onSubmit, loading, privateLocations, onDelete } = usePrivateLocationsAPI({ isOpen, });