From a72f05d033fe37dccca68aa96d0bd31d1dcf68d0 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 3 Jun 2021 11:15:32 -0400 Subject: [PATCH] [Uptime] Move uptime to new solution nav (#100905) (#101271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Expose options to customize the route matching * Add more comments * move uptime to new solution nav * push * update test * add an extra breadcrumb Co-authored-by: Felix Stürmer Co-authored-by: Shahzad Co-authored-by: Felix Stürmer --- x-pack/plugins/uptime/kibana.json | 6 +- x-pack/plugins/uptime/public/apps/plugin.ts | 62 ++++++-- .../plugins/uptime/public/apps/uptime_app.tsx | 1 + .../certificates/certificate_title.tsx | 25 +++ .../header/action_menu_content.test.tsx | 2 +- .../common/header/page_header.test.tsx | 69 --------- .../components/common/header/page_header.tsx | 64 -------- .../components/monitor/monitor_title.test.tsx | 65 -------- .../components/monitor/monitor_title.tsx | 36 +++-- .../synthetics/step_detail/step_detail.tsx | 144 ------------------ .../step_detail/step_detail_container.tsx | 59 ++++--- .../synthetics/step_detail/step_page_nav.tsx | 71 +++++++++ .../step_detail/step_page_title.tsx | 69 +++++++++ .../use_monitor_breadcrumbs.test.tsx | 8 + .../public/hooks/use_breadcrumbs.test.tsx | 16 +- .../uptime/public/hooks/use_breadcrumbs.ts | 40 +++-- .../uptime/public/lib/helper/rtl_helpers.tsx | 8 +- .../uptime/public/pages/certificates.tsx | 25 +-- .../plugins/uptime/public/pages/overview.tsx | 3 +- .../plugins/uptime/public/pages/settings.tsx | 134 ++++++++-------- .../pages/synthetics/synthetics_checks.tsx | 30 ++-- x-pack/plugins/uptime/public/routes.tsx | 82 +++++----- .../services/uptime/certificates.ts | 3 +- .../functional/services/uptime/navigation.ts | 5 +- 24 files changed, 471 insertions(+), 556 deletions(-) create mode 100644 x-pack/plugins/uptime/public/components/certificates/certificate_title.tsx delete mode 100644 x-pack/plugins/uptime/public/components/common/header/page_header.test.tsx delete mode 100644 x-pack/plugins/uptime/public/components/common/header/page_header.tsx delete mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_page_nav.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_page_title.tsx diff --git a/x-pack/plugins/uptime/kibana.json b/x-pack/plugins/uptime/kibana.json index 0d2346f59b0a1..4d5ab531af7c4 100644 --- a/x-pack/plugins/uptime/kibana.json +++ b/x-pack/plugins/uptime/kibana.json @@ -8,7 +8,6 @@ "optionalPlugins": [ "data", "home", - "observability", "ml", "fleet" ], @@ -18,7 +17,8 @@ "features", "licensing", "triggersActionsUi", - "usageCollection" + "usageCollection", + "observability" ], "server": true, "ui": true, @@ -31,4 +31,4 @@ "data", "ml" ] -} \ No newline at end of file +} diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index 80a131676951e..e02cf44b0856e 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -12,6 +12,8 @@ import { PluginInitializerContext, AppMountParameters, } from 'kibana/public'; +import { of } from 'rxjs'; +import { i18n } from '@kbn/i18n'; import { DEFAULT_APP_CATEGORIES } from '../../../../../src/core/public'; import { FeatureCatalogueCategory, @@ -28,7 +30,11 @@ import { } from '../../../../../src/plugins/data/public'; import { alertTypeInitializers } from '../lib/alert_types'; import { FleetStart } from '../../../fleet/public'; -import { FetchDataParams, ObservabilityPublicSetup } from '../../../observability/public'; +import { + FetchDataParams, + ObservabilityPublicSetup, + ObservabilityPublicStart, +} from '../../../observability/public'; import { PLUGIN } from '../../common/constants/plugin'; import { IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public'; import { @@ -48,6 +54,7 @@ export interface ClientPluginsStart { data: DataPublicPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; fleet?: FleetStart; + observability: ObservabilityPublicStart; } export interface UptimePluginServices extends Partial { @@ -83,21 +90,46 @@ export class UptimePlugin return UptimeDataHelper(coreStart); }; - if (plugins.observability) { - plugins.observability.dashboard.register({ - appName: 'synthetics', - hasData: async () => { - const dataHelper = await getUptimeDataHelper(); - const status = await dataHelper.indexStatus(); - return { hasData: status.docCount > 0, indices: status.indices }; - }, - fetchData: async (params: FetchDataParams) => { - const dataHelper = await getUptimeDataHelper(); - return await dataHelper.overviewData(params); - }, - }); - } + plugins.observability.dashboard.register({ + appName: 'synthetics', + hasData: async () => { + const dataHelper = await getUptimeDataHelper(); + const status = await dataHelper.indexStatus(); + return { hasData: status.docCount > 0, indices: status.indices }; + }, + fetchData: async (params: FetchDataParams) => { + const dataHelper = await getUptimeDataHelper(); + return await dataHelper.overviewData(params); + }, + }); + plugins.observability.navigation.registerSections( + of([ + { + label: 'Uptime', + sortKey: 200, + entries: [ + { + label: i18n.translate('xpack.uptime.overview.heading', { + defaultMessage: 'Monitoring overview', + }), + app: 'uptime', + path: '/', + matchFullPath: true, + ignoreTrailingSlash: true, + }, + { + label: i18n.translate('xpack.uptime.certificatesPage.heading', { + defaultMessage: 'TLS Certificates', + }), + app: 'uptime', + path: '/certificates', + matchFullPath: true, + }, + ], + }, + ]) + ); core.application.register({ id: PLUGIN.ID, euiIconType: 'logoObservability', diff --git a/x-pack/plugins/uptime/public/apps/uptime_app.tsx b/x-pack/plugins/uptime/public/apps/uptime_app.tsx index 758d40a95a86a..4d99e877291b5 100644 --- a/x-pack/plugins/uptime/public/apps/uptime_app.tsx +++ b/x-pack/plugins/uptime/public/apps/uptime_app.tsx @@ -122,6 +122,7 @@ const Application = (props: UptimeAppProps) => { storage, data: startPlugins.data, triggersActionsUi: startPlugins.triggersActionsUi, + observability: startPlugins.observability, }} > diff --git a/x-pack/plugins/uptime/public/components/certificates/certificate_title.tsx b/x-pack/plugins/uptime/public/components/certificates/certificate_title.tsx new file mode 100644 index 0000000000000..5056a3d1c1957 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/certificates/certificate_title.tsx @@ -0,0 +1,25 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n/react'; +import { useSelector } from 'react-redux'; +import { certificatesSelector } from '../../state/certificates/certificates'; + +export const CertificateTitle = () => { + const { data: certificates } = useSelector(certificatesSelector); + + return ( + {certificates?.total ?? 0}, + }} + /> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.test.tsx b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.test.tsx index bc5eab6f92111..89d8f38b1e3b3 100644 --- a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.test.tsx @@ -49,7 +49,7 @@ describe('ActionMenuContent', () => { // this href value is mocked, so it doesn't correspond to the real link // that Kibana core services will provide - expect(addDataAnchor.getAttribute('href')).toBe('/app/uptime'); + expect(addDataAnchor.getAttribute('href')).toBe('/home#/tutorial/uptimeMonitors'); expect(getByText('Add data')); }); }); 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 deleted file mode 100644 index 6e04648a817f0..0000000000000 --- a/x-pack/plugins/uptime/public/components/common/header/page_header.test.tsx +++ /dev/null @@ -1,69 +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 moment from 'moment'; -import { PageHeader } from './page_header'; -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.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('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('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 deleted file mode 100644 index 28a133698ae8b..0000000000000 --- a/x-pack/plugins/uptime/public/components/common/header/page_header.tsx +++ /dev/null @@ -1,64 +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 { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import styled from 'styled-components'; -import { UptimeDatePicker } from '../uptime_date_picker'; -import { SyntheticsCallout } from '../../overview/synthetics_callout'; -import { PageTabs } from './page_tabs'; -import { CertRefreshBtn } from '../../certificates/cert_refresh_btn'; -import { MonitorPageTitle } from '../../monitor/monitor_title'; - -export interface Props { - showCertificateRefreshBtn?: boolean; - showDatePicker?: boolean; - showMonitorTitle?: boolean; - showTabs?: boolean; -} - -const StyledPicker = styled(EuiFlexItem)` - &&& { - @media only screen and (max-width: 1024px) and (min-width: 868px) { - .euiSuperDatePicker__flexWrapper { - width: 500px; - } - } - @media only screen and (max-width: 880px) { - flex-grow: 1; - .euiSuperDatePicker__flexWrapper { - width: calc(100% + 8px); - } - } - } -`; - -export const PageHeader = ({ - showCertificateRefreshBtn = false, - showDatePicker = false, - showMonitorTitle = false, - showTabs = false, -}: Props) => { - return ( - <> - - - - {showMonitorTitle && } - {showTabs && } - - {showCertificateRefreshBtn && } - {showDatePicker && ( - - - - )} - - - - ); -}; 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 index 5e77e68720c52..4fd6335c3d3ca 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx @@ -48,38 +48,6 @@ describe('MonitorTitle component', () => { }, }; - const defaultTCPMonitorStatus: Ping = { - docId: 'few213kl', - timestamp: moment(new Date()).subtract(15, 'm').toString(), - monitor: { - duration: { - us: 1234567, - }, - id: 'tcp', - status: 'up', - type: 'tcp', - }, - url: { - full: 'https://www.elastic.co/', - }, - }; - - const defaultICMPMonitorStatus: Ping = { - docId: 'few213kl', - timestamp: moment(new Date()).subtract(15, 'm').toString(), - monitor: { - duration: { - us: 1234567, - }, - id: 'icmp', - status: 'up', - type: 'icmp', - }, - url: { - full: 'https://www.elastic.co/', - }, - }; - const defaultBrowserMonitorStatus: Ping = { docId: 'few213kl', timestamp: moment(new Date()).subtract(15, 'm').toString(), @@ -145,37 +113,4 @@ describe('MonitorTitle component', () => { expect(betaLink.href).toBe('https://www.elastic.co/what-is/synthetic-monitoring'); expect(screen.getByText('Browser (BETA)')).toBeInTheDocument(); }); - - it('does not render beta disclaimer for http', () => { - render(, { - state: { monitorStatus: { status: defaultMonitorStatus, loading: false } }, - }); - expect(screen.getByText('HTTP ping')).toBeInTheDocument(); - expect(screen.queryByText(/BETA/)).not.toBeInTheDocument(); - expect( - screen.queryByRole('link', { name: 'See more External link (opens in a new tab or window)' }) - ).not.toBeInTheDocument(); - }); - - it('does not render beta disclaimer for tcp', () => { - render(, { - state: { monitorStatus: { status: defaultTCPMonitorStatus, loading: false } }, - }); - expect(screen.getByText('TCP ping')).toBeInTheDocument(); - expect(screen.queryByText(/BETA/)).not.toBeInTheDocument(); - expect( - screen.queryByRole('link', { name: 'See more External link (opens in a new tab or window)' }) - ).not.toBeInTheDocument(); - }); - - it('renders badge and does not render beta disclaimer for icmp', () => { - render(, { - state: { monitorStatus: { status: defaultICMPMonitorStatus, loading: false } }, - }); - expect(screen.getByText('ICMP ping')).toBeInTheDocument(); - expect(screen.queryByText(/BETA/)).not.toBeInTheDocument(); - expect( - screen.queryByRole('link', { name: 'See more External link (opens in a new tab or window)' }) - ).not.toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx index eebd3d8aeb14d..8cb1c49cbd974 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx @@ -5,7 +5,15 @@ * 2.0. */ -import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle, EuiLink } from '@elastic/eui'; +import { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, + EuiLink, + EuiText, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { useSelector } from 'react-redux'; @@ -95,26 +103,26 @@ export const MonitorPageTitle: React.FC = () => { - {type && ( + {isBrowser && type && ( {renderMonitorType(type)}{' '} - {isBrowser && ( - - )} + )} {isBrowser && ( - - - + + + + + )} diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail.tsx deleted file mode 100644 index befe53219a449..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail.tsx +++ /dev/null @@ -1,144 +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 { - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiTitle, - EuiButtonEmpty, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; -import moment from 'moment'; -import { WaterfallChartContainer } from './waterfall/waterfall_chart_container'; - -export const PREVIOUS_CHECK_BUTTON_TEXT = i18n.translate( - 'xpack.uptime.synthetics.stepDetail.previousCheckButtonText', - { - defaultMessage: 'Previous check', - } -); - -export const NEXT_CHECK_BUTTON_TEXT = i18n.translate( - 'xpack.uptime.synthetics.stepDetail.nextCheckButtonText', - { - defaultMessage: 'Next check', - } -); - -interface Props { - checkGroup: string; - stepName?: string; - stepIndex: number; - totalSteps: number; - hasPreviousStep: boolean; - hasNextStep: boolean; - handlePreviousStep: () => void; - handleNextStep: () => void; - handleNextRun: () => void; - handlePreviousRun: () => void; - previousCheckGroup?: string; - nextCheckGroup?: string; - checkTimestamp?: string; - dateFormat: string; -} - -export const StepDetail: React.FC = ({ - dateFormat, - stepName, - checkGroup, - stepIndex, - totalSteps, - hasPreviousStep, - hasNextStep, - handlePreviousStep, - handleNextStep, - handlePreviousRun, - handleNextRun, - previousCheckGroup, - nextCheckGroup, - checkTimestamp, -}) => { - return ( - <> - - - - - -

{stepName}

-
-
- - - - - - - - - - - - - - - -
-
- - - - - {PREVIOUS_CHECK_BUTTON_TEXT} - - - - {moment(checkTimestamp).format(dateFormat)} - - - - {NEXT_CHECK_BUTTON_TEXT} - - - - -
- - - - ); -}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx index ef0d001ac905e..df8f5dff59dc2 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx @@ -13,8 +13,12 @@ import { useHistory } from 'react-router-dom'; import { getJourneySteps } from '../../../../state/actions/journey'; import { journeySelector } from '../../../../state/selectors'; import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/public'; -import { StepDetail } from './step_detail'; import { useMonitorBreadcrumb } from './use_monitor_breadcrumb'; +import { ClientPluginsStart } from '../../../../apps/plugin'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { StepPageTitle } from './step_page_title'; +import { StepPageNavigation } from './step_page_nav'; +import { WaterfallChartContainer } from './waterfall/waterfall_chart_container'; export const NO_STEP_DATA = i18n.translate('xpack.uptime.synthetics.stepDetail.noData', { defaultMessage: 'No data could be found for this step', @@ -66,8 +70,40 @@ export const StepDetailContainer: React.FC = ({ checkGroup, stepIndex }) history.push(`/journey/${journey?.details?.previous?.checkGroup}/step/1`); }, [history, journey?.details?.previous?.checkGroup]); + const { + services: { observability }, + } = useKibana(); + const PageTemplateComponent = observability.navigation.PageTemplate; + return ( - <> + + ) : null, + rightSideItems: journey + ? [ + , + ] + : [], + }} + > {(!journey || journey.loading) && ( @@ -86,24 +122,9 @@ export const StepDetailContainer: React.FC = ({ checkGroup, stepIndex }) )} {journey && activeStep && !journey.loading && ( - + )} - + ); }; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_page_nav.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_page_nav.tsx new file mode 100644 index 0000000000000..81c72b74c18e8 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_page_nav.tsx @@ -0,0 +1,71 @@ +/* + * 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 { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import moment from 'moment'; +import { i18n } from '@kbn/i18n'; + +export const PREVIOUS_CHECK_BUTTON_TEXT = i18n.translate( + 'xpack.uptime.synthetics.stepDetail.previousCheckButtonText', + { + defaultMessage: 'Previous check', + } +); + +export const NEXT_CHECK_BUTTON_TEXT = i18n.translate( + 'xpack.uptime.synthetics.stepDetail.nextCheckButtonText', + { + defaultMessage: 'Next check', + } +); + +interface Props { + previousCheckGroup?: string; + dateFormat: string; + checkTimestamp?: string; + nextCheckGroup?: string; + handlePreviousRun: () => void; + handleNextRun: () => void; +} +export const StepPageNavigation = ({ + previousCheckGroup, + dateFormat, + handleNextRun, + handlePreviousRun, + checkTimestamp, + nextCheckGroup, +}: Props) => { + return ( + + + + {PREVIOUS_CHECK_BUTTON_TEXT} + + + + {moment(checkTimestamp).format(dateFormat)} + + + + {NEXT_CHECK_BUTTON_TEXT} + + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_page_title.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_page_title.tsx new file mode 100644 index 0000000000000..083f2f1533e2e --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_page_title.tsx @@ -0,0 +1,69 @@ +/* + * 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 { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +interface Props { + stepName?: string; + stepIndex: number; + totalSteps: number; + hasPreviousStep: boolean; + hasNextStep: boolean; + handlePreviousStep: () => void; + handleNextStep: () => void; +} +export const StepPageTitle = ({ + stepName, + stepIndex, + totalSteps, + handleNextStep, + handlePreviousStep, + hasNextStep, + hasPreviousStep, +}: Props) => { + return ( + + + +

{stepName}

+
+
+ + + + + + + + + + + + + + + +
+ ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/use_monitor_breadcrumbs.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/use_monitor_breadcrumbs.test.tsx index 4aed073424788..4521d9f82f92e 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/use_monitor_breadcrumbs.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/use_monitor_breadcrumbs.test.tsx @@ -63,6 +63,10 @@ describe('useMonitorBreadcrumbs', () => { expect(getBreadcrumbs()).toMatchInlineSnapshot(` Array [ + Object { + "href": "", + "text": "Observability", + }, Object { "href": "/app/uptime", "onClick": [Function], @@ -129,6 +133,10 @@ describe('useMonitorBreadcrumbs', () => { expect(getBreadcrumbs()).toMatchInlineSnapshot(` Array [ + Object { + "href": "", + "text": "Observability", + }, Object { "href": "/app/uptime", "onClick": [Function], diff --git a/x-pack/plugins/uptime/public/hooks/use_breadcrumbs.test.tsx b/x-pack/plugins/uptime/public/hooks/use_breadcrumbs.test.tsx index 6fc98fbaf1f5b..9d7318a45f76e 100644 --- a/x-pack/plugins/uptime/public/hooks/use_breadcrumbs.test.tsx +++ b/x-pack/plugins/uptime/public/hooks/use_breadcrumbs.test.tsx @@ -19,14 +19,8 @@ describe('useBreadcrumbs', () => { const [getBreadcrumbs, core] = mockCore(); const expectedCrumbs: ChromeBreadcrumb[] = [ - { - text: 'Crumb: ', - href: 'http://href.example.net', - }, - { - text: 'Crumb II: Son of Crumb', - href: 'http://href2.example.net', - }, + { text: 'Crumb: ', href: 'http://href.example.net' }, + { text: 'Crumb II: Son of Crumb', href: 'http://href2.example.net' }, ]; const Component = () => { @@ -46,7 +40,9 @@ describe('useBreadcrumbs', () => { const urlParams: UptimeUrlParams = getSupportedUrlParams({}); expect(JSON.stringify(getBreadcrumbs())).toEqual( - JSON.stringify([makeBaseBreadcrumb('/app/uptime', urlParams)].concat(expectedCrumbs)) + JSON.stringify( + makeBaseBreadcrumb('/app/uptime', '/app/observability', urlParams).concat(expectedCrumbs) + ) ); }); }); @@ -58,7 +54,7 @@ const mockCore: () => [() => ChromeBreadcrumb[], any] = () => { }; const core = { application: { - getUrlForApp: () => '/app/uptime', + getUrlForApp: (app: string) => (app === 'uptime' ? '/app/uptime' : '/app/observability'), navigateToUrl: jest.fn(), }, chrome: { diff --git a/x-pack/plugins/uptime/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/uptime/public/hooks/use_breadcrumbs.ts index f2ec25b50332b..5ea81e579ff92 100644 --- a/x-pack/plugins/uptime/public/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/uptime/public/hooks/use_breadcrumbs.ts @@ -36,34 +36,52 @@ function handleBreadcrumbClick( })); } -export const makeBaseBreadcrumb = (href: string, params?: UptimeUrlParams): EuiBreadcrumb => { +export const makeBaseBreadcrumb = ( + uptimePath: string, + observabilityPath: string, + params?: UptimeUrlParams +): [EuiBreadcrumb, EuiBreadcrumb] => { if (params) { const crumbParams: Partial = { ...params }; delete crumbParams.statusFilter; const query = stringifyUrlParams(crumbParams, true); - href += query === EMPTY_QUERY ? '' : query; + uptimePath += query === EMPTY_QUERY ? '' : query; } - return { - text: i18n.translate('xpack.uptime.breadcrumbs.overviewBreadcrumbText', { - defaultMessage: 'Uptime', - }), - href, - }; + + return [ + { + text: i18n.translate('xpack.uptime.breadcrumbs.observabilityText', { + defaultMessage: 'Observability', + }), + href: observabilityPath, + }, + { + text: i18n.translate('xpack.uptime.breadcrumbs.overviewBreadcrumbText', { + defaultMessage: 'Uptime', + }), + href: uptimePath, + }, + ]; }; export const useBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => { const params = useUrlParams()[0](); const kibana = useKibana(); const setBreadcrumbs = kibana.services.chrome?.setBreadcrumbs; - const appPath = kibana.services.application?.getUrlForApp(PLUGIN.ID) ?? ''; + const uptimePath = kibana.services.application?.getUrlForApp(PLUGIN.ID) ?? ''; + const observabilityPath = + kibana.services.application?.getUrlForApp('observability-overview') ?? ''; const navigate = kibana.services.application?.navigateToUrl; useEffect(() => { if (setBreadcrumbs) { setBreadcrumbs( - handleBreadcrumbClick([makeBaseBreadcrumb(appPath, params)].concat(extraCrumbs), navigate) + handleBreadcrumbClick( + makeBaseBreadcrumb(uptimePath, observabilityPath, params).concat(extraCrumbs), + navigate + ) ); } - }, [appPath, extraCrumbs, navigate, params, setBreadcrumbs]); + }, [uptimePath, observabilityPath, extraCrumbs, navigate, params, setBreadcrumbs]); }; diff --git a/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx b/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx index a84209a23449a..0c2e31589bb10 100644 --- a/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx +++ b/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx @@ -79,6 +79,12 @@ const createMockStore = () => { }; }; +const mockAppUrls: Record = { + uptime: '/app/uptime', + observability: '/app/observability', + '/home#/tutorial/uptimeMonitors': '/home#/tutorial/uptimeMonitors', +}; + /* default mock core */ const defaultCore = coreMock.createStart(); const mockCore: () => Partial = () => { @@ -86,7 +92,7 @@ const mockCore: () => Partial = () => { ...defaultCore, application: { ...defaultCore.application, - getUrlForApp: () => '/app/uptime', + getUrlForApp: (app: string) => mockAppUrls[app], navigateToUrl: jest.fn(), capabilities: { ...defaultCore.application.capabilities, diff --git a/x-pack/plugins/uptime/public/pages/certificates.tsx b/x-pack/plugins/uptime/public/pages/certificates.tsx index 7c21493dbde06..4b8617d594547 100644 --- a/x-pack/plugins/uptime/public/pages/certificates.tsx +++ b/x-pack/plugins/uptime/public/pages/certificates.tsx @@ -5,15 +5,14 @@ * 2.0. */ -import { useDispatch, useSelector } from 'react-redux'; -import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { useDispatch } from 'react-redux'; +import { EuiSpacer } from '@elastic/eui'; import React, { useContext, useEffect, useState } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; import { useTrackPageview } from '../../../observability/public'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { getDynamicSettings } from '../state/actions/dynamic_settings'; import { UptimeRefreshContext } from '../contexts'; -import { certificatesSelector, getCertificatesAction } from '../state/certificates/certificates'; +import { getCertificatesAction } from '../state/certificates/certificates'; import { CertificateList, CertificateSearch, CertSort } from '../components/certificates'; const DEFAULT_PAGE_SIZE = 10; @@ -58,22 +57,8 @@ export const CertificatesPage: React.FC = () => { ); }, [dispatch, page, search, sort.direction, sort.field, lastRefresh]); - const { data: certificates } = useSelector(certificatesSelector); - return ( - - -

- {certificates?.total ?? 0}, - }} - /> -

-
- + <> @@ -86,6 +71,6 @@ export const CertificatesPage: React.FC = () => { }} sort={sort} /> -
+ ); }; diff --git a/x-pack/plugins/uptime/public/pages/overview.tsx b/x-pack/plugins/uptime/public/pages/overview.tsx index 846698bc390db..626e797bd9fd1 100644 --- a/x-pack/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/plugins/uptime/public/pages/overview.tsx @@ -15,6 +15,7 @@ import { MonitorList } from '../components/overview/monitor_list/monitor_list_co import { EmptyState, FilterGroup } from '../components/overview'; import { StatusPanel } from '../components/overview/status_panel'; import { QueryBar } from '../components/overview/query_bar/query_bar'; +import { MONITORING_OVERVIEW_LABEL } from '../routes'; const EuiFlexItemStyled = styled(EuiFlexItem)` && { @@ -32,7 +33,7 @@ export const OverviewPageComponent = () => { useTrackPageview({ app: 'uptime', path: 'overview' }); useTrackPageview({ app: 'uptime', path: 'overview', delay: 15000 }); - useBreadcrumbs([]); // No extra breadcrumbs on overview + useBreadcrumbs([{ text: MONITORING_OVERVIEW_LABEL }]); // No extra breadcrumbs on overview return ( diff --git a/x-pack/plugins/uptime/public/pages/settings.tsx b/x-pack/plugins/uptime/public/pages/settings.tsx index 394285f7c3dcf..b0825709aa381 100644 --- a/x-pack/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/plugins/uptime/public/pages/settings.tsx @@ -148,73 +148,71 @@ export const SettingsPage: React.FC = () => { ); return ( - <> - - - {cannotEditNotice} - - - -
- - - - - - - - - { - resetForm(); - }} - > - - - - - - - - - - -
-
-
-
- + + + {cannotEditNotice} + + + +
+ + + + + + + + + { + resetForm(); + }} + > + + + + + + + + + + +
+
+
+
); }; diff --git a/x-pack/plugins/uptime/public/pages/synthetics/synthetics_checks.tsx b/x-pack/plugins/uptime/public/pages/synthetics/synthetics_checks.tsx index edfd7ae24f91b..fe41e72fa4c48 100644 --- a/x-pack/plugins/uptime/public/pages/synthetics/synthetics_checks.tsx +++ b/x-pack/plugins/uptime/public/pages/synthetics/synthetics_checks.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { useTrackPageview } from '../../../../observability/public'; import { useInitApp } from '../../hooks/use_init_app'; import { StepsList } from '../../components/synthetics/check_steps/steps_list'; @@ -14,6 +14,7 @@ import { useCheckSteps } from '../../components/synthetics/check_steps/use_check import { ChecksNavigation } from './checks_navigation'; import { useMonitorBreadcrumb } from '../../components/monitor/synthetics/step_detail/use_monitor_breadcrumb'; import { EmptyJourney } from '../../components/synthetics/empty_journey'; +import { ClientPluginsStart } from '../../apps/plugin'; export const SyntheticsCheckSteps: React.FC = () => { useInitApp(); @@ -24,21 +25,22 @@ export const SyntheticsCheckSteps: React.FC = () => { useMonitorBreadcrumb({ details, activeStep: details?.journey }); + const { + services: { observability }, + } = useKibana(); + const PageTemplateComponent = observability.navigation.PageTemplate; + return ( - <> - - - -

{details?.journey?.monitor.name || details?.journey?.monitor.id}

-
-
- - {details && } - -
- + : null, + ], + }} + > {(!steps || steps.length === 0) && !loading && } - + ); }; diff --git a/x-pack/plugins/uptime/public/routes.tsx b/x-pack/plugins/uptime/public/routes.tsx index 1c025edd0a73d..192b5552fea40 100644 --- a/x-pack/plugins/uptime/public/routes.tsx +++ b/x-pack/plugins/uptime/public/routes.tsx @@ -6,8 +6,9 @@ */ import React, { FC, useEffect } from 'react'; -import { Route, RouteComponentProps, Switch } from 'react-router-dom'; -import { Props as PageHeaderProps, PageHeader } from './components/common/header/page_header'; +import { Route, Switch } from 'react-router-dom'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { CERTIFICATES_ROUTE, MONITOR_ROUTE, @@ -21,6 +22,13 @@ import { CertificatesPage } from './pages/certificates'; import { UptimePage, useUptimeTelemetry } from './hooks'; import { OverviewPageComponent } from './pages/overview'; import { SyntheticsCheckSteps } from './pages/synthetics/synthetics_checks'; +import { ClientPluginsStart } from './apps/plugin'; +import { MonitorPageTitle } from './components/monitor/monitor_title'; +import { UptimeDatePicker } from './components/common/uptime_date_picker'; +import { useKibana } from '../../../../src/plugins/kibana_react/public'; +import { CertRefreshBtn } from './components/certificates/cert_refresh_btn'; +import { CertificateTitle } from './components/certificates/certificate_title'; +import { SyntheticsCallout } from './components/overview/synthetics_callout'; interface RouteProps { path: string; @@ -28,11 +36,15 @@ interface RouteProps { dataTestSubj: string; title: string; telemetryId: UptimePage; - headerProps?: PageHeaderProps; + pageHeader?: { pageTitle: string | JSX.Element; rightSideItems?: JSX.Element[] }; } const baseTitle = 'Uptime - Kibana'; +export const MONITORING_OVERVIEW_LABEL = i18n.translate('xpack.uptime.overview.heading', { + defaultMessage: 'Monitoring overview', +}); + const Routes: RouteProps[] = [ { title: `Monitor | ${baseTitle}`, @@ -40,9 +52,9 @@ const Routes: RouteProps[] = [ component: MonitorPage, dataTestSubj: 'uptimeMonitorPage', telemetryId: UptimePage.Monitor, - headerProps: { - showDatePicker: true, - showMonitorTitle: true, + pageHeader: { + pageTitle: , + rightSideItems: [], }, }, { @@ -51,8 +63,10 @@ const Routes: RouteProps[] = [ component: SettingsPage, dataTestSubj: 'uptimeSettingsPage', telemetryId: UptimePage.Settings, - headerProps: { - showTabs: true, + pageHeader: { + pageTitle: ( + + ), }, }, { @@ -61,9 +75,9 @@ const Routes: RouteProps[] = [ component: CertificatesPage, dataTestSubj: 'uptimeCertificatesPage', telemetryId: UptimePage.Certificates, - headerProps: { - showCertificateRefreshBtn: true, - showTabs: true, + pageHeader: { + pageTitle: , + rightSideItems: [], }, }, { @@ -86,9 +100,9 @@ const Routes: RouteProps[] = [ component: OverviewPageComponent, dataTestSubj: 'uptimeOverviewPage', telemetryId: UptimePage.Overview, - headerProps: { - showDatePicker: true, - showTabs: true, + pageHeader: { + pageTitle: MONITORING_OVERVIEW_LABEL, + rightSideItems: [], }, }, ]; @@ -106,31 +120,31 @@ const RouteInit: React.FC> = }; export const PageRouter: FC = () => { + const { + services: { observability }, + } = useKibana(); + const PageTemplateComponent = observability.navigation.PageTemplate; + 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 }) => ( + + {Routes.map( + ({ title, path, component: RouteComponent, dataTestSubj, telemetryId, pageHeader }) => (
+ - + {pageHeader ? ( + + + + ) : ( + + )}
- ))} - -
- + ) + )} + +
); }; diff --git a/x-pack/test/functional/services/uptime/certificates.ts b/x-pack/test/functional/services/uptime/certificates.ts index 498e18de8e281..3a560acee52d8 100644 --- a/x-pack/test/functional/services/uptime/certificates.ts +++ b/x-pack/test/functional/services/uptime/certificates.ts @@ -11,6 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export function UptimeCertProvider({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); + const find = getService('find'); const PageObjects = getPageObjects(['common', 'timePicker', 'header']); @@ -27,7 +28,7 @@ export function UptimeCertProvider({ getService, getPageObjects }: FtrProviderCo return { async hasViewCertButton() { return retry.tryForTime(15000, async () => { - await testSubjects.existOrFail('uptimeCertificatesLink'); + await find.existsByCssSelector('[href="/app/uptime/certificates"]'); }); }, async certificateExists(cert: { certId: string; monitorId: string }) { diff --git a/x-pack/test/functional/services/uptime/navigation.ts b/x-pack/test/functional/services/uptime/navigation.ts index b76d68e1eb454..51806d1006ab4 100644 --- a/x-pack/test/functional/services/uptime/navigation.ts +++ b/x-pack/test/functional/services/uptime/navigation.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const testSubjects = getService('testSubjects'); + const find = getService('find'); const PageObjects = getPageObjects(['common', 'timePicker', 'header']); const goToUptimeRoot = async () => { @@ -70,8 +71,8 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv goToCertificates: async () => { if (!(await testSubjects.exists('uptimeCertificatesPage', { timeout: 0 }))) { return retry.try(async () => { - if (await testSubjects.exists('uptimeCertificatesLink', { timeout: 0 })) { - await testSubjects.click('uptimeCertificatesLink', 10000); + if (await find.existsByCssSelector('[href="/app/uptime/certificates"]', 0)) { + await find.clickByCssSelector('[href="/app/uptime/certificates"]'); } await testSubjects.existOrFail('uptimeCertificatesPage'); });