forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Infrastructure UI] Host filtering controls (elastic#145935)
Closes [elastic#140445](elastic#140445) ## Summary This PR adds 2 filters (Operating System and Cloud Provider) using [Kibana Controls API](https://github.com/elastic/kibana/tree/main/src/plugins/controls) to the Host view. ## Testing - Open Host View - The Operating System and Cloud Provider filters should be visible under the search bar. Supported values: - Filter Include/exclude OS name / Cloud Provider name - Exist / Does not exist - Any (also when clearing the filters) - The control filters should update the possible values when the other control filter or unified search query/filters are changes - When the control group filters are updated the table is loading the filtered result. - Combination with unified search query/filters should be possible. ![image](https://user-images.githubusercontent.com/14139027/203373557-f9220f22-53ee-4fe0-9bdd-cdc08ce31156.png) - Copy the url after adding the filters and paste it into a separate tab - The control group AND the other filters/query should be prefilled ## 🎉 UPDATE the control panels are prefilled from the URL ### The Workaround: Together with @ThomThomson we found a way to prefill the control group selections from the URL state by adding the panels' objects to the URL state (using a separate key to avoid the infinite loop issue) and keeping the output filters (used for updating the table) separately. ## Discovered issues with persisting the new filters to the URL state ~~⚠️ This PR does not support persisting those filters in the URL state. The reason behind this is that if we persist those filters inside the other filters parameter it will create an infinite loop (as those controls are relying on the filters to adjust the possible values).~~ In order to avoid that we can persist them in a different parameter (instead of adding them to the existing `_a` we can add a new one for example named `controlFilters`. This will work with filtering the table results. BUT If we go with the solution to persist them in another `urlStateKey` we also need to prefill those selections from the url state to the control filters (Operating System and Cloud Provide). Currently, the controls API supports setting `selectedOptions` as a string array. ### Workaraound Ideas Option 1: I tried first on a[ separate branch ](elastic/kibana@main...jennypavlova:kibana:140445-host-filtering-controls-with-url-state) - Persist the filters as an array of filter options. - on load prefill the control filters - extract the string values from the filters and set them as `selectedOptions` inside the control group input `panel` (based on the field name for example) Option 2 (Suggestion from Devon ) - on load pass in the selections from the URL to the control group input - Don't render the table right away - Wait until control group is ready .then - Get the filters from the control group output - Set filters from controls in the use state by doing controls.getOutput().filters - Render the table with ...unifiedSearchFilters, ...filtersFromControls ❌ The issue with both 1 & 2 - With `selectedOptions` we can prefill only **strings** so `Exist` and `Negate` won't be supported
- Loading branch information
1 parent
8382355
commit a44304e
Showing
7 changed files
with
226 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
x-pack/plugins/infra/public/pages/metrics/hosts/components/controls_content.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/* | ||
* 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 { ControlGroupContainer, CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/public'; | ||
import { ViewMode } from '@kbn/embeddable-plugin/public'; | ||
import { Filter, TimeRange, compareFilters } from '@kbn/es-query'; | ||
import { isEqual } from 'lodash'; | ||
import { LazyControlsRenderer } from './lazy_controls_renderer'; | ||
import { useControlPanels } from '../hooks/use_control_panels_url_state'; | ||
|
||
interface Props { | ||
timeRange: TimeRange; | ||
dataViewId: string; | ||
filters: Filter[]; | ||
query: { | ||
language: string; | ||
query: string; | ||
}; | ||
setPanelFilters: React.Dispatch<React.SetStateAction<null | Filter[]>>; | ||
} | ||
|
||
// Disable refresh, allow our timerange changes to refresh the embeddable. | ||
const REFRESH_CONFIG = { | ||
pause: true, | ||
value: 0, | ||
}; | ||
|
||
export const ControlsContent: React.FC<Props> = ({ | ||
timeRange, | ||
dataViewId, | ||
query, | ||
filters, | ||
setPanelFilters, | ||
}) => { | ||
const [controlPanel, setControlPanels] = useControlPanels(dataViewId); | ||
const [controlGroup, setControlGroup] = useState<ControlGroupContainer | undefined>(); | ||
|
||
useEffect(() => { | ||
if (!controlGroup) { | ||
return; | ||
} | ||
if ( | ||
!isEqual(controlGroup.getInput().timeRange, timeRange) || | ||
!compareFilters(controlGroup.getInput().filters ?? [], filters) || | ||
!isEqual(controlGroup.getInput().query, query) | ||
) { | ||
controlGroup.updateInput({ | ||
timeRange, | ||
query, | ||
filters, | ||
}); | ||
} | ||
}, [query, filters, controlGroup, timeRange]); | ||
|
||
return ( | ||
<LazyControlsRenderer | ||
getCreationOptions={async ({ addDataControlFromField }) => ({ | ||
id: dataViewId, | ||
type: CONTROL_GROUP_TYPE, | ||
timeRange, | ||
refreshConfig: REFRESH_CONFIG, | ||
viewMode: ViewMode.VIEW, | ||
filters: [...filters], | ||
query, | ||
chainingSystem: 'HIERARCHICAL', | ||
controlStyle: 'oneLine', | ||
defaultControlWidth: 'small', | ||
panels: controlPanel, | ||
})} | ||
onEmbeddableLoad={(newControlGroup) => { | ||
setControlGroup(newControlGroup); | ||
newControlGroup.onFiltersPublished$.subscribe((newFilters) => { | ||
setPanelFilters([...newFilters]); | ||
}); | ||
newControlGroup.getInput$().subscribe(({ panels, filters: currentFilters }) => { | ||
setControlPanels(panels); | ||
if (currentFilters?.length === 0) { | ||
setPanelFilters([]); | ||
} | ||
}); | ||
}} | ||
/> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
x-pack/plugins/infra/public/pages/metrics/hosts/components/lazy_controls_renderer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/* | ||
* 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 { LazyControlGroupRenderer } from '@kbn/controls-plugin/public'; | ||
import { EuiLoadingSpinner, EuiErrorBoundary } from '@elastic/eui'; | ||
import React from 'react'; | ||
|
||
export const LazyControlsRenderer = ( | ||
props: React.ComponentProps<typeof LazyControlGroupRenderer> | ||
) => ( | ||
<EuiErrorBoundary> | ||
<React.Suspense fallback={<EuiLoadingSpinner />}> | ||
<LazyControlGroupRenderer {...props} /> | ||
</React.Suspense> | ||
</EuiErrorBoundary> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_control_panels_url_state.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* | ||
* 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 * as rt from 'io-ts'; | ||
import { pipe } from 'fp-ts/lib/pipeable'; | ||
import { fold } from 'fp-ts/lib/Either'; | ||
import { constant, identity } from 'fp-ts/lib/function'; | ||
import { ControlGroupInput } from '@kbn/controls-plugin/common'; | ||
import { useUrlState } from '../../../../utils/use_url_state'; | ||
|
||
export const getDefaultPanels = (dataViewId: string): ControlGroupInput['panels'] => | ||
({ | ||
osPanel: { | ||
order: 0, | ||
width: 'medium', | ||
grow: false, | ||
type: 'optionsListControl', | ||
explicitInput: { | ||
id: 'osPanel', | ||
dataViewId, | ||
fieldName: 'host.os.name', | ||
title: 'Operating System', | ||
}, | ||
}, | ||
cloudProviderPanel: { | ||
order: 1, | ||
width: 'medium', | ||
grow: false, | ||
type: 'optionsListControl', | ||
explicitInput: { | ||
id: 'cloudProviderPanel', | ||
dataViewId, | ||
fieldName: 'cloud.provider', | ||
title: 'Cloud Provider', | ||
}, | ||
}, | ||
} as unknown as ControlGroupInput['panels']); | ||
const HOST_FILTERS_URL_STATE_KEY = 'controlPanels'; | ||
|
||
export const useControlPanels = (dataViewId: string) => { | ||
return useUrlState<ControlPanels>({ | ||
defaultState: getDefaultPanels(dataViewId), | ||
decodeUrlState, | ||
encodeUrlState, | ||
urlStateKey: HOST_FILTERS_URL_STATE_KEY, | ||
}); | ||
}; | ||
|
||
const PanelRT = rt.type({ | ||
order: rt.number, | ||
width: rt.union([rt.literal('medium'), rt.literal('small'), rt.literal('large')]), | ||
grow: rt.boolean, | ||
type: rt.string, | ||
explicitInput: rt.intersection([ | ||
rt.type({ id: rt.string }), | ||
rt.partial({ | ||
dataViewId: rt.string, | ||
fieldName: rt.string, | ||
title: rt.union([rt.string, rt.undefined]), | ||
}), | ||
]), | ||
}); | ||
|
||
const ControlPanelRT = rt.record(rt.string, PanelRT); | ||
|
||
type ControlPanels = rt.TypeOf<typeof ControlPanelRT>; | ||
const encodeUrlState = ControlPanelRT.encode; | ||
const decodeUrlState = (value: unknown) => { | ||
if (value) { | ||
return pipe(ControlPanelRT.decode(value), fold(constant({}), identity)); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters