[Infra] Refactor routes context providers and inventory alert flyout (#177189)

closes [144387](https://github.com/elastic/kibana/issues/144387)

## Summary

This PR changes the context Waffle context providers usage, so that they
are only used in the Inventory UI page. Therefore, removes the
`waffleTime`, `waffleOptions` and `waffleFilters` query parameters from
other Infrastructure pages:

Hosts View route example:

**Before**
```
https://edge-oblt.kb.us-west2.gcp.elastic-cloud.com/app/metrics/hosts?
  waffleFilter=(expression:%27%27,kind:kuery)&
  waffleTime=(currentTime:1708347453400,isAutoReloading:!f)&
  waffleOptions=(accountId:%27%27,autoBounds:!t,boundsOverride:(max:1,min:0),customMetrics:!(),customOptions:!(),groupBy:!(),legend:(palette:cool,reverseColors:!f,steps:10),metric:(type:cpu),nodeType:host,region:%27%27,sort:(by:name,direction:desc),source:default,timelineOpen:!f,view:map)&
  _a=(dateRange:(from:now-15m,to:now),filters:!(),limit:500,panelFilters:!(),query:(language:kuery,query:%27%27))&
  controlPanels=(cloud.provider:(explicitInput:(fieldName:cloud.provider,id:cloud.provider,title:%27Cloud%20Provider%27),grow:!f,order:1,type:optionsListControl,width:medium),host.os.name:(explicitInput:(fieldName:host.os.name,id:host.os.name,title:%27Operating%20System%27),grow:!f,order:0,type:optionsListControl,width:medium),service.name:(explicitInput:(fieldName:service.name,id:service.name,title:%27Service%20Name%27),grow:!f,order:2,type:optionsListControl,width:medium))
```


**Now**
```
http://localhost:5601/ftw/app/metrics/hosts?
  _a=(dateRange:(from:now-15m,to:now),filters:!(),limit:100,panelFilters:!(),query:(language:kuery,query:%27%27))&
  controlPanels=(cloud.provider:(explicitInput:(fieldName:cloud.provider,id:cloud.provider,title:%27Cloud%20Provider%27),grow:!f,order:1,type:optionsListControl,width:medium),host.os.name:(explicitInput:(fieldName:host.os.name,id:host.os.name,title:%27Operating%20System%27),grow:!f,order:0,type:optionsListControl,width:medium),service.name:(explicitInput:(fieldName:service.name,id:service.name,title:%27Service%20Name%27),grow:!f,order:2,type:optionsListControl,width:medium))
```

**NOTE**: I had to refactor some alerting components because they were
depending on the `WaffleOptions` context to retrieve some properties
that are only relevant within the Inventory UI context

### How to test

- Start a local Kibana instance
- Navigate to the pages below and confirm that they work. `waffle` query
variables only exist in the Inventory UI
  - Inventory
    - Navigate to Pod details 
    - Create and load Saved Views
    - Create Inventory and Metrics alerts
  - Metrics
    - Create and load Saved Views
    - Create Inventory and Metrics alerts
  - Hosts View
    - Navigate Hosts Details

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Carlos Crespo 2024-02-22 15:32:03 +01:00 committed by GitHub
parent bc488fb93e
commit 5a3f0a0da9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 167 additions and 119 deletions

View file

@ -23,10 +23,10 @@ interface Props {
export const AlertFlyout = ({ options, nodeType, filter, visible, setVisible }: Props) => {
const { triggersActionsUI } = useContext(TriggerActionsContext);
const { inventoryPrefill } = useAlertPrefillContext();
const { customMetrics = [] } = inventoryPrefill ?? {};
const onCloseFlyout = useCallback(() => setVisible(false), [setVisible]);
const { inventoryPrefill } = useAlertPrefillContext();
const { customMetrics = [], accountId, region } = inventoryPrefill;
const AddAlertFlyout = useMemo(
() =>
triggersActionsUI &&
@ -36,10 +36,12 @@ export const AlertFlyout = ({ options, nodeType, filter, visible, setVisible }:
canChangeTrigger: false,
ruleTypeId: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
metadata: {
accountId,
options,
nodeType,
filter,
customMetrics,
region,
},
useRuleProducer: true,
}),

View file

@ -70,6 +70,8 @@ import { NodeTypeExpression } from './node_type';
const FILTER_TYPING_DEBOUNCE_MS = 500;
export interface AlertContextMeta {
accountId?: string;
region?: string;
options?: Partial<InfraWaffleMapOptions>;
nodeType?: InventoryItemType;
filter?: string;
@ -86,6 +88,8 @@ type Props = Omit<
filterQueryText?: string;
sourceId: string;
alertOnNoData?: boolean;
accountId?: string;
region?: string;
},
AlertContextMeta
>,
@ -309,6 +313,8 @@ export const Expressions: React.FC<Props> = (props) => {
filterQuery={ruleParams.filterQuery}
nodeType={ruleParams.nodeType}
sourceId={ruleParams.sourceId}
accountId={ruleParams.accountId}
region={ruleParams.region}
data-test-subj="preview-chart"
/>
</ExpressionRow>

View file

@ -17,7 +17,6 @@ import { InventoryMetricConditions } from '../../../../common/alerting/metrics';
import { Color } from '../../../../common/color_palette';
import { MetricsExplorerAggregation, MetricsExplorerRow } from '../../../../common/http_api';
import { useSnapshot } from '../../../pages/metrics/inventory_view/hooks/use_snaphot';
import { useWaffleOptionsContext } from '../../../pages/metrics/inventory_view/hooks/use_waffle_options';
import { createInventoryMetricFormatter } from '../../../pages/metrics/inventory_view/lib/create_inventory_metric_formatter';
import { calculateDomain } from '../../../pages/metrics/metrics_explorer/components/helpers/calculate_domain';
import { getMetricId } from '../../../pages/metrics/metrics_explorer/components/helpers/get_metric_id';
@ -37,6 +36,8 @@ interface Props {
filterQuery?: string | symbol;
nodeType: InventoryItemType;
sourceId: string;
accountId?: string;
region?: string;
}
export const ExpressionChart: React.FC<Props> = ({
@ -44,6 +45,8 @@ export const ExpressionChart: React.FC<Props> = ({
filterQuery,
nodeType,
sourceId,
accountId = '',
region = '',
}) => {
const chartTheme = useTimelineChartTheme();
const timerange = useMemo(
@ -63,7 +66,6 @@ export const ExpressionChart: React.FC<Props> = ({
type: 'custom' as SnapshotMetricType,
});
const options = useWaffleOptionsContext();
const { loading, nodes } = useSnapshot({
filterQuery,
metrics:
@ -74,8 +76,8 @@ export const ExpressionChart: React.FC<Props> = ({
nodeType,
sourceId,
currentTime: 0,
accountId: options.accountId,
region: options.region,
accountId,
region,
timerange,
});

View file

@ -17,15 +17,23 @@ export const useInventoryAlertPrefill = () => {
const [filterQuery, setFilterQuery] = useState<string | undefined>();
const [metric, setMetric] = useState<SnapshotMetricInput>({ type: 'cpu' });
const [customMetrics, setCustomMetrics] = useState<SnapshotCustomMetricInput[]>([]);
// only shows for AWS when there are regions info
const [region, setRegion] = useState('');
// only shows for AWS when there are accounts info
const [accountId, setAccountId] = useState('');
return {
nodeType,
filterQuery,
metric,
customMetrics,
accountId,
region,
setAccountId,
setNodeType,
setFilterQuery,
setMetric,
setCustomMetrics,
setRegion,
};
};

View file

@ -17,7 +17,6 @@ import { InfraPublicConfig } from '../../common/plugin_config_types';
import { LinkToMetricsPage } from '../pages/link_to/link_to_metrics';
import { InfrastructurePage } from '../pages/metrics';
import { InfraClientStartDeps, InfraClientStartExports } from '../types';
import { RedirectWithQueryParams } from '../utils/redirect_with_query_params';
import { CommonInfraProviders, CoreProviders } from './common_providers';
import { prepareMountElement } from './common_styles';
import { SourceProvider } from '../containers/metrics_source';
@ -104,15 +103,6 @@ const MetricsApp: React.FC<{
<Router history={history}>
<Routes>
<Route path="/link-to" component={LinkToMetricsPage} />
{uiCapabilities?.infrastructure?.show && (
<RedirectWithQueryParams from="/" exact={true} to="/inventory" />
)}
{uiCapabilities?.infrastructure?.show && (
<RedirectWithQueryParams from="/snapshot" exact={true} to="/inventory" />
)}
{uiCapabilities?.infrastructure?.show && (
<RedirectWithQueryParams from="/metrics-explorer" exact={true} to="/explorer" />
)}
{uiCapabilities?.infrastructure?.show && (
<Route path="/" component={InfrastructurePage} />
)}

View file

@ -31,9 +31,6 @@ import { SnapshotPage } from './inventory_view';
import { NodeDetail } from './metric_detail';
import { MetricsSettingsPage } from './settings';
import { SourceLoadingPage } from '../../components/source_loading_page';
import { WaffleOptionsProvider } from './inventory_view/hooks/use_waffle_options';
import { WaffleTimeProvider } from './inventory_view/hooks/use_waffle_time';
import { WaffleFiltersProvider } from './inventory_view/hooks/use_waffle_filters';
import { MetricsAlertDropdown } from '../../alerting/common/components/metrics_alert_dropdown';
import { AlertPrefillProvider } from '../../alerting/use_alert_prefill';
import { InfraMLCapabilitiesProvider } from '../../containers/ml/infra_ml_capabilities';
@ -44,6 +41,7 @@ import { NotFoundPage } from '../404';
import { ReactQueryProvider } from '../../containers/react_query_provider';
import { usePluginConfig } from '../../containers/plugin_config_context';
import { HostsPage } from './hosts';
import { RedirectWithQueryParams } from '../../utils/redirect_with_query_params';
const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLabel', {
defaultMessage: 'Add data',
@ -75,77 +73,79 @@ export const InfrastructurePage = () => {
return (
<EuiErrorBoundary>
<AlertPrefillProvider>
<WaffleOptionsProvider>
<WaffleTimeProvider>
<WaffleFiltersProvider>
<ReactQueryProvider>
<InfraMLCapabilitiesProvider>
<HelpCenterContent
feedbackLink="https://discuss.elastic.co/c/metrics"
appName={i18n.translate('xpack.infra.header.infrastructureHelpAppName', {
defaultMessage: 'Metrics',
})}
/>
{setHeaderActionMenu && theme$ && (
<HeaderMenuPortal setHeaderActionMenu={setHeaderActionMenu} theme$={theme$}>
<EuiFlexGroup responsive={false} gutterSize="s">
<EuiFlexItem>
<EuiHeaderLinks gutterSize="xs">
<EuiHeaderLink color={'text'} {...settingsLinkProps}>
{settingsTabTitle}
</EuiHeaderLink>
<Route path={'/inventory'} component={AnomalyDetectionFlyout} />
{config.featureFlags.alertsAndRulesDropdownEnabled && (
<MetricsAlertDropdown />
)}
<EuiHeaderLink
href={kibana.services?.application?.getUrlForApp(
'/integrations/browse'
)}
color="primary"
iconType="indexOpen"
>
{ADD_DATA_LABEL}
</EuiHeaderLink>
</EuiHeaderLinks>
</EuiFlexItem>
{ObservabilityAIAssistantActionMenuItem ? (
<EuiFlexItem>
<ObservabilityAIAssistantActionMenuItem />
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</HeaderMenuPortal>
<ReactQueryProvider>
<AlertPrefillProvider>
<InfraMLCapabilitiesProvider>
<HelpCenterContent
feedbackLink="https://discuss.elastic.co/c/metrics"
appName={i18n.translate('xpack.infra.header.infrastructureHelpAppName', {
defaultMessage: 'Metrics',
})}
/>
{setHeaderActionMenu && theme$ && (
<HeaderMenuPortal setHeaderActionMenu={setHeaderActionMenu} theme$={theme$}>
<EuiFlexGroup responsive={false} gutterSize="s">
<EuiFlexItem>
<EuiHeaderLinks gutterSize="xs">
<EuiHeaderLink color={'text'} {...settingsLinkProps}>
{settingsTabTitle}
</EuiHeaderLink>
<Route path={'/inventory'} component={AnomalyDetectionFlyout} />
{config.featureFlags.alertsAndRulesDropdownEnabled && (
<MetricsAlertDropdown />
)}
<EuiHeaderLink
href={kibana.services?.application?.getUrlForApp('/integrations/browse')}
color="primary"
iconType="indexOpen"
>
{ADD_DATA_LABEL}
</EuiHeaderLink>
</EuiHeaderLinks>
</EuiFlexItem>
{ObservabilityAIAssistantActionMenuItem ? (
<EuiFlexItem>
<ObservabilityAIAssistantActionMenuItem />
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</HeaderMenuPortal>
)}
<Routes>
<Route path="/inventory" component={SnapshotPage} />
{config.featureFlags.metricsExplorerEnabled && (
<Route
path="/explorer"
render={() => (
<MetricsExplorerOptionsContainer>
<WithMetricsExplorerOptionsUrlState />
{source?.configuration ? (
<PageContent
configuration={source.configuration}
createDerivedIndexPattern={createDerivedIndexPattern}
/>
) : (
<SourceLoadingPage />
)}
</MetricsExplorerOptionsContainer>
)}
<Routes>
<Route path={'/inventory'} component={SnapshotPage} />
{config.featureFlags.metricsExplorerEnabled && (
<Route path={'/explorer'}>
<MetricsExplorerOptionsContainer>
<WithMetricsExplorerOptionsUrlState />
{source?.configuration ? (
<PageContent
configuration={source.configuration}
createDerivedIndexPattern={createDerivedIndexPattern}
/>
) : (
<SourceLoadingPage />
)}
</MetricsExplorerOptionsContainer>
</Route>
)}
<Route path="/detail/:type/:node" component={NodeDetail} />
{isHostsViewEnabled && <Route path={'/hosts'} component={HostsPage} />}
<Route path={'/settings'} component={MetricsSettingsPage} />
<Route render={() => <NotFoundPage title="Infrastructure" />} />
</Routes>
</InfraMLCapabilitiesProvider>
</ReactQueryProvider>
</WaffleFiltersProvider>
</WaffleTimeProvider>
</WaffleOptionsProvider>
</AlertPrefillProvider>
/>
)}
<Route path="/detail/:type/:node" component={NodeDetail} />
{isHostsViewEnabled && <Route path="/hosts" component={HostsPage} />}
<Route path="/settings" component={MetricsSettingsPage} />
<RedirectWithQueryParams from="/snapshot" exact to="/inventory" />
<RedirectWithQueryParams from="/metrics-explorer" exact to="/explorer" />
<RedirectWithQueryParams from="/" exact to="/inventory" />
<Route render={() => <NotFoundPage title="Infrastructure" />} />
</Routes>
</InfraMLCapabilitiesProvider>
</AlertPrefillProvider>
</ReactQueryProvider>
</EuiErrorBoundary>
);
};

View file

@ -22,6 +22,8 @@ jest.mock('react-router-dom', () => ({
let PREFILL_NODETYPE: WaffleOptionsState['nodeType'] | undefined;
let PREFILL_METRIC: WaffleOptionsState['metric'] | undefined;
let PREFILL_CUSTOM_METRICS: WaffleOptionsState['customMetrics'] | undefined;
let PREFILL_ACCOUNT_ID: WaffleOptionsState['accountId'] | undefined;
let PREFILL_REGION: WaffleOptionsState['region'] | undefined;
jest.mock('../../../../alerting/use_alert_prefill', () => ({
useAlertPrefillContext: () => ({
inventoryPrefill: {
@ -34,6 +36,12 @@ jest.mock('../../../../alerting/use_alert_prefill', () => ({
setCustomMetrics(customMetrics: WaffleOptionsState['customMetrics']) {
PREFILL_CUSTOM_METRICS = customMetrics;
},
setAccountId(accountId: WaffleOptionsState['accountId']) {
PREFILL_ACCOUNT_ID = accountId;
},
setRegion(region: WaffleOptionsState['region']) {
PREFILL_REGION = region;
},
},
}),
}));
@ -45,6 +53,8 @@ describe('useWaffleOptions', () => {
PREFILL_NODETYPE = undefined;
PREFILL_METRIC = undefined;
PREFILL_CUSTOM_METRICS = undefined;
PREFILL_ACCOUNT_ID = undefined;
PREFILL_REGION = undefined;
});
it('should sync the options to the inventory alert preview context', () => {
@ -61,6 +71,8 @@ describe('useWaffleOptions', () => {
field: 'hey.system.are.you.good',
},
],
accountId: '123456789012',
region: 'us-east-1',
} as WaffleOptionsState;
act(() => {
result.current.changeNodeType(newOptions.nodeType);
@ -77,5 +89,15 @@ describe('useWaffleOptions', () => {
});
rerender();
expect(PREFILL_CUSTOM_METRICS).toEqual(newOptions.customMetrics);
act(() => {
result.current.changeAccount(newOptions.accountId);
});
rerender();
expect(PREFILL_ACCOUNT_ID).toEqual(newOptions.accountId);
act(() => {
result.current.changeRegion(newOptions.region);
});
rerender();
expect(PREFILL_REGION).toEqual(newOptions.region);
});
});

View file

@ -131,10 +131,14 @@ export const useWaffleOptions = () => {
const { inventoryPrefill } = useAlertPrefillContext();
useEffect(() => {
const { setNodeType, setMetric, setCustomMetrics } = inventoryPrefill;
const { setNodeType, setMetric, setCustomMetrics, setAccountId, setRegion } = inventoryPrefill;
setNodeType(state.nodeType);
setMetric(state.metric);
setCustomMetrics(state.customMetrics);
// only shows for AWS when there are accounts info
setAccountId(state.accountId);
// only shows for AWS when there are regions info
setRegion(state.region);
}, [state, inventoryPrefill]);
const changeTimelineOpen = useCallback(

View file

@ -23,6 +23,9 @@ import { SnapshotContainer } from './components/snapshot_container';
import { fullHeightContentStyles } from '../../../page_template.styles';
import { SurveySection } from './components/survey_section';
import { NoRemoteCluster } from '../../../components/empty_states';
import { WaffleOptionsProvider } from './hooks/use_waffle_options';
import { WaffleTimeProvider } from './hooks/use_waffle_time';
import { WaffleFiltersProvider } from './hooks/use_waffle_filters';
export const SnapshotPage = () => {
const { isLoading, loadSourceFailureMessage, loadSource, source } = useSourceContext();
@ -55,32 +58,43 @@ export const SnapshotPage = () => {
return (
<EuiErrorBoundary>
<div className={APP_WRAPPER_CLASS}>
<MetricsPageTemplate
hasData={metricIndicesExist}
pageHeader={{
pageTitle: inventoryTitle,
rightSideItems: [<SavedViews />, <SurveySection />],
}}
pageSectionProps={{
contentProps: {
css: css`
${fullHeightContentStyles};
padding-bottom: 0;
`,
},
}}
>
<SnapshotContainer
render={({ loading, nodes, reload, interval }) => (
<>
<FilterBar interval={interval} />
<LayoutView loading={loading} nodes={nodes} reload={reload} interval={interval} />
</>
)}
/>
</MetricsPageTemplate>
</div>
<WaffleOptionsProvider>
<WaffleTimeProvider>
<WaffleFiltersProvider>
<div className={APP_WRAPPER_CLASS}>
<MetricsPageTemplate
hasData={metricIndicesExist}
pageHeader={{
pageTitle: inventoryTitle,
rightSideItems: [<SavedViews />, <SurveySection />],
}}
pageSectionProps={{
contentProps: {
css: css`
${fullHeightContentStyles};
padding-bottom: 0;
`,
},
}}
>
<SnapshotContainer
render={({ loading, nodes, reload, interval }) => (
<>
<FilterBar interval={interval} />
<LayoutView
loading={loading}
nodes={nodes}
reload={reload}
interval={interval}
/>
</>
)}
/>
</MetricsPageTemplate>
</div>
</WaffleFiltersProvider>
</WaffleTimeProvider>
</WaffleOptionsProvider>
</EuiErrorBoundary>
);
};

View file

@ -339,8 +339,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('Should redirect to Node Details page', async () => {
await pageObjects.infraHome.goToTime(DATE_WITH_POD_WITH_DATA);
await pageObjects.infraHome.goToPods();
await pageObjects.infraHome.goToTime(DATE_WITH_POD_WITH_DATA);
await pageObjects.infraHome.clickOnFirstNode();
await pageObjects.infraHome.clickOnGoToNodeDetails();

View file

@ -97,8 +97,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('Should redirect to Node Details page', async () => {
await pageObjects.infraHome.goToTime(DATE_WITH_POD_WITH_DATA);
await pageObjects.infraHome.goToPods();
await pageObjects.infraHome.goToTime(DATE_WITH_POD_WITH_DATA);
await pageObjects.infraHome.clickOnFirstNode();
await pageObjects.infraHome.clickOnGoToNodeDetails();