diff --git a/x-pack/plugins/uptime/public/components/certificates/cert_refresh_btn.tsx b/x-pack/plugins/uptime/public/components/certificates/cert_refresh_btn.tsx index d0823276f1885..39661fe84a4be 100644 --- a/x-pack/plugins/uptime/public/components/certificates/cert_refresh_btn.tsx +++ b/x-pack/plugins/uptime/public/components/certificates/cert_refresh_btn.tsx @@ -20,7 +20,11 @@ export const CertRefreshBtn = () => { const { refreshApp } = useContext(UptimeRefreshContext); return ( - + diff --git a/x-pack/plugins/uptime/public/components/common/header/__snapshots__/page_header.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/header/__snapshots__/page_header.test.tsx.snap deleted file mode 100644 index 05a78624848c6..0000000000000 --- a/x-pack/plugins/uptime/public/components/common/header/__snapshots__/page_header.test.tsx.snap +++ /dev/null @@ -1,907 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PageHeader shallow renders extra links: page_header_with_extra_links 1`] = ` -Array [ - @media only screen and (max-width:1024px) and (min-width:868px) { - -} - -@media only screen and (max-width:880px) { - -} - -
-
-
-
-

- Uptime is now previewing support for scripted multi-step availability checks. This means you can interact with elements of a webpage and check the availability of an entire journey (such as making a purchase or signing into a system) instead of just a simple single page up/down check. Please click below to read more and, if you'd like to be one of the first to use these capabilities, you can download our preview synthetics agent and view your synthetic checks in Uptime. -

-
- -
- -
-
-
-
, - @media only screen and (max-width:1024px) and (min-width:868px) { - -} - -@media only screen and (max-width:880px) { - -} - -
, - @media only screen and (max-width:1024px) and (min-width:868px) { - .c0.c0.c0 .euiSuperDatePicker__flexWrapper { - width: 500px; - } -} - -@media only screen and (max-width:880px) { - .c0.c0.c0 { - -webkit-box-flex: 1; - -webkit-flex-grow: 1; - -ms-flex-positive: 1; - flex-grow: 1; - } - - .c0.c0.c0 .euiSuperDatePicker__flexWrapper { - width: calc(100% + 8px); - } -} - -
- -
-
-
- -
-
-
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
- - - -
-
-
-
, - @media only screen and (max-width:1024px) and (min-width:868px) { - -} - -@media only screen and (max-width:880px) { - -} - -
, -] -`; - -exports[`PageHeader shallow renders with the date picker: page_header_with_date_picker 1`] = ` -Array [ - @media only screen and (max-width:1024px) and (min-width:868px) { - -} - -@media only screen and (max-width:880px) { - -} - -
-
-
-
-

- Uptime is now previewing support for scripted multi-step availability checks. This means you can interact with elements of a webpage and check the availability of an entire journey (such as making a purchase or signing into a system) instead of just a simple single page up/down check. Please click below to read more and, if you'd like to be one of the first to use these capabilities, you can download our preview synthetics agent and view your synthetic checks in Uptime. -

-
- -
- -
-
-
-
, - @media only screen and (max-width:1024px) and (min-width:868px) { - -} - -@media only screen and (max-width:880px) { - -} - -
, - @media only screen and (max-width:1024px) and (min-width:868px) { - .c0.c0.c0 .euiSuperDatePicker__flexWrapper { - width: 500px; - } -} - -@media only screen and (max-width:880px) { - .c0.c0.c0 { - -webkit-box-flex: 1; - -webkit-flex-grow: 1; - -ms-flex-positive: 1; - flex-grow: 1; - } - - .c0.c0.c0 .euiSuperDatePicker__flexWrapper { - width: calc(100% + 8px); - } -} - -
- -
-
-
- -
-
-
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
- - - -
-
-
-
, - @media only screen and (max-width:1024px) and (min-width:868px) { - -} - -@media only screen and (max-width:880px) { - -} - -
, -] -`; - -exports[`PageHeader shallow renders without the date picker: page_header_no_date_picker 1`] = ` -Array [ - @media only screen and (max-width:1024px) and (min-width:868px) { - -} - -@media only screen and (max-width:880px) { - -} - -
-
-
-
-

- Uptime is now previewing support for scripted multi-step availability checks. This means you can interact with elements of a webpage and check the availability of an entire journey (such as making a purchase or signing into a system) instead of just a simple single page up/down check. Please click below to read more and, if you'd like to be one of the first to use these capabilities, you can download our preview synthetics agent and view your synthetic checks in Uptime. -

-
- -
- -
-
-
-
, - @media only screen and (max-width:1024px) and (min-width:868px) { - -} - -@media only screen and (max-width:880px) { - -} - -
, - @media only screen and (max-width:1024px) and (min-width:868px) { - .c0.c0.c0 .euiSuperDatePicker__flexWrapper { - width: 500px; - } -} - -@media only screen and (max-width:880px) { - .c0.c0.c0 { - -webkit-box-flex: 1; - -webkit-flex-grow: 1; - -ms-flex-positive: 1; - flex-grow: 1; - } - - .c0.c0.c0 .euiSuperDatePicker__flexWrapper { - width: calc(100% + 8px); - } -} - -
- -
-
-
- -
-
-
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
- - - -
-
-
-
, - @media only screen and (max-width:1024px) and (min-width:868px) { - -} - -@media only screen and (max-width:880px) { - -} - -
, -] -`; diff --git a/x-pack/plugins/uptime/public/components/common/header/page_header.test.tsx b/x-pack/plugins/uptime/public/components/common/header/page_header.test.tsx index cde87e736d09c..e3c62fb2a48f2 100644 --- a/x-pack/plugins/uptime/public/components/common/header/page_header.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/header/page_header.test.tsx @@ -5,34 +5,64 @@ */ import React from 'react'; +import moment from 'moment'; import { PageHeader } from './page_header'; -import { renderWithRouter, MountWithReduxProvider } from '../../../lib'; +import { Ping } from '../../../../common/runtime_types'; +import { renderWithRouter } from '../../../lib'; +import { mockReduxHooks } from '../../../lib/helper/test_helpers'; describe('PageHeader', () => { + const monitorName = 'sample monitor'; + const defaultMonitorId = 'always-down'; + + const defaultMonitorStatus: Ping = { + docId: 'few213kl', + timestamp: moment(new Date()).subtract(15, 'm').toString(), + monitor: { + duration: { + us: 1234567, + }, + id: defaultMonitorId, + status: 'up', + type: 'http', + name: monitorName, + }, + url: { + full: 'https://www.elastic.co/', + }, + }; + + beforeEach(() => { + mockReduxHooks(defaultMonitorStatus); + }); + + it('does not render dynamic elements by default', () => { + const component = renderWithRouter(); + + expect(component.find('[data-test-subj="superDatePickerShowDatesButton"]').length).toBe(0); + expect(component.find('[data-test-subj="certificatesRefreshButton"]').length).toBe(0); + expect(component.find('[data-test-subj="monitorTitle"]').length).toBe(0); + expect(component.find('[data-test-subj="uptimeTabs"]').length).toBe(0); + }); + it('shallow renders with the date picker', () => { - const component = renderWithRouter( - - - - ); - expect(component).toMatchSnapshot('page_header_with_date_picker'); + const component = renderWithRouter(); + expect(component.find('[data-test-subj="superDatePickerShowDatesButton"]').length).toBe(1); + }); + + it('shallow renders with certificate refresh button', () => { + const component = renderWithRouter(); + expect(component.find('[data-test-subj="certificatesRefreshButton"]').length).toBe(1); }); - it('shallow renders without the date picker', () => { - const component = renderWithRouter( - - - - ); - expect(component).toMatchSnapshot('page_header_no_date_picker'); + it('renders monitor title when showMonitorTitle', () => { + const component = renderWithRouter(); + expect(component.find('[data-test-subj="monitorTitle"]').length).toBe(1); + expect(component.find('h1').text()).toBe(monitorName); }); - it('shallow renders extra links', () => { - const component = renderWithRouter( - - - - ); - expect(component).toMatchSnapshot('page_header_with_extra_links'); + it('renders tabs when showTabs is true', () => { + const component = renderWithRouter(); + expect(component.find('[data-test-subj="uptimeTabs"]').length).toBe(1); }); }); diff --git a/x-pack/plugins/uptime/public/components/common/header/page_header.tsx b/x-pack/plugins/uptime/public/components/common/header/page_header.tsx index 923aee472faa6..2bb14d279f2d9 100644 --- a/x-pack/plugins/uptime/public/components/common/header/page_header.tsx +++ b/x-pack/plugins/uptime/public/components/common/header/page_header.tsx @@ -5,20 +5,21 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import styled from 'styled-components'; -import { useRouteMatch } from 'react-router-dom'; import { UptimeDatePicker } from '../uptime_date_picker'; import { SyntheticsCallout } from '../../overview/synthetics_callout'; import { PageTabs } from './page_tabs'; -import { - CERTIFICATES_ROUTE, - MONITOR_ROUTE, - SETTINGS_ROUTE, - STEP_DETAIL_ROUTE, -} from '../../../../common/constants'; import { CertRefreshBtn } from '../../certificates/cert_refresh_btn'; import { ToggleAlertFlyoutButton } from '../../overview/alerts/alerts_containers'; +import { MonitorPageTitle } from '../../monitor/monitor_title'; + +export interface Props { + showCertificateRefreshBtn?: boolean; + showDatePicker?: boolean; + showMonitorTitle?: boolean; + showTabs?: boolean; +} const StyledPicker = styled(EuiFlexItem)` &&& { @@ -36,40 +37,31 @@ const StyledPicker = styled(EuiFlexItem)` } `; -export const PageHeader = () => { - const isCertRoute = useRouteMatch(CERTIFICATES_ROUTE); - const isSettingsRoute = useRouteMatch(SETTINGS_ROUTE); - const isStepDetailRoute = useRouteMatch(STEP_DETAIL_ROUTE); - - const DatePickerComponent = () => - isCertRoute ? ( - - ) : ( - - - - ); - - const isMonRoute = useRouteMatch(MONITOR_ROUTE); - - if (isStepDetailRoute) { - return null; - } - +export const PageHeader = ({ + showCertificateRefreshBtn = false, + showDatePicker = false, + showMonitorTitle = false, + showTabs = false, +}: Props) => { return ( <> - + {showMonitorTitle && } + {showTabs && } - {!isSettingsRoute && } + {showCertificateRefreshBtn && } + {showDatePicker && ( + + + + )} - {isMonRoute && } - {!isMonRoute && } + ); }; diff --git a/x-pack/plugins/uptime/public/components/common/header/page_tabs.tsx b/x-pack/plugins/uptime/public/components/common/header/page_tabs.tsx index 68df15c52c65e..56da7d5f92803 100644 --- a/x-pack/plugins/uptime/public/components/common/header/page_tabs.tsx +++ b/x-pack/plugins/uptime/public/components/common/header/page_tabs.tsx @@ -73,7 +73,7 @@ export const PageTabs = () => { }; return ( - + {renderTabs()} ); diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx new file mode 100644 index 0000000000000..6ee25f8360933 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import moment from 'moment'; +import * as reactRouterDom from 'react-router-dom'; +import { Ping } from '../../../common/runtime_types'; +import { MonitorPageTitle } from './monitor_title'; +import { renderWithRouter } from '../../lib'; +import { mockReduxHooks } from '../../lib/helper/test_helpers'; + +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + + return { + ...originalModule, + useParams: jest.fn(), + }; +}); + +export function mockReactRouterDomHooks({ useParamsResponse }: { useParamsResponse: any }) { + jest.spyOn(reactRouterDom, 'useParams').mockReturnValue(useParamsResponse); +} + +describe('MonitorTitle component', () => { + const monitorName = 'sample monitor'; + const defaultMonitorId = 'always-down'; + const defaultMonitorIdEncoded = 'YWx3YXlzLWRvd24'; // resolves to always-down + const autoGeneratedMonitorIdEncoded = 'YXV0by1pY21wLTBYMjQ5NDhGNDY3QzZDNEYwMQ'; // resolves to auto-icmp-0X24948F467C6C4F01 + + const defaultMonitorStatus: Ping = { + docId: 'few213kl', + timestamp: moment(new Date()).subtract(15, 'm').toString(), + monitor: { + duration: { + us: 1234567, + }, + id: defaultMonitorId, + status: 'up', + type: 'http', + }, + url: { + full: 'https://www.elastic.co/', + }, + }; + + const monitorStatusWithName: Ping = { + ...defaultMonitorStatus, + monitor: { + ...defaultMonitorStatus.monitor, + name: monitorName, + }, + }; + + beforeEach(() => { + mockReactRouterDomHooks({ useParamsResponse: { monitorId: defaultMonitorIdEncoded } }); + mockReduxHooks(defaultMonitorStatus); + }); + + it('renders the monitor heading and EnableMonitorAlert toggle', () => { + mockReduxHooks(monitorStatusWithName); + const component = renderWithRouter(); + expect(component.find('h1').text()).toBe(monitorName); + expect(component.find('[data-test-subj="uptimeDisplayDefineConnector"]').length).toBe(1); + }); + + it('renders the user provided monitorId when the name is not present', () => { + mockReactRouterDomHooks({ useParamsResponse: { monitorId: defaultMonitorIdEncoded } }); + const component = renderWithRouter(); + expect(component.find('h1').text()).toBe(defaultMonitorId); + }); + + it('renders the url when the monitorId is auto generated and the monitor name is not present', () => { + mockReactRouterDomHooks({ useParamsResponse: { monitorId: autoGeneratedMonitorIdEncoded } }); + const component = renderWithRouter(); + expect(component.find('h1').text()).toBe(defaultMonitorStatus.url?.full); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx new file mode 100644 index 0000000000000..cecef14298267 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; +import React from 'react'; +import { useSelector } from 'react-redux'; +import { useMonitorId } from '../../hooks'; +import { monitorStatusSelector } from '../../state/selectors'; +import { EnableMonitorAlert } from '../overview/monitor_list/columns/enable_alert'; +import { Ping } from '../../../common/runtime_types/ping'; +import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; + +const isAutogeneratedId = (id: string) => { + const autoGeneratedId = /^auto-(icmp|http|tcp|browser)-0X[A-F0-9]{16}.*/; + return autoGeneratedId.test(id); +}; + +// For monitors with no explicit ID, we display the URL instead of the +// auto-generated ID because it is difficult to derive meaning from a +// generated id like `auto-http-0X8D6082B94BBE3B8A`. +// We may deprecate this behavior in the next major release, because +// the heartbeat config will require an explicit ID. +const getPageTitle = (monitorId: string, selectedMonitor: Ping | null) => { + if (isAutogeneratedId(monitorId)) { + return selectedMonitor?.url?.full || monitorId; + } + return monitorId; +}; + +export const MonitorPageTitle: React.FC = () => { + const monitorId = useMonitorId(); + + const selectedMonitor = useSelector(monitorStatusSelector); + + const nameOrId = selectedMonitor?.monitor?.name || getPageTitle(monitorId, selectedMonitor); + + useBreadcrumbs([{ text: nameOrId }]); + + return ( + + + +

{nameOrId}

+
+ +
+ + + +
+ ); +}; diff --git a/x-pack/plugins/uptime/public/lib/helper/test_helpers.ts b/x-pack/plugins/uptime/public/lib/helper/test_helpers.ts index faa1b968d2546..8102bdee9485b 100644 --- a/x-pack/plugins/uptime/public/lib/helper/test_helpers.ts +++ b/x-pack/plugins/uptime/public/lib/helper/test_helpers.ts @@ -9,6 +9,7 @@ import moment from 'moment'; import { Moment } from 'moment-timezone'; import * as redux from 'react-redux'; +import * as reactRouterDom from 'react-router-dom'; export function mockMoment() { // avoid timezone issues @@ -27,3 +28,7 @@ export function mockReduxHooks(response?: any) { jest.spyOn(redux, 'useSelector').mockReturnValue(response); } + +export function mockReactRouterDomHooks({ useParamsResponse }: { useParamsResponse: any }) { + jest.spyOn(reactRouterDom, 'useParams').mockReturnValue(useParamsResponse); +} diff --git a/x-pack/plugins/uptime/public/pages/monitor.tsx b/x-pack/plugins/uptime/public/pages/monitor.tsx index 559f118e89665..d68f791e3b77b 100644 --- a/x-pack/plugins/uptime/public/pages/monitor.tsx +++ b/x-pack/plugins/uptime/public/pages/monitor.tsx @@ -4,39 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { EuiSpacer } from '@elastic/eui'; import React, { useEffect } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { monitorStatusSelector } from '../state/selectors'; -import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; +import { useDispatch } from 'react-redux'; import { useTrackPageview } from '../../../observability/public'; import { useMonitorId } from '../hooks'; import { MonitorCharts } from '../components/monitor'; import { MonitorStatusDetails, PingList } from '../components/monitor'; import { getDynamicSettings } from '../state/actions/dynamic_settings'; -import { Ping } from '../../common/runtime_types/ping'; import { setSelectedMonitorId } from '../state/actions'; -import { EnableMonitorAlert } from '../components/overview/monitor_list/columns/enable_alert'; import { getMonitorAlertsAction } from '../state/alerts/alerts'; import { useInitApp } from '../hooks/use_init_app'; -const isAutogeneratedId = (id: string) => { - const autoGeneratedId = /^auto-(icmp|http|tcp)-OX[A-F0-9]{16}-[a-f0-9]{16}/; - return autoGeneratedId.test(id); -}; - -// For monitors with no explicit ID, we display the URL instead of the -// auto-generated ID because it is difficult to derive meaning from a -// generated id like `auto-http-0X8D6082B94BBE3B8A`. -// We may deprecate this behavior in the next major release, because -// the heartbeat config will require an explicit ID. -const getPageTitle = (monId: string, selectedMonitor: Ping | null) => { - if (isAutogeneratedId(monId)) { - return selectedMonitor?.url?.full || monId; - } - return monId; -}; - export const MonitorPage: React.FC = () => { const dispatch = useDispatch(); @@ -53,31 +32,11 @@ export const MonitorPage: React.FC = () => { dispatch(getMonitorAlertsAction.get()); }, [monitorId, dispatch]); - const selectedMonitor = useSelector(monitorStatusSelector); - useTrackPageview({ app: 'uptime', path: 'monitor' }); useTrackPageview({ app: 'uptime', path: 'monitor', delay: 15000 }); - const nameOrId = selectedMonitor?.monitor?.name || getPageTitle(monitorId, selectedMonitor); - useBreadcrumbs([{ text: nameOrId }]); - return ( <> - - - -

{nameOrId}

-
- -
- - - -
- diff --git a/x-pack/plugins/uptime/public/pages/settings.tsx b/x-pack/plugins/uptime/public/pages/settings.tsx index f19a462f41218..5231f590d2dbb 100644 --- a/x-pack/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/plugins/uptime/public/pages/settings.tsx @@ -145,7 +145,6 @@ export const SettingsPage: React.FC = () => { return ( <> - {cannotEditNotice} diff --git a/x-pack/plugins/uptime/public/routes.tsx b/x-pack/plugins/uptime/public/routes.tsx index 65526f9bca4fc..5eb689e72bc1b 100644 --- a/x-pack/plugins/uptime/public/routes.tsx +++ b/x-pack/plugins/uptime/public/routes.tsx @@ -5,8 +5,9 @@ */ import React, { FC, useEffect } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Route, RouteComponentProps, Switch } from 'react-router-dom'; import { OverviewPage } from './components/overview/overview_container'; +import { Props as PageHeaderProps, PageHeader } from './components/common/header/page_header'; import { CERTIFICATES_ROUTE, MONITOR_ROUTE, @@ -17,7 +18,6 @@ import { import { MonitorPage, StepDetailPage, NotFoundPage, SettingsPage } from './pages'; import { CertificatesPage } from './pages/certificates'; import { UptimePage, useUptimeTelemetry } from './hooks'; -import { PageHeader } from './components/common/header/page_header'; interface RouteProps { path: string; @@ -25,6 +25,7 @@ interface RouteProps { dataTestSubj: string; title: string; telemetryId: UptimePage; + headerProps?: PageHeaderProps; } const baseTitle = 'Uptime - Kibana'; @@ -36,6 +37,10 @@ const Routes: RouteProps[] = [ component: MonitorPage, dataTestSubj: 'uptimeMonitorPage', telemetryId: UptimePage.Monitor, + headerProps: { + showDatePicker: true, + showMonitorTitle: true, + }, }, { title: `Settings | ${baseTitle}`, @@ -43,6 +48,9 @@ const Routes: RouteProps[] = [ component: SettingsPage, dataTestSubj: 'uptimeSettingsPage', telemetryId: UptimePage.Settings, + headerProps: { + showTabs: true, + }, }, { title: `Certificates | ${baseTitle}`, @@ -50,6 +58,10 @@ const Routes: RouteProps[] = [ component: CertificatesPage, dataTestSubj: 'uptimeCertificatesPage', telemetryId: UptimePage.Certificates, + headerProps: { + showCertificateRefreshBtn: true, + showTabs: true, + }, }, { title: baseTitle, @@ -64,6 +76,10 @@ const Routes: RouteProps[] = [ component: OverviewPage, dataTestSubj: 'uptimeOverviewPage', telemetryId: UptimePage.Overview, + headerProps: { + showDatePicker: true, + showTabs: true, + }, }, ]; @@ -82,7 +98,18 @@ const RouteInit: React.FC> = export const PageRouter: FC = () => { return ( <> - + {/* Independent page header route that matches all paths and passes appropriate header props */} + {/* Prevents the header from being remounted on route changes */} + route.path)]} + exact={true} + render={({ match }: RouteComponentProps) => { + const routeProps: RouteProps | undefined = Routes.find( + (route: RouteProps) => route?.path === match?.path + ); + return routeProps?.headerProps && ; + }} + /> {Routes.map(({ title, path, component: RouteComponent, dataTestSubj, telemetryId }) => (