mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[Logs+] Add Filter Control Customization Point (#162013)
closes https://github.com/elastic/kibana/issues/158561
## 📝 Summary
This PR adds a new customization point to allow for prepending custom
filter controls to the search bar.
At the moment we are only showing a default namespace filter, once this
is ready we will then check how to provide curated filters per
integration.
## ✅ Testing
1. Make sure to have some documents to different data sets with
different namespace, you can use [this
document](https://www.elastic.co/guide/en/elasticsearch/reference/current/set-up-a-data-stream.html)
as an example
2. Navigate to Discover with the log-explorer profile, `/p/log-explorer`
3. Validate that the new filter control is there
4. Filter using this new control and make sure documents are filtered
out
## 🎥 Demo
6828f62f
-dd09-42bd-930c-dd7eaf94958b
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
10c09d140a
commit
0c9afa1442
34 changed files with 819 additions and 52 deletions
|
@ -6,6 +6,6 @@
|
|||
"id": "discoverCustomizationExamples",
|
||||
"server": false,
|
||||
"browser": true,
|
||||
"requiredPlugins": ["developerExamples", "discover"]
|
||||
"requiredPlugins": ["controls", "developerExamples", "discover", "embeddable", "kibanaUtils"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,10 @@ import { noop } from 'lodash';
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { AwaitingControlGroupAPI, ControlGroupRenderer } from '@kbn/controls-plugin/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import type { ControlsPanels } from '@kbn/controls-plugin/common';
|
||||
import image from './discover_customization_examples.png';
|
||||
|
||||
export interface DiscoverCustomizationExamplesSetupPlugins {
|
||||
|
@ -228,6 +232,166 @@ export class DiscoverCustomizationExamplesPlugin implements Plugin {
|
|||
},
|
||||
});
|
||||
|
||||
customizations.set({
|
||||
id: 'search_bar',
|
||||
CustomDataViewPicker: () => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const togglePopover = () => setIsPopoverOpen((open) => !open);
|
||||
const closePopover = () => setIsPopoverOpen(false);
|
||||
const [savedSearches, setSavedSearches] = useState<
|
||||
Array<SimpleSavedObject<{ title: string }>>
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
core.savedObjects.client
|
||||
.find<{ title: string }>({ type: 'search' })
|
||||
.then((response) => {
|
||||
setSavedSearches(response.savedObjects);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const currentSavedSearch = useObservable(
|
||||
stateContainer.savedSearchState.getCurrent$(),
|
||||
stateContainer.savedSearchState.getState()
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButton
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
fullWidth
|
||||
onClick={togglePopover}
|
||||
data-test-subj="logsViewSelectorButton"
|
||||
>
|
||||
{currentSavedSearch.title ?? 'None selected'}
|
||||
</EuiButton>
|
||||
}
|
||||
anchorClassName="eui-fullWidth"
|
||||
isOpen={isPopoverOpen}
|
||||
panelPaddingSize="none"
|
||||
closePopover={closePopover}
|
||||
>
|
||||
<EuiContextMenu
|
||||
size="s"
|
||||
initialPanelId={0}
|
||||
panels={[
|
||||
{
|
||||
id: 0,
|
||||
title: 'Saved logs views',
|
||||
items: savedSearches.map((savedSearch) => ({
|
||||
name: savedSearch.get('title'),
|
||||
onClick: () => stateContainer.actions.onOpenSavedSearch(savedSearch.id),
|
||||
icon: savedSearch.id === currentSavedSearch.id ? 'check' : 'empty',
|
||||
'data-test-subj': `logsViewSelectorOption-${savedSearch.attributes.title.replace(
|
||||
/[^a-zA-Z0-9]/g,
|
||||
''
|
||||
)}`,
|
||||
})),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
},
|
||||
PrependFilterBar: () => {
|
||||
const [controlGroupAPI, setControlGroupAPI] = useState<AwaitingControlGroupAPI>();
|
||||
const stateStorage = stateContainer.stateStorage;
|
||||
const dataView = useObservable(
|
||||
stateContainer.internalState.state$,
|
||||
stateContainer.internalState.getState()
|
||||
).dataView;
|
||||
|
||||
useEffect(() => {
|
||||
if (!controlGroupAPI) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stateSubscription = stateStorage
|
||||
.change$<ControlsPanels>('controlPanels')
|
||||
.subscribe((panels) =>
|
||||
controlGroupAPI.updateInput({ panels: panels ?? undefined })
|
||||
);
|
||||
|
||||
const inputSubscription = controlGroupAPI.getInput$().subscribe((input) => {
|
||||
if (input && input.panels) stateStorage.set('controlPanels', input.panels);
|
||||
});
|
||||
|
||||
const filterSubscription = controlGroupAPI.onFiltersPublished$.subscribe(
|
||||
(newFilters) => {
|
||||
stateContainer.internalState.transitions.setCustomFilters(newFilters);
|
||||
stateContainer.actions.fetchData();
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
stateSubscription.unsubscribe();
|
||||
inputSubscription.unsubscribe();
|
||||
filterSubscription.unsubscribe();
|
||||
};
|
||||
}, [controlGroupAPI, stateStorage]);
|
||||
|
||||
const fieldToFilterOn = dataView?.fields.filter((field) =>
|
||||
field.esTypes?.includes('keyword')
|
||||
)[0];
|
||||
|
||||
if (!fieldToFilterOn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexItem
|
||||
data-test-subj="customPrependedFilter"
|
||||
grow={false}
|
||||
css={css`
|
||||
.controlGroup {
|
||||
min-height: unset;
|
||||
}
|
||||
|
||||
.euiFormLabel {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
line-height: 32px !important;
|
||||
}
|
||||
|
||||
.euiFormControlLayout {
|
||||
height: 32px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<ControlGroupRenderer
|
||||
ref={setControlGroupAPI}
|
||||
getCreationOptions={async (initialInput, builder) => {
|
||||
const panels = stateStorage.get<ControlsPanels>('controlPanels');
|
||||
|
||||
if (!panels) {
|
||||
await builder.addOptionsListControl(initialInput, {
|
||||
dataViewId: dataView?.id!,
|
||||
title: fieldToFilterOn.name.split('.')[0],
|
||||
fieldName: fieldToFilterOn.name,
|
||||
grow: false,
|
||||
width: 'small',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
initialInput: {
|
||||
...initialInput,
|
||||
panels: panels ?? initialInput.panels,
|
||||
viewMode: ViewMode.VIEW,
|
||||
filters: stateContainer.appState.get().filters ?? [],
|
||||
},
|
||||
};
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Cleaning up Logs explorer customizations');
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
"@kbn/core",
|
||||
"@kbn/discover-plugin",
|
||||
"@kbn/developer-examples-plugin",
|
||||
"@kbn/controls-plugin",
|
||||
"@kbn/embeddable-plugin",
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue