[8.6] [Synthetics] Update monitor details page loading (#145931) (#146282)

# Backport

This will backport the following commits from `main` to `8.6`:
- [[Synthetics] Update monitor details page loading
(#145931)](https://github.com/elastic/kibana/pull/145931)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Shahzad","email":"shahzad.muhammad@elastic.co"},"sourceCommit":{"committedDate":"2022-11-24T11:33:40Z","message":"[Synthetics]
Update monitor details page loading (#145931)\n\nCo-authored-by: Abdul
Zahid
<awahab07@yahoo.com>","sha":"698504708a48b4706fbe2d4cb98bd2f997da3d6d","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:uptime","release_note:skip","v8.6.0","v8.7.0"],"number":145931,"url":"https://github.com/elastic/kibana/pull/145931","mergeCommit":{"message":"[Synthetics]
Update monitor details page loading (#145931)\n\nCo-authored-by: Abdul
Zahid
<awahab07@yahoo.com>","sha":"698504708a48b4706fbe2d4cb98bd2f997da3d6d"}},"sourceBranch":"main","suggestedTargetBranches":["8.6"],"targetPullRequestStates":[{"branch":"8.6","label":"v8.6.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/145931","number":145931,"mergeCommit":{"message":"[Synthetics]
Update monitor details page loading (#145931)\n\nCo-authored-by: Abdul
Zahid
<awahab07@yahoo.com>","sha":"698504708a48b4706fbe2d4cb98bd2f997da3d6d"}}]}]
BACKPORT-->

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Shahzad 2022-11-28 19:45:04 +01:00 committed by GitHub
parent d3c8d82a88
commit 2f04daef98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 173 additions and 152 deletions

View file

@ -349,7 +349,7 @@ export const EncryptedSyntheticsMonitorWithIdCodec = t.intersection([
// 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 }),
t.interface({ id: t.string, updated_at: t.string, created_at: t.string }),
]);
export type SyntheticsMonitorWithId = t.TypeOf<typeof SyntheticsMonitorWithIdCodec>;
@ -377,6 +377,7 @@ export const MonitorManagementListResultCodec = t.type({
id: t.string,
attributes: EncryptedSyntheticsMonitorCodec,
updated_at: t.string,
created_at: t.string,
})
),
page: t.number,

View file

@ -1,35 +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 { useFetcher } from '@kbn/observability-plugin/public';
import { useParams } from 'react-router-dom';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useMemo } from 'react';
import moment from 'moment';
import { useMonitorQueryId } from './use_monitor_query_id';
export const useEarliestStartDate = () => {
const monitorId = useMonitorQueryId();
const { monitorId: soId } = useParams<{ monitorId: string }>();
const { savedObjects } = useKibana().services;
const { data: dataFromSO, loading } = useFetcher(async () => {
return savedObjects?.client?.get('synthetics-monitor', soId);
}, [monitorId]);
return useMemo(() => {
if (dataFromSO?.createdAt) {
const diff = moment(dataFromSO?.createdAt).diff(moment().subtract(30, 'day'), 'days');
if (diff > 0) {
return { from: dataFromSO?.createdAt, loading };
}
}
return { from: 'now-30d/d', loading };
}, [dataFromSO?.createdAt, loading]);
};

View file

@ -0,0 +1,24 @@
/*
* 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 { useMemo } from 'react';
import moment from 'moment';
import { useSelectedMonitor } from './use_selected_monitor';
export const useEarliestStartDate = () => {
const { monitor, loading } = useSelectedMonitor();
return useMemo(() => {
if (monitor?.created_at) {
const diff = moment(monitor?.created_at).diff(moment().subtract(30, 'day'), 'days');
if (diff > 0) {
return { from: monitor?.created_at, loading };
}
}
return { from: 'now-30d/d', loading };
}, [monitor?.created_at, loading]);
};

View file

@ -21,6 +21,7 @@ export const useSelectedMonitor = () => {
const { monitorId } = useParams<{ monitorId: string }>();
const monitorsList = useSelector(selectEncryptedSyntheticsSavedMonitors);
const { loading: monitorListLoading } = useSelector(selectMonitorListState);
const monitorFromList = useMemo(
() => monitorsList.find((monitor) => monitor[ConfigKey.CONFIG_ID] === monitorId) ?? null,
[monitorId, monitorsList]

View file

@ -0,0 +1,125 @@
/*
* 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, { useEffect, useState } from 'react';
import {
EuiHighlight,
EuiLink,
EuiPopoverTitle,
EuiSelectable,
EuiSelectableOption,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useHistory } from 'react-router-dom';
import { useRecentlyViewedMonitors } from './use_recently_viewed_monitors';
import { useSelectedLocation } from '../hooks/use_selected_location';
import { useMonitorName } from './use_monitor_name';
import { AddMonitorLink } from '../../common/links/add_monitor';
import { useSyntheticsSettingsContext } from '../../../contexts';
export const MonitorSearchableList = ({ closePopover }: { closePopover: () => void }) => {
const history = useHistory();
const recentlyViewed = useRecentlyViewedMonitors();
const [options, setOptions] = useState<EuiSelectableOption[]>([]);
const [searchValue, setSearchValue] = useState('');
const selectedLocation = useSelectedLocation();
const { basePath } = useSyntheticsSettingsContext();
const { values, loading } = useMonitorName({ search: searchValue });
useEffect(() => {
const newOptions: EuiSelectableOption[] = [];
if (recentlyViewed.length > 0 && !searchValue) {
const otherMonitors = values.filter((value) =>
recentlyViewed.every((recent) => recent.key !== value.key)
);
if (otherMonitors.length > 0) {
newOptions.push({ key: 'monitors', label: OTHER_MONITORS, isGroupLabel: true });
}
setOptions([...recentlyViewed, ...newOptions, ...otherMonitors]);
} else {
setOptions(values);
}
}, [recentlyViewed, searchValue, values]);
return (
<EuiSelectable
searchable
isLoading={loading}
searchProps={{
placeholder: PLACEHOLDER,
compressed: true,
onChange: (val) => setSearchValue(val),
autoFocus: true,
}}
options={options}
onChange={(selectedOptions) => {
setOptions(selectedOptions);
const option = selectedOptions.find((opt) => opt.checked === 'on');
if (option) {
history.push(`/monitor/${option.key}?locationId=${selectedLocation?.id}`);
}
closePopover();
}}
singleSelection={true}
listProps={{
showIcons: false,
}}
renderOption={(option, search) => (
<EuiLink
href={`${basePath}/app/synthetics/monitor/${option.key}?locationId=${selectedLocation?.id}`}
>
<EuiHighlight search={searchValue}>{option.label}</EuiHighlight>
</EuiLink>
)}
noMatchesMessage={NO_RESULT_FOUND}
emptyMessage={<AddMonitorLink />}
loadingMessage={LOADING_MONITORS}
>
{(list, search) => (
<div style={{ width: 280 }}>
<EuiPopoverTitle paddingSize="s">
{options.length > 0 || searchValue ? (
search
) : (
<EuiText color="subdued" size="s" className="eui-textCenter">
{NO_OTHER_MONITORS_EXISTS}
</EuiText>
)}
</EuiPopoverTitle>
{list}
</div>
)}
</EuiSelectable>
);
};
const LOADING_MONITORS = i18n.translate('xpack.synthetics.monitorSummary.loadingMonitors', {
defaultMessage: 'Loading monitors',
});
const NO_OTHER_MONITORS_EXISTS = i18n.translate('xpack.synthetics.monitorSummary.noOtherMonitors', {
defaultMessage: 'No other monitors exist.',
});
const NO_RESULT_FOUND = i18n.translate('xpack.synthetics.monitorSummary.noResultsFound', {
defaultMessage: 'No monitors found. Try modifying your query.',
});
const PLACEHOLDER = i18n.translate('xpack.synthetics.monitorSummary.placeholderSearch', {
defaultMessage: 'Monitor name or tag',
});
const OTHER_MONITORS = i18n.translate('xpack.synthetics.monitorSummary.otherMonitors', {
defaultMessage: 'Other monitors',
});

View file

@ -5,57 +5,14 @@
* 2.0.
*/
import React, { Fragment, useEffect, useState } from 'react';
import {
EuiPopover,
EuiPopoverTitle,
EuiSelectable,
EuiButtonIcon,
EuiSelectableOption,
EuiHighlight,
EuiLink,
EuiText,
} from '@elastic/eui';
import React, { Fragment, useState } from 'react';
import { EuiPopover, EuiPopoverTitle, EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useHistory } from 'react-router-dom';
import { AddMonitorLink } from '../../common/links/add_monitor';
import { useRecentlyViewedMonitors } from './use_recently_viewed_monitors';
import { useSyntheticsSettingsContext } from '../../../contexts';
import { useMonitorName } from './use_monitor_name';
import { useSelectedLocation } from '../hooks/use_selected_location';
import { MonitorSearchableList } from './monitor_searchable_list';
export const MonitorSelector = () => {
const history = useHistory();
const [options, setOptions] = useState<EuiSelectableOption[]>([]);
const [searchValue, setSearchValue] = useState('');
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const selectedLocation = useSelectedLocation();
const { basePath } = useSyntheticsSettingsContext();
const { values, loading } = useMonitorName({ search: searchValue });
const recentlyViewed = useRecentlyViewedMonitors();
useEffect(() => {
const newOptions: EuiSelectableOption[] = [];
if (recentlyViewed.length > 0 && !searchValue) {
const otherMonitors = values.filter((value) =>
recentlyViewed.every((recent) => recent.key !== value.key)
);
if (otherMonitors.length > 0) {
newOptions.push({ key: 'monitors', label: OTHER_MONITORS, isGroupLabel: true });
}
setOptions([...recentlyViewed, ...newOptions, ...otherMonitors]);
} else {
setOptions(values);
}
}, [recentlyViewed, searchValue, values]);
const onButtonClick = () => {
setIsPopoverOpen(!isPopoverOpen);
};
@ -83,54 +40,7 @@ export const MonitorSelector = () => {
closePopover={closePopover}
>
<EuiPopoverTitle paddingSize="s">{GO_TO_MONITOR}</EuiPopoverTitle>
<EuiSelectable
searchable
isLoading={loading}
searchProps={{
placeholder: PLACEHOLDER,
compressed: true,
onChange: (val) => setSearchValue(val),
autoFocus: true,
}}
options={options}
onChange={(selectedOptions) => {
setOptions(selectedOptions);
const option = selectedOptions.find((opt) => opt.checked === 'on');
if (option) {
history.push(`/monitor/${option.key}?locationId=${selectedLocation?.id}`);
}
closePopover();
}}
singleSelection={true}
listProps={{
showIcons: false,
}}
renderOption={(option, search) => (
<EuiLink
href={`${basePath}/app/synthetics/monitor/${option.key}?locationId=${selectedLocation?.id}`}
>
<EuiHighlight search={searchValue}>{option.label}</EuiHighlight>
</EuiLink>
)}
noMatchesMessage={NO_RESULT_FOUND}
emptyMessage={<AddMonitorLink />}
loadingMessage={LOADING_MONITORS}
>
{(list, search) => (
<div style={{ width: 280 }}>
<EuiPopoverTitle paddingSize="s">
{options.length > 0 || searchValue ? (
search
) : (
<EuiText color="subdued" size="s" className="eui-textCenter">
{NO_OTHER_MONITORS_EXISTS}
</EuiText>
)}
</EuiPopoverTitle>
{list}
</div>
)}
</EuiSelectable>
<MonitorSearchableList closePopover={closePopover} />
</EuiPopover>
</Fragment>
);
@ -140,26 +50,6 @@ const GO_TO_MONITOR = i18n.translate('xpack.synthetics.monitorSummary.goToMonito
defaultMessage: 'Go to monitor',
});
const NO_RESULT_FOUND = i18n.translate('xpack.synthetics.monitorSummary.noResultsFound', {
defaultMessage: 'No monitors found. Try modifying your query.',
});
const PLACEHOLDER = i18n.translate('xpack.synthetics.monitorSummary.placeholderSearch', {
defaultMessage: 'Monitor name or tag',
});
const SELECT_MONITOR = i18n.translate('xpack.synthetics.monitorSummary.selectMonitor', {
defaultMessage: 'Select a different monitor to view its details',
});
const OTHER_MONITORS = i18n.translate('xpack.synthetics.monitorSummary.otherMonitors', {
defaultMessage: 'Other monitors',
});
const LOADING_MONITORS = i18n.translate('xpack.synthetics.monitorSummary.loadingMonitors', {
defaultMessage: 'Loading monitors',
});
const NO_OTHER_MONITORS_EXISTS = i18n.translate('xpack.synthetics.monitorSummary.noOtherMonitors', {
defaultMessage: 'No other monitors exist.',
});

View file

@ -18,7 +18,7 @@ import {
import { i18n } from '@kbn/i18n';
import { LoadWhenInView } from '@kbn/observability-plugin/public';
import { useEarliestStartDate } from '../hooks/use_earliest_start_data';
import { useEarliestStartDate } from '../hooks/use_earliest_start_date';
import { MonitorErrorSparklines } from './monitor_error_sparklines';
import { MonitorStatusPanel } from '../monitor_status/monitor_status_panel';
import { DurationSparklines } from './duration_sparklines';
@ -40,7 +40,7 @@ export const MonitorSummary = () => {
const { from, to } = useAbsoluteDate({ from: fromRelative, to: toRelative });
if (loading) {
return <EuiLoadingSpinner size="xl" />;
return <LoadingState />;
}
const dateLabel = from === 'now-30d/d' ? LAST_30_DAYS_LABEL : TO_DATE_LABEL;
@ -133,6 +133,16 @@ export const MonitorSummary = () => {
);
};
function LoadingState({ height }: { height?: string }) {
return (
<EuiFlexGroup alignItems="center" justifyContent="center" style={{ height: height ?? '100%' }}>
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="xl" />
</EuiFlexItem>
</EuiFlexGroup>
);
}
const SUMMARY_LABEL = i18n.translate('xpack.synthetics.detailsPanel.summary', {
defaultMessage: 'Summary',
});

View file

@ -76,5 +76,6 @@ export const fetchSyntheticsMonitor = async ({
return {
...savedObject.attributes,
updated_at: savedObject.updated_at,
created_at: savedObject.created_at,
} as EncryptedSyntheticsSavedMonitor;
};

View file

@ -18,6 +18,7 @@ export const selectEncryptedSyntheticsSavedMonitors = createSelector(
...monitor.attributes,
id: monitor.attributes[ConfigKey.MONITOR_QUERY_ID],
updated_at: monitor.updated_at,
created_at: monitor.created_at,
})) as EncryptedSyntheticsSavedMonitor[]
);
export const selectMonitorUpsertStatuses = (state: SyntheticsAppState) =>

View file

@ -416,6 +416,7 @@ function getMonitorDetailsMockSlice() {
'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'] as TLSVersion[],
revision: 1,
updated_at: '2022-07-24T17:15:46.342Z',
created_at: '2022-05-24T13:20:49.322Z',
},
syntheticsMonitorLoading: false,
error: null,

View file

@ -40,6 +40,7 @@ export const useInvalidMonitors = (errorSummaries?: Ping[]) => {
.map(({ saved_object: savedObject }) => ({
...savedObject,
updated_at: savedObject.updatedAt!,
created_at: savedObject.createdAt!,
}));
}
}

View file

@ -28,6 +28,7 @@ describe('<MonitorManagementList />', () => {
monitors.push({
id: `test-monitor-id-${i}`,
updated_at: '123',
created_at: '123',
attributes: {
config_id: `test-monitor-id-${i}`,
name: `test-monitor-${i}`,