mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# 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:
parent
d3c8d82a88
commit
2f04daef98
12 changed files with 173 additions and 152 deletions
|
@ -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,
|
||||
|
|
|
@ -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]);
|
||||
};
|
|
@ -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]);
|
||||
};
|
|
@ -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]
|
||||
|
|
|
@ -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',
|
||||
});
|
|
@ -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.',
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
|
|
@ -76,5 +76,6 @@ export const fetchSyntheticsMonitor = async ({
|
|||
return {
|
||||
...savedObject.attributes,
|
||||
updated_at: savedObject.updated_at,
|
||||
created_at: savedObject.created_at,
|
||||
} as EncryptedSyntheticsSavedMonitor;
|
||||
};
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -40,6 +40,7 @@ export const useInvalidMonitors = (errorSummaries?: Ping[]) => {
|
|||
.map(({ saved_object: savedObject }) => ({
|
||||
...savedObject,
|
||||
updated_at: savedObject.updatedAt!,
|
||||
created_at: savedObject.createdAt!,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}`,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue