[Uptime] Move uptime to new solution nav (#100905)
* 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 <stuermer@weltenwort.de>
This commit is contained in:
parent
c30dc1e08f
commit
2ea4d5713c
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<CoreStart> {
|
||||
|
@ -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',
|
||||
|
|
|
@ -122,6 +122,7 @@ const Application = (props: UptimeAppProps) => {
|
|||
storage,
|
||||
data: startPlugins.data,
|
||||
triggersActionsUi: startPlugins.triggersActionsUi,
|
||||
observability: startPlugins.observability,
|
||||
}}
|
||||
>
|
||||
<Router history={appMountParameters.history}>
|
||||
|
|
|
@ -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 (
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.certificates.heading"
|
||||
defaultMessage="TLS Certificates ({total})"
|
||||
values={{
|
||||
total: <span data-test-subj="uptimeCertTotal">{certificates?.total ?? 0}</span>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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'));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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(<PageHeader />);
|
||||
|
||||
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(<PageHeader showDatePicker />);
|
||||
expect(component.find('[data-test-subj="superDatePickerShowDatesButton"]').length).toBe(1);
|
||||
});
|
||||
|
||||
it('shallow renders with certificate refresh button', () => {
|
||||
const component = renderWithRouter(<PageHeader showCertificateRefreshBtn />);
|
||||
expect(component.find('[data-test-subj="certificatesRefreshButton"]').length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders monitor title when showMonitorTitle', () => {
|
||||
const component = renderWithRouter(<PageHeader showMonitorTitle />);
|
||||
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(<PageHeader showTabs />);
|
||||
expect(component.find('[data-test-subj="uptimeTabs"]').length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -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 (
|
||||
<>
|
||||
<SyntheticsCallout />
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" wrap responsive={false}>
|
||||
<EuiFlexItem>
|
||||
{showMonitorTitle && <MonitorPageTitle />}
|
||||
{showTabs && <PageTabs />}
|
||||
</EuiFlexItem>
|
||||
{showCertificateRefreshBtn && <CertRefreshBtn />}
|
||||
{showDatePicker && (
|
||||
<StyledPicker grow={false} style={{ flexBasis: 485 }}>
|
||||
<UptimeDatePicker />
|
||||
</StyledPicker>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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(<MonitorPageTitle />, {
|
||||
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(<MonitorPageTitle />, {
|
||||
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(<MonitorPageTitle />, {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 = () => {
|
|||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup wrap={false} gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
{type && (
|
||||
{isBrowser && type && (
|
||||
<EuiBadge color="hollow">
|
||||
{renderMonitorType(type)}{' '}
|
||||
{isBrowser && (
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorDetails.title.disclaimer.description"
|
||||
defaultMessage="(BETA)"
|
||||
/>
|
||||
)}
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorDetails.title.disclaimer.description"
|
||||
defaultMessage="(BETA)"
|
||||
/>
|
||||
</EuiBadge>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{isBrowser && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink href="https://www.elastic.co/what-is/synthetic-monitoring" target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorDetails.title.disclaimer.link"
|
||||
defaultMessage="See more"
|
||||
/>
|
||||
</EuiLink>
|
||||
<EuiText>
|
||||
<EuiLink href="https://www.elastic.co/what-is/synthetic-monitoring" target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorDetails.title.disclaimer.link"
|
||||
defaultMessage="See more"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -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<Props> = ({
|
||||
dateFormat,
|
||||
stepName,
|
||||
checkGroup,
|
||||
stepIndex,
|
||||
totalSteps,
|
||||
hasPreviousStep,
|
||||
hasNextStep,
|
||||
handlePreviousStep,
|
||||
handleNextStep,
|
||||
handlePreviousRun,
|
||||
handleNextRun,
|
||||
previousCheckGroup,
|
||||
nextCheckGroup,
|
||||
checkTimestamp,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" responsive={false} wrap>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h1>{stepName}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
onClick={handlePreviousStep}
|
||||
disabled={!hasPreviousStep}
|
||||
iconType="arrowLeft"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.synthetics.stepDetail.totalSteps"
|
||||
defaultMessage="Step {stepIndex} of {totalSteps}"
|
||||
values={{
|
||||
stepIndex,
|
||||
totalSteps,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
onClick={handleNextStep}
|
||||
disabled={!hasNextStep}
|
||||
iconType="arrowRight"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="flexEnd" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
onClick={handlePreviousRun}
|
||||
disabled={!previousCheckGroup}
|
||||
iconType="arrowLeft"
|
||||
aria-label={PREVIOUS_CHECK_BUTTON_TEXT}
|
||||
>
|
||||
{PREVIOUS_CHECK_BUTTON_TEXT}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">{moment(checkTimestamp).format(dateFormat)}</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
onClick={handleNextRun}
|
||||
disabled={!nextCheckGroup}
|
||||
iconType="arrowRight"
|
||||
iconSide="right"
|
||||
aria-label={NEXT_CHECK_BUTTON_TEXT}
|
||||
>
|
||||
{NEXT_CHECK_BUTTON_TEXT}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
<WaterfallChartContainer checkGroup={checkGroup} stepIndex={stepIndex} />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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<Props> = ({ checkGroup, stepIndex })
|
|||
history.push(`/journey/${journey?.details?.previous?.checkGroup}/step/1`);
|
||||
}, [history, journey?.details?.previous?.checkGroup]);
|
||||
|
||||
const {
|
||||
services: { observability },
|
||||
} = useKibana<ClientPluginsStart>();
|
||||
const PageTemplateComponent = observability.navigation.PageTemplate;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTemplateComponent
|
||||
pageHeader={{
|
||||
pageTitle:
|
||||
journey && activeStep ? (
|
||||
<StepPageTitle
|
||||
stepName={activeStep.synthetics?.step?.name}
|
||||
stepIndex={stepIndex}
|
||||
totalSteps={journey.steps.length}
|
||||
hasPreviousStep={hasPreviousStep}
|
||||
hasNextStep={hasNextStep}
|
||||
handlePreviousStep={handlePreviousStep}
|
||||
handleNextStep={handleNextStep}
|
||||
/>
|
||||
) : null,
|
||||
rightSideItems: journey
|
||||
? [
|
||||
<StepPageNavigation
|
||||
dateFormat={dateFormat}
|
||||
handleNextRun={handleNextRun}
|
||||
handlePreviousRun={handlePreviousRun}
|
||||
nextCheckGroup={journey.details?.next?.checkGroup}
|
||||
previousCheckGroup={journey.details?.previous?.checkGroup}
|
||||
checkTimestamp={journey.details?.timestamp}
|
||||
/>,
|
||||
]
|
||||
: [],
|
||||
}}
|
||||
>
|
||||
<EuiPanel>
|
||||
{(!journey || journey.loading) && (
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
|
@ -86,24 +122,9 @@ export const StepDetailContainer: React.FC<Props> = ({ checkGroup, stepIndex })
|
|||
</EuiFlexGroup>
|
||||
)}
|
||||
{journey && activeStep && !journey.loading && (
|
||||
<StepDetail
|
||||
checkGroup={checkGroup}
|
||||
stepName={activeStep.synthetics?.step?.name}
|
||||
stepIndex={stepIndex}
|
||||
totalSteps={journey.steps.length}
|
||||
hasPreviousStep={hasPreviousStep}
|
||||
hasNextStep={hasNextStep}
|
||||
handlePreviousStep={handlePreviousStep}
|
||||
handleNextStep={handleNextStep}
|
||||
handleNextRun={handleNextRun}
|
||||
handlePreviousRun={handlePreviousRun}
|
||||
previousCheckGroup={journey.details?.previous?.checkGroup}
|
||||
nextCheckGroup={journey.details?.next?.checkGroup}
|
||||
checkTimestamp={journey.details?.timestamp}
|
||||
dateFormat={dateFormat}
|
||||
/>
|
||||
<WaterfallChartContainer checkGroup={checkGroup} stepIndex={stepIndex} />
|
||||
)}
|
||||
</EuiPanel>
|
||||
</>
|
||||
</PageTemplateComponent>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="flexEnd" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
onClick={handlePreviousRun}
|
||||
disabled={!previousCheckGroup}
|
||||
iconType="arrowLeft"
|
||||
aria-label={PREVIOUS_CHECK_BUTTON_TEXT}
|
||||
>
|
||||
{PREVIOUS_CHECK_BUTTON_TEXT}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">{moment(checkTimestamp).format(dateFormat)}</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
onClick={handleNextRun}
|
||||
disabled={!nextCheckGroup}
|
||||
iconType="arrowRight"
|
||||
iconSide="right"
|
||||
aria-label={NEXT_CHECK_BUTTON_TEXT}
|
||||
>
|
||||
{NEXT_CHECK_BUTTON_TEXT}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -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 (
|
||||
<EuiFlexGroup alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h1>{stepName}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
onClick={handlePreviousStep}
|
||||
disabled={!hasPreviousStep}
|
||||
iconType="arrowLeft"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.synthetics.stepDetail.totalSteps"
|
||||
defaultMessage="Step {stepIndex} of {totalSteps}"
|
||||
values={{
|
||||
stepIndex,
|
||||
totalSteps,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
onClick={handleNextStep}
|
||||
disabled={!hasNextStep}
|
||||
iconType="arrowRight"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -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],
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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<UptimeUrlParams> = { ...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]);
|
||||
};
|
||||
|
|
|
@ -79,6 +79,12 @@ const createMockStore = () => {
|
|||
};
|
||||
};
|
||||
|
||||
const mockAppUrls: Record<string, string> = {
|
||||
uptime: '/app/uptime',
|
||||
observability: '/app/observability',
|
||||
'/home#/tutorial/uptimeMonitors': '/home#/tutorial/uptimeMonitors',
|
||||
};
|
||||
|
||||
/* default mock core */
|
||||
const defaultCore = coreMock.createStart();
|
||||
const mockCore: () => Partial<CoreStart> = () => {
|
||||
|
@ -86,7 +92,7 @@ const mockCore: () => Partial<CoreStart> = () => {
|
|||
...defaultCore,
|
||||
application: {
|
||||
...defaultCore.application,
|
||||
getUrlForApp: () => '/app/uptime',
|
||||
getUrlForApp: (app: string) => mockAppUrls[app],
|
||||
navigateToUrl: jest.fn(),
|
||||
capabilities: {
|
||||
...defaultCore.application.capabilities,
|
||||
|
|
|
@ -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 (
|
||||
<EuiPanel>
|
||||
<EuiTitle>
|
||||
<h1 className="eui-textNoWrap">
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.certificates.heading"
|
||||
defaultMessage="TLS Certificates ({total})"
|
||||
values={{
|
||||
total: <span data-test-subj="uptimeCertTotal">{certificates?.total ?? 0}</span>,
|
||||
}}
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<CertificateSearch setSearch={setSearch} />
|
||||
<EuiSpacer size="m" />
|
||||
|
@ -86,6 +71,6 @@ export const CertificatesPage: React.FC = () => {
|
|||
}}
|
||||
sort={sort}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 (
|
||||
<EmptyState>
|
||||
|
|
|
@ -148,73 +148,71 @@ export const SettingsPage: React.FC = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPanel style={{ maxWidth: 1000, margin: 'auto' }}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>{cannotEditNotice}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div id="settings-form">
|
||||
<EuiForm>
|
||||
<IndicesForm
|
||||
loading={dss.loading}
|
||||
onChange={onChangeFormField}
|
||||
formFields={formFields}
|
||||
fieldErrors={fieldErrors}
|
||||
isDisabled={isFormDisabled}
|
||||
/>
|
||||
<AlertDefaultsForm
|
||||
loading={dss.loading}
|
||||
formFields={formFields}
|
||||
onChange={onChangeFormField}
|
||||
fieldErrors={fieldErrors}
|
||||
isDisabled={isFormDisabled}
|
||||
/>
|
||||
<CertificateExpirationForm
|
||||
loading={dss.loading}
|
||||
onChange={onChangeFormField}
|
||||
formFields={formFields}
|
||||
fieldErrors={fieldErrors}
|
||||
isDisabled={isFormDisabled}
|
||||
/>
|
||||
<EuiPanel style={{ maxWidth: 1000, margin: 'auto' }}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>{cannotEditNotice}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div id="settings-form">
|
||||
<EuiForm>
|
||||
<IndicesForm
|
||||
loading={dss.loading}
|
||||
onChange={onChangeFormField}
|
||||
formFields={formFields}
|
||||
fieldErrors={fieldErrors}
|
||||
isDisabled={isFormDisabled}
|
||||
/>
|
||||
<AlertDefaultsForm
|
||||
loading={dss.loading}
|
||||
formFields={formFields}
|
||||
onChange={onChangeFormField}
|
||||
fieldErrors={fieldErrors}
|
||||
isDisabled={isFormDisabled}
|
||||
/>
|
||||
<CertificateExpirationForm
|
||||
loading={dss.loading}
|
||||
onChange={onChangeFormField}
|
||||
formFields={formFields}
|
||||
fieldErrors={fieldErrors}
|
||||
isDisabled={isFormDisabled}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="discardSettingsButton"
|
||||
isDisabled={!isFormDirty || isFormDisabled}
|
||||
onClick={() => {
|
||||
resetForm();
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.discardSettingsButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="apply-settings-button"
|
||||
onClick={onApply}
|
||||
color="primary"
|
||||
isDisabled={!isFormDirty || !isFormValid || isFormDisabled}
|
||||
fill
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.applySettingsButtonLabel"
|
||||
defaultMessage="Apply changes"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiForm>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="discardSettingsButton"
|
||||
isDisabled={!isFormDirty || isFormDisabled}
|
||||
onClick={() => {
|
||||
resetForm();
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.discardSettingsButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="apply-settings-button"
|
||||
onClick={onApply}
|
||||
color="primary"
|
||||
isDisabled={!isFormDirty || !isFormValid || isFormDisabled}
|
||||
fill
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.applySettingsButtonLabel"
|
||||
defaultMessage="Apply changes"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiForm>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<ClientPluginsStart>();
|
||||
const PageTemplateComponent = observability.navigation.PageTemplate;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h1>{details?.journey?.monitor.name || details?.journey?.monitor.id}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{details && <ChecksNavigation timestamp={details.timestamp} details={details} />}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<PageTemplateComponent
|
||||
pageHeader={{
|
||||
pageTitle: details?.journey?.monitor.name || details?.journey?.monitor.id,
|
||||
rightSideItems: [
|
||||
details ? <ChecksNavigation timestamp={details.timestamp} details={details} /> : null,
|
||||
],
|
||||
}}
|
||||
>
|
||||
<StepsList data={steps} loading={loading} error={error} />
|
||||
{(!steps || steps.length === 0) && !loading && <EmptyJourney checkGroup={checkGroup} />}
|
||||
</>
|
||||
</PageTemplateComponent>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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: <MonitorPageTitle />,
|
||||
rightSideItems: [<UptimeDatePicker />],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -51,8 +63,10 @@ const Routes: RouteProps[] = [
|
|||
component: SettingsPage,
|
||||
dataTestSubj: 'uptimeSettingsPage',
|
||||
telemetryId: UptimePage.Settings,
|
||||
headerProps: {
|
||||
showTabs: true,
|
||||
pageHeader: {
|
||||
pageTitle: (
|
||||
<FormattedMessage id="xpack.uptime.settings.heading" defaultMessage="Uptime settings" />
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -61,9 +75,9 @@ const Routes: RouteProps[] = [
|
|||
component: CertificatesPage,
|
||||
dataTestSubj: 'uptimeCertificatesPage',
|
||||
telemetryId: UptimePage.Certificates,
|
||||
headerProps: {
|
||||
showCertificateRefreshBtn: true,
|
||||
showTabs: true,
|
||||
pageHeader: {
|
||||
pageTitle: <CertificateTitle />,
|
||||
rightSideItems: [<CertRefreshBtn />],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -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: [<UptimeDatePicker />],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -106,31 +120,31 @@ const RouteInit: React.FC<Pick<RouteProps, 'path' | 'title' | 'telemetryId'>> =
|
|||
};
|
||||
|
||||
export const PageRouter: FC = () => {
|
||||
const {
|
||||
services: { observability },
|
||||
} = useKibana<ClientPluginsStart>();
|
||||
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={[...Routes.map((route) => route.path)]}
|
||||
exact={true}
|
||||
render={({ match }: RouteComponentProps) => {
|
||||
const routeProps: RouteProps | undefined = Routes.find(
|
||||
(route: RouteProps) => route?.path === match?.path
|
||||
);
|
||||
return routeProps?.headerProps && <PageHeader {...routeProps?.headerProps} />;
|
||||
}}
|
||||
/>
|
||||
<Switch>
|
||||
{Routes.map(({ title, path, component: RouteComponent, dataTestSubj, telemetryId }) => (
|
||||
<Switch>
|
||||
{Routes.map(
|
||||
({ title, path, component: RouteComponent, dataTestSubj, telemetryId, pageHeader }) => (
|
||||
<Route path={path} key={telemetryId} exact={true}>
|
||||
<div data-test-subj={dataTestSubj}>
|
||||
<SyntheticsCallout />
|
||||
<RouteInit title={title} path={path} telemetryId={telemetryId} />
|
||||
<RouteComponent />
|
||||
{pageHeader ? (
|
||||
<PageTemplateComponent pageHeader={pageHeader}>
|
||||
<RouteComponent />
|
||||
</PageTemplateComponent>
|
||||
) : (
|
||||
<RouteComponent />
|
||||
)}
|
||||
</div>
|
||||
</Route>
|
||||
))}
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 }) {
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue