Skip to content

Commit

Permalink
[Synthetics App] Monitor management page (monitor list) (#132836) (#1…
Browse files Browse the repository at this point in the history
…28300)

Basic table foundation for monitor list with paging and disable and delete actions.
  • Loading branch information
awahab07 authored Jun 3, 2022
1 parent 4b273d5 commit bf06045
Show file tree
Hide file tree
Showing 68 changed files with 2,646 additions and 290 deletions.
5 changes: 4 additions & 1 deletion x-pack/plugins/synthetics/common/constants/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ export const MONITOR_EDIT_ROUTE = '/edit-monitor/:monitorId';
export const MONITOR_MANAGEMENT_ROUTE = '/manage-monitors';

export const OVERVIEW_ROUTE = '/';
export const GETTING_STARTED_ROUTE = '/manage-monitors/getting-started';

export const MONITORS_ROUTE = '/monitors';

export const GETTING_STARTED_ROUTE = '/monitors/getting-started';

export const SETTINGS_ROUTE = '/settings';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,12 +315,20 @@ export const EncryptedSyntheticsMonitorWithIdCodec = t.intersection([
t.interface({ id: t.string }),
]);

// TODO: Remove EncryptedSyntheticsMonitorWithIdCodec (as well as SyntheticsMonitorWithIdCodec if possible) along with respective TypeScript types in favor of EncryptedSyntheticsSavedMonitorCodec
export const EncryptedSyntheticsSavedMonitorCodec = t.intersection([
EncryptedSyntheticsMonitorCodec,
t.interface({ id: t.string, updated_at: t.string }),
]);

export type SyntheticsMonitorWithId = t.TypeOf<typeof SyntheticsMonitorWithIdCodec>;

export type EncryptedSyntheticsMonitorWithId = t.TypeOf<
typeof EncryptedSyntheticsMonitorWithIdCodec
>;

export type EncryptedSyntheticsSavedMonitor = t.TypeOf<typeof EncryptedSyntheticsSavedMonitorCodec>;

export const MonitorDefaultsCodec = t.interface({
[DataStream.HTTP]: HTTPFieldsCodec,
[DataStream.TCP]: TCPFieldsCodec,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { EuiPageHeaderProps, EuiPageTemplateProps } from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useInspectorContext } from '@kbn/observability-plugin/public';
import { ClientPluginsStart } from '../../../../../plugin';
import { EmptyStateLoading } from '../../overview/empty_state/empty_state_loading';
import { EmptyStateError } from '../../overview/empty_state/empty_state_error';
import { useHasData } from '../../overview/empty_state/use_has_data';
import { EmptyStateLoading } from '../../monitors_page/overview/empty_state/empty_state_loading';
import { EmptyStateError } from '../../monitors_page/overview/empty_state/empty_state_error';
import { useHasData } from '../../monitors_page/overview/empty_state/use_has_data';
import { useBreakpoints } from '../../../hooks';

interface Props {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
* 2.0.
*/

import { EuiComboBox, EuiFormRow } from '@elastic/eui';
import { Controller, FieldErrors, Control } from 'react-hook-form';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { Controller, FieldErrors, Control } from 'react-hook-form';
import { useSelector } from 'react-redux';
import { serviceLocationsSelector } from '../../../state/monitor_management/selectors';

import { EuiComboBox, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { selectServiceLocationsState } from '../../../state';

import { SimpleFormData } from '../simple_monitor_form';
import { ConfigKey } from '../../../../../../common/constants/monitor_management';

Expand All @@ -21,7 +23,7 @@ export const ServiceLocationsField = ({
errors: FieldErrors;
control: Control<SimpleFormData, any>;
}) => {
const locations = useSelector(serviceLocationsSelector);
const { locations } = useSelector(selectServiceLocationsState);

return (
<EuiFormRow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import { useDispatch } from 'react-redux';
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import { useBreadcrumbs } from '../../hooks';
import { fetchServiceLocationsAction } from '../../state/monitor_management/service_locations';
import { getServiceLocations } from '../../state';
import { SimpleMonitorForm } from './simple_monitor_form';
import { MONITORING_OVERVIEW_LABEL } from '../../../../legacy_uptime/routes';

export const GettingStartedPage = () => {
const dispatch = useDispatch();

useEffect(() => {
dispatch(fetchServiceLocationsAction.get());
dispatch(getServiceLocations());
}, [dispatch]);

useBreadcrumbs([{ text: MONITORING_OVERVIEW_LABEL }]); // No extra breadcrumbs on overview
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,30 @@ import { useFetcher } from '@kbn/observability-plugin/public';
import { useEffect } from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useSelector } from 'react-redux';
import { serviceLocationsSelector } from '../../state/monitor_management/selectors';
import { showSyncErrors } from '../monitor_management/show_sync_errors';
import { createMonitorAPI } from '../../state/monitor_management/api';
import { selectServiceLocationsState } from '../../state';
import { showSyncErrors } from '../monitors_page/management/show_sync_errors';
import { fetchCreateMonitor } from '../../state';
import { DEFAULT_FIELDS } from '../../../../../common/constants/monitor_defaults';
import { ConfigKey } from '../../../../../common/constants/monitor_management';
import { DataStream, SyntheticsMonitorWithId } from '../../../../../common/runtime_types';
import {
DataStream,
ServiceLocationErrors,
SyntheticsMonitorWithId,
} from '../../../../../common/runtime_types';
import { MONITOR_SUCCESS_LABEL, MY_FIRST_MONITOR, SimpleFormData } from './simple_monitor_form';
import { kibanaService } from '../../../../utils/kibana_service';

export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData }) => {
const { application } = useKibana().services;
const locationsList = useSelector(serviceLocationsSelector);
const { locations: serviceLocations } = useSelector(selectServiceLocationsState);

const { data, loading } = useFetcher(() => {
if (!monitorData) {
return new Promise<undefined>((resolve) => resolve(undefined));
}
const { urls, locations } = monitorData;

return createMonitorAPI({
return fetchCreateMonitor({
monitor: {
...DEFAULT_FIELDS.browser,
'source.inline.script': `step('Go to ${urls}', async () => {
Expand All @@ -46,7 +50,11 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData
const newMonitor = data as SyntheticsMonitorWithId;
const hasErrors = data && 'attributes' in data && data.attributes.errors?.length > 0;
if (hasErrors && !loading) {
showSyncErrors(data.attributes.errors, locationsList, kibanaService.toasts);
showSyncErrors(
(data as { attributes: { errors: ServiceLocationErrors } })?.attributes.errors ?? [],
serviceLocations,
kibanaService.toasts
);
}

if (!loading && newMonitor?.id) {
Expand All @@ -56,7 +64,7 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData
});
application?.navigateToApp('uptime', { path: `/monitor/${btoa(newMonitor.id)}` });
}
}, [application, data, loading, locationsList]);
}, [application, data, loading, serviceLocations]);

return { data, loading };
};

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@
*/
import { i18n } from '@kbn/i18n';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
import { MONITOR_MANAGEMENT_ROUTE } from '../../../../../common/constants';
import { PLUGIN } from '../../../../../common/constants/plugin';
import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs';
import { MONITORS_ROUTE } from '../../../../../../common/constants';
import { PLUGIN } from '../../../../../../common/constants/plugin';

export const useMonitorManagementBreadcrumbs = () => {
export const useMonitorListBreadcrumbs = () => {
const kibana = useKibana();
const appPath = kibana.services.application?.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID) ?? '';

useBreadcrumbs([
{
text: MONITOR_MANAGEMENT_CRUMB,
href: `${appPath}/${MONITOR_MANAGEMENT_ROUTE}`,
href: `${appPath}/${MONITORS_ROUTE}`,
},
]);
};

const MONITOR_MANAGEMENT_CRUMB = i18n.translate('xpack.synthetics.monitorsPage.monitorCrumb', {
const MONITOR_MANAGEMENT_CRUMB = i18n.translate('xpack.synthetics.monitorsPage.monitorsMCrumb', {
defaultMessage: 'Monitors',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* 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 { useSelector } from 'react-redux';
import moment from 'moment';
import { useMemo } from 'react';
import { useEsSearch } from '@kbn/observability-plugin/public';
import { selectEncryptedSyntheticsSavedMonitors } from '../../../state';
import { Ping } from '../../../../../../common/runtime_types';
import { EXCLUDE_RUN_ONCE_FILTER } from '../../../../../../common/constants/client_defaults';
import { useSyntheticsRefreshContext } from '../../../contexts/synthetics_refresh_context';
import { useInlineErrorsCount } from './use_inline_errors_count';
import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants';

const sortFieldMap: Record<string, string> = {
['name.keyword']: 'monitor.name',
['urls.keyword']: 'url.full',
['type.keyword']: 'monitor.type',
'@timestamp': '@timestamp',
};

export const getInlineErrorFilters = () => [
{
exists: {
field: 'summary',
},
},
{
exists: {
field: 'error',
},
},
{
bool: {
minimum_should_match: 1,
should: [
{
match_phrase: {
'error.message': 'journey did not finish executing',
},
},
{
match_phrase: {
'error.message': 'ReferenceError:',
},
},
],
},
},
{
range: {
'monitor.timespan': {
lte: moment().toISOString(),
gte: moment().subtract(5, 'minutes').toISOString(),
},
},
},
EXCLUDE_RUN_ONCE_FILTER,
];

export function useInlineErrors({
onlyInvalidMonitors,
sortField = '@timestamp',
sortOrder = 'desc',
}: {
onlyInvalidMonitors?: boolean;
sortField?: string;
sortOrder?: 'asc' | 'desc';
}) {
const syntheticsMonitors = useSelector(selectEncryptedSyntheticsSavedMonitors);

const { lastRefresh } = useSyntheticsRefreshContext();

const configIds = syntheticsMonitors.map((monitor) => monitor.id);

const doFetch = configIds.length > 0 || onlyInvalidMonitors;

const { data } = useEsSearch(
{
index: doFetch ? SYNTHETICS_INDEX_PATTERN : '',
body: {
size: 1000,
query: {
bool: {
filter: getInlineErrorFilters(),
},
},
collapse: { field: 'config_id' },
sort: [{ [sortFieldMap[sortField]]: sortOrder }],
},
},
[syntheticsMonitors, lastRefresh, doFetch, sortField, sortOrder],
{ name: 'getInvalidMonitors' }
);

const { count, loading: countLoading } = useInlineErrorsCount();

return useMemo(() => {
const errorSummaries = data?.hits.hits.map(({ _source: source }) => ({
...(source as Ping),
timestamp: (source as any)['@timestamp'],
}));

return { loading: countLoading, errorSummaries, count };
}, [count, countLoading, data]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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 { useSelector } from 'react-redux';
import { useMemo } from 'react';
import { useEsSearch } from '@kbn/observability-plugin/public';
import { selectEncryptedSyntheticsSavedMonitors } from '../../../state';
import { useSyntheticsRefreshContext } from '../../../contexts/synthetics_refresh_context';
import { getInlineErrorFilters } from './use_inline_errors';
import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants';

export function useInlineErrorsCount() {
const syntheticsMonitors = useSelector(selectEncryptedSyntheticsSavedMonitors);

const { lastRefresh } = useSyntheticsRefreshContext();

const { data, loading } = useEsSearch(
{
index: SYNTHETICS_INDEX_PATTERN,
body: {
size: 0,
query: {
bool: {
filter: getInlineErrorFilters(),
},
},
aggs: {
total: {
cardinality: { field: 'config_id' },
},
},
},
},
[syntheticsMonitors, lastRefresh],
{ name: 'getInvalidMonitorsCount' }
);

return useMemo(() => {
const errorSummariesCount = data?.aggregations?.total.value;

return { loading: loading ?? false, count: errorSummariesCount };
}, [data, loading]);
}
Loading

0 comments on commit bf06045

Please sign in to comment.