Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Uptime] Fix/details page tabs #86296

Merged
merged 32 commits into from
Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ccf1107
Remove tabs from details page
shahzad31 Dec 15, 2020
1b9bce4
Merge branch 'master' into details-page-tabs
shahzad31 Dec 15, 2020
12b3303
update
shahzad31 Dec 15, 2020
b8977ca
fix monitord id
shahzad31 Dec 15, 2020
c6ea0a6
Merge branch 'master' into details-page-tabs
shahzad31 Dec 15, 2020
914f9b3
var name
shahzad31 Dec 15, 2020
0b9a3b2
add Uptime PageHeader tests to test for the presences of tabs or header
dominiqueclarke Dec 17, 2020
57439d6
add Uptime MonitorPageTitle test
dominiqueclarke Dec 17, 2020
b157dae
Uptime adjust auto generated monitor id regex
dominiqueclarke Dec 17, 2020
1792f21
Uptime add tests for MonitorPageTitle to test behavior for missing mo…
dominiqueclarke Dec 17, 2020
5df147c
Merge branch 'master' into fix/details-page-tabs
kibanamachine Dec 17, 2020
b1df186
remove history from MonitorPageTitle test
dominiqueclarke Dec 17, 2020
93a3441
Merge branch 'master' into fix/details-page-tabs
kibanamachine Dec 22, 2020
fd703a6
adjust uptime tabs tests
dominiqueclarke Dec 23, 2020
34cbb18
adjust MonitorPageTitle tests to mock useSelector
dominiqueclarke Dec 29, 2020
2cb107c
Merge branch 'master' into fix/details-page-tabs
kibanamachine Dec 29, 2020
c96b6e3
adjust uptime PageHeader tests
dominiqueclarke Dec 29, 2020
6d4b1cd
Merge branch 'fix/details-page-tabs' of https://github.com/dominiquec…
dominiqueclarke Dec 29, 2020
d7dcfc0
adjust import order in page_header.test
dominiqueclarke Dec 29, 2020
d0912ab
add props to Uptime PageHeader to determine render, rather than route…
dominiqueclarke Dec 30, 2020
0b21318
Merge branch 'master' into fix/details-page-tabs
kibanamachine Jan 4, 2021
6d63f3c
alphabetize props in Uptime PageHeader
dominiqueclarke Jan 4, 2021
ca811ab
Merge branch 'fix/details-page-tabs' of https://github.com/dominiquec…
dominiqueclarke Jan 4, 2021
9bac833
remove header from individual pages
dominiqueclarke Jan 5, 2021
104320f
add indepdent page header route that matches all paths
dominiqueclarke Jan 5, 2021
7e6de88
Merge branch 'master' into fix/details-page-tabs
kibanamachine Jan 5, 2021
d884b92
adjust monitor tests to use mockReduxHooks helper, and add mockReactR…
dominiqueclarke Jan 7, 2021
0791a25
Merge branch 'fix/details-page-tabs' of https://github.com/dominiquec…
dominiqueclarke Jan 7, 2021
cbca6e9
pull upstream master and resolve conflicts
dominiqueclarke Jan 7, 2021
8aae9e0
update tests
dominiqueclarke Jan 7, 2021
f294605
adjust header spacing
dominiqueclarke Jan 7, 2021
bae3e91
Merge branch 'master' into fix/details-page-tabs
kibanamachine Jan 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,32 @@

import React from 'react';
import { PageHeader } from '../page_header';
import { createMemoryHistory } from 'history';
import { renderWithRouter, MountWithReduxProvider } from '../../../../lib';
import { AppState, store } from '../../../../state';

describe('PageHeader', () => {
const monitorName = 'sample monitor';
const initialState = store.getState();
const state: AppState = {
...initialState,
monitorStatus: {
loading: false,
status: {
docId: '',
timestamp: '',
monitor: {
duration: { us: 0 },
id: '',
status: '',
type: '',
...initialState?.monitorStatus?.status,
name: monitorName,
},
},
},
};

it('shallow renders with the date picker', () => {
const component = renderWithRouter(
<MountWithReduxProvider>
Expand All @@ -35,4 +58,46 @@ describe('PageHeader', () => {
);
expect(component).toMatchSnapshot('page_header_with_extra_links');
});

it('renders null when on a step detail page', () => {
const history = createMemoryHistory();
// navigate to step page
history.push('/journey/1/step/1');
const component = renderWithRouter(
<MountWithReduxProvider store={state}>
<PageHeader />
</MountWithReduxProvider>,
history
);
expect(component.html()).toBe(null);
});

it('renders monitor header without tabs when on a monitor page', () => {
const history = createMemoryHistory();
// navigate to monitor page
history.push('/monitor/1');
const component = renderWithRouter(
<MountWithReduxProvider store={state}>
<PageHeader />
</MountWithReduxProvider>,
history
);
expect(component.find('h1').text()).toBe(monitorName);
expect(component.find('[data-test-subj="uptimeSettingsToOverviewLink"]').length).toBe(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way we can just add a data-test-subj to the whole tab group instead of looking for specific links? Seems like the wrong approach, and fragile if the links themselves change.

expect(component.find('[data-test-subj="uptimeCertificatesLink"]').length).toBe(0);
expect(component.find('[data-test-subj="settings-page-link"]').length).toBe(0);
});

it('renders tabs when not on a monitor or step detail page', () => {
const history = createMemoryHistory();
const component = renderWithRouter(
<MountWithReduxProvider store={state}>
<PageHeader />
</MountWithReduxProvider>,
history
);
expect(component.find('[data-test-subj="uptimeSettingsToOverviewLink"]').length).toBe(1);
expect(component.find('[data-test-subj="uptimeCertificatesLink"]').length).toBe(1);
expect(component.find('[data-test-subj="settings-page-link"]').length).toBe(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

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';
Expand All @@ -19,6 +19,7 @@ import {
} from '../../../../common/constants';
import { CertRefreshBtn } from '../../certificates/cert_refresh_btn';
import { ToggleAlertFlyoutButton } from '../../overview/alerts/alerts_containers';
import { MonitorPageTitle } from '../../monitor/monitor_title';

const StyledPicker = styled(EuiFlexItem)`
&&& {
Expand All @@ -44,28 +45,29 @@ export const PageHeader = () => {
const DatePickerComponent = () =>
isCertRoute ? (
<CertRefreshBtn />
) : isStepDetailRoute ? null : (
) : (
<StyledPicker grow={false} style={{ flexBasis: 485 }}>
<UptimeDatePicker />
</StyledPicker>
);

const isMonRoute = useRouteMatch(MONITOR_ROUTE);
const isMonitorRoute = useRouteMatch(MONITOR_ROUTE);

if (isStepDetailRoute) {
return null;
}

return (
<>
<SyntheticsCallout />
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" wrap responsive={false}>
<EuiFlexItem>
<PageTabs />
</EuiFlexItem>
<EuiFlexItem>{isMonitorRoute ? <MonitorPageTitle /> : <PageTabs />}</EuiFlexItem>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit weird to me that the header should care about what page it's on. It would make more sense, IMHO, for the page to tell the header what should be in the header, that would, IMHO be less fragile. Each page could just say explicitly "I want this type of header".

Thinking ahead here, when we add a new page we want whoever works on it to make an explicit choice of header.

<EuiFlexItem grow={false}>
<ToggleAlertFlyoutButton />
</EuiFlexItem>
{!isSettingsRoute && <DatePickerComponent />}
</EuiFlexGroup>
{isMonRoute && <EuiHorizontalRule margin="m" />}
{!isMonRoute && <EuiSpacer size="m" />}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i believe this includeSpacer prop isn't needed, can we move this perhaps to top of those pages? where it's needed?

{!isMonitorRoute && <EuiSpacer size="m" />}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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 * as reactRouterDom from 'react-router-dom';
import { MonitorPageTitle } from '../monitor_title';
import { renderWithRouter, MountWithReduxProvider } from '../../../lib';
import { AppState, store } from '../../../state';

jest.mock('react-router-dom', () => ({
__esModule: true,
// @ts-ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for this being ignored? Any type script ignore statements should come with a comment explaining the why here. As a reviewer (or someone coming along later) it answers a lot of questions.

...jest.requireActual('react-router-dom'),
}));

describe('MonitorTitle component', () => {
const monitorName = 'sample monitor';
const monitorId = 'always-down';
const initialState = store.getState();
const url = {
full: 'https://www.elastic.co/',
};
const monitorStatus = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like the complexity of these mocks at this point justifies a bit of refactoring here.

We do things like this elsewhere, but it's something we should avoid when we can. The reason is, if we add any additional mandatory fields to this type, this is one more file we must update. Also, it's hard to read etc.

I am wondering if we could make a new selector in redux that just pulls the fields we need, so we would only need to mock that minimal set of fields. In general, when passing a complex mocked object, that can point to a need to reduce the surface area of the function (or component) being tested. It may accept a more complex object than it needs to. I'm also open to other approaches that could simplify the tests here.

loading: false,
status: {
docId: '',
timestamp: '',
monitor: {
duration: { us: 0 },
id: monitorId,
status: '',
type: '',
...initialState?.monitorStatus?.status,
name: monitorName,
},
url,
},
};

const stateWithMonitorName: AppState = {
...initialState,
monitorStatus,
};

const stateWithoutMonitorName: AppState = {
...initialState,
monitorStatus: {
...monitorStatus,
status: {
...monitorStatus.status,
monitor: {
...monitorStatus.status.monitor,
name: undefined,
},
},
},
};

it('renders the monitor heading and EnableMonitorAlert toggle', () => {
const component = renderWithRouter(
<MountWithReduxProvider store={stateWithMonitorName}>
<MonitorPageTitle />
</MountWithReduxProvider>
);
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', () => {
jest.spyOn(reactRouterDom, 'useParams').mockImplementation(() => ({
monitorId: 'YWx3YXlzLWRvd24', // resolves to always-down
}));
const component = renderWithRouter(
<MountWithReduxProvider store={stateWithoutMonitorName}>
<MonitorPageTitle />
</MountWithReduxProvider>
);
expect(component.find('h1').text()).toBe(monitorId);
});

it('renders the url when the monitorId is auto generated and the monitor name is not present', () => {
jest.spyOn(reactRouterDom, 'useParams').mockImplementation(() => ({
monitorId: 'YXV0by1pY21wLTBYMjQ5NDhGNDY3QzZDNEYwMQ', // resolves to auto-icmp-0X24948F467C6C4F01
}));
const component = renderWithRouter(
<MountWithReduxProvider store={stateWithoutMonitorName}>
<MonitorPageTitle />
</MountWithReduxProvider>
);
expect(component.find('h1').text()).toBe(url.full);
});
});
58 changes: 58 additions & 0 deletions x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx
Original file line number Diff line number Diff line change
@@ -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}.*/;
shahzad31 marked this conversation as resolved.
Show resolved Hide resolved
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 (
<EuiFlexGroup wrap={false}>
<EuiFlexItem grow={false}>
<EuiTitle>
<h1 className="eui-textNoWrap">{nameOrId}</h1>
</EuiTitle>
<EuiSpacer size="xs" />
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ justifyContent: 'center' }}>
<EnableMonitorAlert
monitorId={monitorId}
monitorName={selectedMonitor?.monitor?.name || selectedMonitor?.url?.full}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
};
44 changes: 2 additions & 42 deletions x-pack/plugins/uptime/public/pages/monitor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -53,30 +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 (
<>
<EuiFlexGroup wrap={false}>
<EuiFlexItem grow={false}>
<EuiTitle>
<h1 className="eui-textNoWrap">{nameOrId}</h1>
</EuiTitle>
<EuiSpacer size="xs" />
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ justifyContent: 'center' }}>
<EnableMonitorAlert
monitorId={monitorId}
monitorName={selectedMonitor?.monitor?.name || selectedMonitor?.url?.full}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<MonitorStatusDetails monitorId={monitorId} />
<EuiSpacer size="s" />
Expand Down
26 changes: 12 additions & 14 deletions x-pack/plugins/uptime/public/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,19 +81,17 @@ const RouteInit: React.FC<Pick<RouteProps, 'path' | 'title' | 'telemetryId'>> =

export const PageRouter: FC = () => {
return (
<>
<PageHeader />
<Switch>
{Routes.map(({ title, path, component: RouteComponent, dataTestSubj, telemetryId }) => (
<Route path={path} key={telemetryId} exact={true}>
<div data-test-subj={dataTestSubj}>
<RouteInit title={title} path={path} telemetryId={telemetryId} />
<RouteComponent />
</div>
</Route>
))}
<Route component={NotFoundPage} />
</Switch>
</>
<Switch>
{Routes.map(({ title, path, component: RouteComponent, dataTestSubj, telemetryId }) => (
<Route path={path} key={telemetryId} exact={true}>
<div data-test-subj={dataTestSubj}>
<PageHeader />
<RouteInit title={title} path={path} telemetryId={telemetryId} />
<RouteComponent />
</div>
</Route>
))}
<Route component={NotFoundPage} />
</Switch>
);
};