mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Synthetics] Changed embeddable view when only one monitor in one location is selected (#218402)
This PR closes #208981 by adding a new action to the Monitor card to view only that monitor in the dashboard. https://github.com/user-attachments/assets/f500d220-b57f-4c43-a632-b2383e33988e --------- Co-authored-by: Shahzad <shahzad31comp@gmail.com>
This commit is contained in:
parent
431116a33a
commit
ec939b6718
7 changed files with 217 additions and 65 deletions
|
@ -51,7 +51,7 @@ export const getMonitorsEmbeddableFactory = (
|
|||
deserializeState: (state) => {
|
||||
return state.rawState as OverviewEmbeddableState;
|
||||
},
|
||||
buildEmbeddable: async (state, buildApi, uuid, parentApi) => {
|
||||
buildEmbeddable: async (state, buildApi) => {
|
||||
const [coreStart, pluginStart] = await getStartServices();
|
||||
|
||||
const titleManager = initializeTitleManager(state);
|
||||
|
|
|
@ -5,17 +5,28 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { Subject } from 'rxjs';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { areFiltersEmpty } from '../common/utils';
|
||||
import { getOverviewStore } from './redux_store';
|
||||
import { ShowSelectedFilters } from '../common/show_selected_filters';
|
||||
import { setOverviewPageStateAction } from '../../synthetics/state';
|
||||
import {
|
||||
selectOverviewTrends,
|
||||
setFlyoutConfig,
|
||||
setOverviewPageStateAction,
|
||||
trendStatsBatch,
|
||||
} from '../../synthetics/state';
|
||||
import { MonitorFilters } from './types';
|
||||
import { EmbeddablePanelWrapper } from '../../synthetics/components/common/components/embeddable_panel_wrapper';
|
||||
import { SyntheticsEmbeddableContext } from '../synthetics_embeddable_context';
|
||||
import { OverviewGrid } from '../../synthetics/components/monitors_page/overview/overview/overview_grid';
|
||||
import { useMonitorsSortedByStatus } from '../../synthetics/hooks/use_monitors_sorted_by_status';
|
||||
import { MetricItem } from '../../synthetics/components/monitors_page/overview/overview/metric_item/metric_item';
|
||||
import { FlyoutParamProps } from '../../synthetics/components/monitors_page/overview/overview/types';
|
||||
import { MaybeMonitorDetailsFlyout } from '../../synthetics/components/monitors_page/overview/overview/monitor_detail_flyout';
|
||||
import { useOverviewStatus } from '../../synthetics/components/monitors_page/hooks/use_overview_status';
|
||||
import { OverviewLoader } from '../../synthetics/components/monitors_page/overview/overview/overview_loader';
|
||||
|
||||
export const StatusGridComponent = ({
|
||||
reload$,
|
||||
|
@ -27,19 +38,83 @@ export const StatusGridComponent = ({
|
|||
const overviewStore = useRef(getOverviewStore());
|
||||
|
||||
const hasFilters = !areFiltersEmpty(filters);
|
||||
const singleMonitor =
|
||||
filters && filters.locations.length === 1 && filters.monitorIds.length === 1;
|
||||
|
||||
return (
|
||||
const monitorOverviewListComponent = (
|
||||
<SyntheticsEmbeddableContext reload$={reload$} reduxStore={overviewStore.current}>
|
||||
<MonitorsOverviewList filters={filters} singleMonitor={singleMonitor} />
|
||||
</SyntheticsEmbeddableContext>
|
||||
);
|
||||
|
||||
return singleMonitor ? (
|
||||
monitorOverviewListComponent
|
||||
) : (
|
||||
<EmbeddablePanelWrapper
|
||||
titleAppend={hasFilters ? <ShowSelectedFilters filters={filters ?? {}} /> : null}
|
||||
>
|
||||
<SyntheticsEmbeddableContext reload$={reload$} reduxStore={overviewStore.current}>
|
||||
<MonitorsOverviewList filters={filters} />
|
||||
</SyntheticsEmbeddableContext>
|
||||
{monitorOverviewListComponent}
|
||||
</EmbeddablePanelWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const MonitorsOverviewList = ({ filters }: { filters: MonitorFilters }) => {
|
||||
const SingleMonitorView = () => {
|
||||
const trendData = useSelector(selectOverviewTrends);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const setFlyoutConfigCallback = useCallback(
|
||||
(params: FlyoutParamProps) => {
|
||||
dispatch(setFlyoutConfig(params));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const { loaded } = useOverviewStatus({
|
||||
scopeStatusByLocation: true,
|
||||
});
|
||||
const monitorsSortedByStatus = useMonitorsSortedByStatus();
|
||||
|
||||
if (loaded && monitorsSortedByStatus.length !== 1) {
|
||||
throw new Error(
|
||||
'One and only one monitor should always be returned by useMonitorsSortedByStatus in this component, this should never happen'
|
||||
);
|
||||
}
|
||||
|
||||
const monitor = monitorsSortedByStatus.length === 1 ? monitorsSortedByStatus[0] : undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (monitor && !trendData[monitor.configId + monitor.locationId]) {
|
||||
dispatch(
|
||||
trendStatsBatch.get([
|
||||
{
|
||||
configId: monitor.configId,
|
||||
locationId: monitor.locationId,
|
||||
schedule: monitor.schedule,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
}, [dispatch, monitor, trendData]);
|
||||
|
||||
const style = { height: '100%' };
|
||||
|
||||
if (!monitor) return <OverviewLoader rows={1} columns={1} style={style} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<MetricItem monitor={monitor} onClick={setFlyoutConfigCallback} style={style} />
|
||||
<MaybeMonitorDetailsFlyout setFlyoutConfigCallback={setFlyoutConfigCallback} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MonitorsOverviewList = ({
|
||||
filters,
|
||||
singleMonitor,
|
||||
}: {
|
||||
filters: MonitorFilters;
|
||||
singleMonitor?: boolean;
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
useEffect(() => {
|
||||
if (!filters) return;
|
||||
|
@ -54,5 +129,9 @@ const MonitorsOverviewList = ({ filters }: { filters: MonitorFilters }) => {
|
|||
);
|
||||
}, [dispatch, filters]);
|
||||
|
||||
if (singleMonitor) {
|
||||
return <SingleMonitorView />;
|
||||
}
|
||||
|
||||
return <OverviewGrid />;
|
||||
};
|
||||
|
|
|
@ -28,25 +28,28 @@ import {
|
|||
|
||||
const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard);
|
||||
|
||||
export const AddToDashboard = ({
|
||||
export const useAddToDashboard = ({
|
||||
type,
|
||||
asButton = false,
|
||||
embeddableInput = {},
|
||||
objectType = i18n.translate('xpack.synthetics.item.actions.addToDashboard.objectTypeLabel', {
|
||||
defaultMessage: 'Status Overview',
|
||||
}),
|
||||
documentTitle = i18n.translate('xpack.synthetics.item.actions.addToDashboard.attachmentTitle', {
|
||||
defaultMessage: 'Status Overview',
|
||||
}),
|
||||
}: {
|
||||
type: typeof SYNTHETICS_STATS_OVERVIEW_EMBEDDABLE | typeof SYNTHETICS_MONITORS_EMBEDDABLE;
|
||||
asButton?: boolean;
|
||||
embeddableInput?: Record<string, unknown>;
|
||||
objectType?: string;
|
||||
documentTitle?: string;
|
||||
}) => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
|
||||
const [isDashboardAttachmentReady, setDashboardAttachmentReady] = React.useState(false);
|
||||
const closePopover = () => {
|
||||
setIsPopoverOpen(false);
|
||||
};
|
||||
|
||||
const { embeddable } = useKibana<ClientPluginsStart>().services;
|
||||
|
||||
const handleAttachToDashboardSave: SaveModalDashboardProps['onSave'] = useCallback(
|
||||
({ dashboardId, newTitle, newDescription }) => {
|
||||
({ dashboardId }) => {
|
||||
const stateTransfer = embeddable.getStateTransfer();
|
||||
const embeddableInput = {};
|
||||
|
||||
const state = {
|
||||
input: embeddableInput,
|
||||
|
@ -60,8 +63,42 @@ export const AddToDashboard = ({
|
|||
path,
|
||||
});
|
||||
},
|
||||
[embeddable, type]
|
||||
[embeddable, type, embeddableInput]
|
||||
);
|
||||
|
||||
const MaybeSavedObjectSaveModalDashboard = isDashboardAttachmentReady ? (
|
||||
<SavedObjectSaveModalDashboard
|
||||
objectType={objectType}
|
||||
documentInfo={{
|
||||
title: documentTitle,
|
||||
}}
|
||||
canSaveByReference={false}
|
||||
onClose={() => {
|
||||
setDashboardAttachmentReady(false);
|
||||
}}
|
||||
onSave={handleAttachToDashboardSave}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return { setDashboardAttachmentReady, MaybeSavedObjectSaveModalDashboard };
|
||||
};
|
||||
|
||||
export const AddToDashboard = ({
|
||||
type,
|
||||
asButton = false,
|
||||
}: {
|
||||
type: typeof SYNTHETICS_STATS_OVERVIEW_EMBEDDABLE | typeof SYNTHETICS_MONITORS_EMBEDDABLE;
|
||||
asButton?: boolean;
|
||||
}) => {
|
||||
const { setDashboardAttachmentReady, MaybeSavedObjectSaveModalDashboard } = useAddToDashboard({
|
||||
type,
|
||||
});
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
|
||||
const closePopover = () => {
|
||||
setIsPopoverOpen(false);
|
||||
};
|
||||
|
||||
const isSyntheticsApp = window.location.pathname.includes('/app/synthetics');
|
||||
|
||||
if (!isSyntheticsApp) {
|
||||
|
@ -123,26 +160,7 @@ export const AddToDashboard = ({
|
|||
/>
|
||||
</EuiPopover>
|
||||
)}
|
||||
{isDashboardAttachmentReady ? (
|
||||
<SavedObjectSaveModalDashboard
|
||||
objectType={i18n.translate(
|
||||
'xpack.synthetics.item.actions.addToDashboard.objectTypeLabel',
|
||||
{
|
||||
defaultMessage: 'Status Overview',
|
||||
}
|
||||
)}
|
||||
documentInfo={{
|
||||
title: i18n.translate('xpack.synthetics.item.actions.addToDashboard.attachmentTitle', {
|
||||
defaultMessage: 'Status Overview',
|
||||
}),
|
||||
}}
|
||||
canSaveByReference={false}
|
||||
onClose={() => {
|
||||
setDashboardAttachmentReady(false);
|
||||
}}
|
||||
onSave={handleAttachToDashboardSave}
|
||||
/>
|
||||
) : null}
|
||||
{MaybeSavedObjectSaveModalDashboard}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ import { FETCH_STATUS } from '@kbn/observability-shared-plugin/public';
|
|||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { SYNTHETICS_MONITORS_EMBEDDABLE } from '../../../../../embeddables/constants';
|
||||
import { useCreateSLO } from '../../hooks/use_create_slo';
|
||||
import { TEST_SCHEDULED_LABEL } from '../../../monitor_add_edit/form/run_test_btn';
|
||||
import { useCanUsePublicLocById } from '../../hooks/use_can_use_public_loc_id';
|
||||
|
@ -36,6 +37,7 @@ import { setFlyoutConfig } from '../../../../state/overview/actions';
|
|||
import { useEditMonitorLocator } from '../../../../hooks/use_edit_monitor_locator';
|
||||
import { useMonitorDetailLocator } from '../../../../hooks/use_monitor_detail_locator';
|
||||
import { NoPermissionsTooltip } from '../../../common/components/permissions';
|
||||
import { useAddToDashboard } from '../../../common/components/add_to_dashboard';
|
||||
|
||||
type PopoverPosition = 'relative' | 'default';
|
||||
|
||||
|
@ -178,6 +180,23 @@ export function ActionsPopover({
|
|||
},
|
||||
};
|
||||
|
||||
const { MaybeSavedObjectSaveModalDashboard, setDashboardAttachmentReady } = useAddToDashboard({
|
||||
type: SYNTHETICS_MONITORS_EMBEDDABLE,
|
||||
embeddableInput: {
|
||||
filters: {
|
||||
monitorIds: [{ label: monitor.name, value: monitor.configId }],
|
||||
tags: [],
|
||||
locations: [{ label: monitor.locationLabel, value: monitor.locationId }],
|
||||
monitorTypes: [],
|
||||
projects: [],
|
||||
},
|
||||
},
|
||||
documentTitle: `${monitor.name} - ${monitor.locationLabel}`,
|
||||
objectType: i18n.translate('xpack.synthetics.overview.actions.addToDashboard.objectTypeLabel', {
|
||||
defaultMessage: 'Monitor Overview',
|
||||
}),
|
||||
});
|
||||
|
||||
const alertLoading = alertStatus(monitor.configId) === FETCH_STATUS.LOADING;
|
||||
let popoverItems: EuiContextMenuPanelItemDescriptor[] = [
|
||||
{
|
||||
|
@ -291,6 +310,14 @@ export function ActionsPopover({
|
|||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: addMonitorToDashboardLabel,
|
||||
icon: 'dashboardApp',
|
||||
onClick: () => {
|
||||
setIsPopoverOpen(false);
|
||||
setDashboardAttachmentReady(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
if (isInspectView) popoverItems = popoverItems.filter((i) => i !== quickInspectPopoverItem);
|
||||
|
||||
|
@ -331,6 +358,7 @@ export function ActionsPopover({
|
|||
</EuiPopover>
|
||||
</Container>
|
||||
{CreateSLOFlyout}
|
||||
{MaybeSavedObjectSaveModalDashboard}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -421,6 +449,13 @@ const enableMonitorAlertLabel = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
const addMonitorToDashboardLabel = i18n.translate(
|
||||
'xpack.synthetics.overview.actions.addToDashboard',
|
||||
{
|
||||
defaultMessage: 'Add to dashboard',
|
||||
}
|
||||
);
|
||||
|
||||
const enabledSuccessLabel = (name: string) =>
|
||||
i18n.translate('xpack.synthetics.overview.actions.enabledSuccessLabel', {
|
||||
defaultMessage: 'Monitor "{name}" enabled successfully',
|
||||
|
|
|
@ -38,6 +38,7 @@ import { LocationsStatus, useStatusByLocation } from '../../../../hooks/use_stat
|
|||
import {
|
||||
getMonitorAction,
|
||||
selectMonitorUpsertStatus,
|
||||
selectOverviewState,
|
||||
selectServiceLocationsState,
|
||||
selectSyntheticsMonitor,
|
||||
selectSyntheticsMonitorError,
|
||||
|
@ -52,6 +53,7 @@ import { MonitorEnabled } from '../../management/monitor_list_table/monitor_enab
|
|||
import { ConfigKey, EncryptedSyntheticsMonitor, OverviewStatusMetaData } from '../types';
|
||||
import { ActionsPopover } from './actions_popover';
|
||||
import { FlyoutParamProps } from './types';
|
||||
import { quietFetchOverviewStatusAction } from '../../../../state/overview_status';
|
||||
|
||||
interface Props {
|
||||
configId: string;
|
||||
|
@ -209,7 +211,7 @@ function DetailedFlyoutHeader({
|
|||
|
||||
export function LoadingState() {
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" style={{ height: '100%' }}>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" css={{ height: '100%' }}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiFlexItem>
|
||||
|
@ -370,6 +372,34 @@ export function MonitorDetailFlyout(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export const MaybeMonitorDetailsFlyout = ({
|
||||
setFlyoutConfigCallback,
|
||||
}: {
|
||||
setFlyoutConfigCallback: (params: FlyoutParamProps) => void;
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { flyoutConfig, pageState } = useSelector(selectOverviewState);
|
||||
const hideFlyout = useCallback(() => dispatch(setFlyoutConfig(null)), [dispatch]);
|
||||
const forceRefreshCallback = useCallback(
|
||||
() => dispatch(quietFetchOverviewStatusAction.get({ pageState })),
|
||||
[dispatch, pageState]
|
||||
);
|
||||
|
||||
return flyoutConfig?.configId && flyoutConfig?.location ? (
|
||||
<MonitorDetailFlyout
|
||||
configId={flyoutConfig.configId}
|
||||
id={flyoutConfig.id}
|
||||
location={flyoutConfig.location}
|
||||
locationId={flyoutConfig.locationId}
|
||||
spaceId={flyoutConfig.spaceId}
|
||||
onClose={hideFlyout}
|
||||
onEnabledChange={forceRefreshCallback}
|
||||
onLocationChange={setFlyoutConfigCallback}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
const DURATION_HEADER_TEXT = i18n.translate('xpack.synthetics.monitorList.durationHeaderText', {
|
||||
defaultMessage: 'Duration',
|
||||
});
|
||||
|
|
|
@ -21,7 +21,6 @@ import {
|
|||
import { MetricItem } from './metric_item/metric_item';
|
||||
import { ShowAllSpaces } from '../../common/show_all_spaces';
|
||||
import { OverviewStatusMetaData } from '../../../../../../../common/runtime_types';
|
||||
import { quietFetchOverviewStatusAction } from '../../../../state/overview_status';
|
||||
import type { TrendRequest } from '../../../../../../../common/types';
|
||||
import { SYNTHETICS_MONITORS_EMBEDDABLE } from '../../../../../embeddables/constants';
|
||||
import { AddToDashboard } from '../../../common/components/add_to_dashboard';
|
||||
|
@ -39,9 +38,9 @@ import { OverviewLoader } from './overview_loader';
|
|||
import { OverviewPaginationInfo } from './overview_pagination_info';
|
||||
import { SortFields } from './sort_fields';
|
||||
import { NoMonitorsFound } from '../../common/no_monitors_found';
|
||||
import { MonitorDetailFlyout } from './monitor_detail_flyout';
|
||||
import { useSyntheticsRefreshContext } from '../../../../contexts';
|
||||
import { FlyoutParamProps } from './types';
|
||||
import { MaybeMonitorDetailsFlyout } from './monitor_detail_flyout';
|
||||
|
||||
const ITEM_HEIGHT = 172;
|
||||
const ROW_COUNT = 4;
|
||||
|
@ -61,7 +60,6 @@ export const OverviewGrid = memo(() => {
|
|||
const monitorsSortedByStatus: OverviewStatusMetaData[] = useMonitorsSortedByStatus();
|
||||
|
||||
const {
|
||||
flyoutConfig,
|
||||
pageState,
|
||||
groupBy: { field: groupField },
|
||||
} = useSelector(selectOverviewState);
|
||||
|
@ -77,12 +75,7 @@ export const OverviewGrid = memo(() => {
|
|||
(params: FlyoutParamProps) => dispatch(setFlyoutConfig(params)),
|
||||
[dispatch]
|
||||
);
|
||||
const hideFlyout = useCallback(() => dispatch(setFlyoutConfig(null)), [dispatch]);
|
||||
const { lastRefresh } = useSyntheticsRefreshContext();
|
||||
const forceRefreshCallback = useCallback(
|
||||
() => dispatch(quietFetchOverviewStatusAction.get({ pageState })),
|
||||
[dispatch, pageState]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (monitorsSortedByStatus.length) {
|
||||
|
@ -186,7 +179,7 @@ export const OverviewGrid = memo(() => {
|
|||
<EuiFlexGroup
|
||||
data-test-subj={`overview-grid-row-${listIndex}`}
|
||||
gutterSize="m"
|
||||
style={{ ...style }}
|
||||
css={{ ...style }}
|
||||
>
|
||||
{listData[listIndex].map((_, idx) => (
|
||||
<EuiFlexItem
|
||||
|
@ -252,18 +245,7 @@ export const OverviewGrid = memo(() => {
|
|||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
{flyoutConfig?.configId && flyoutConfig?.location && (
|
||||
<MonitorDetailFlyout
|
||||
configId={flyoutConfig.configId}
|
||||
id={flyoutConfig.id}
|
||||
location={flyoutConfig.location}
|
||||
locationId={flyoutConfig.locationId}
|
||||
spaceId={flyoutConfig.spaceId}
|
||||
onClose={hideFlyout}
|
||||
onEnabledChange={forceRefreshCallback}
|
||||
onLocationChange={setFlyoutConfigCallback}
|
||||
/>
|
||||
)}
|
||||
<MaybeMonitorDetailsFlyout setFlyoutConfigCallback={setFlyoutConfigCallback} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -6,15 +6,23 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiFlexGrid, EuiFlexItem, EuiFlexGridProps } from '@elastic/eui';
|
||||
import { OverviewGridItemLoader } from './overview_grid_item_loader';
|
||||
|
||||
export const OverviewLoader = ({ rows }: { rows?: number }) => {
|
||||
export const OverviewLoader = ({
|
||||
rows,
|
||||
columns,
|
||||
style,
|
||||
}: {
|
||||
rows?: number;
|
||||
columns?: EuiFlexGridProps['columns'];
|
||||
style?: { height: string };
|
||||
}) => {
|
||||
const ROWS = rows ?? 4;
|
||||
const COLUMNS = 4;
|
||||
const COLUMNS = columns ?? 4;
|
||||
const loaders = Array(ROWS * COLUMNS).fill(null);
|
||||
return (
|
||||
<EuiFlexGrid gutterSize="m" columns={COLUMNS}>
|
||||
<EuiFlexGrid gutterSize="m" columns={COLUMNS} css={style}>
|
||||
{loaders.map((_, i) => (
|
||||
<EuiFlexItem key={i}>
|
||||
<OverviewGridItemLoader />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue