From a5587031b110114ea3439d74deea9fa0b6ffe0ea Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Tue, 23 Jul 2024 16:51:00 +0300 Subject: [PATCH] AG filters (#4718) # What this PR does - Refactored filters to allow for easier customization part of `extraInformation` - Refactored filters to render with portals - Always initialize mandatory fields with default values - Added `TimeRangePickerWrapper` that mimics logic of the same selector used in Grafana from `@grafana/scenes`, specifically `ScenesTimePicker` ~~- Bumped all `@grafana/*` dependencies to latest version~~ (postponed to be done in a separate PR) ## Which issue(s) this PR closes Related to https://github.com/grafana/oncall/issues/4464 --- .../RemoteFilters/RemoteFilters.tsx | 131 +++++++++++++----- .../RemoteFilters/TimeRangePickerWrapper.tsx | 122 ++++++++++++++++ .../src/models/alertgroup/alertgroup.ts | 1 + .../src/models/filters/filters.types.ts | 13 ++ .../src/pages/incidents/Incidents.tsx | 127 ++++++++++++----- grafana-plugin/src/types.ts | 2 +- 6 files changed, 323 insertions(+), 73 deletions(-) create mode 100644 grafana-plugin/src/containers/RemoteFilters/TimeRangePickerWrapper.tsx diff --git a/grafana-plugin/src/containers/RemoteFilters/RemoteFilters.tsx b/grafana-plugin/src/containers/RemoteFilters/RemoteFilters.tsx index 61a83f9f..309f2a79 100644 --- a/grafana-plugin/src/containers/RemoteFilters/RemoteFilters.tsx +++ b/grafana-plugin/src/containers/RemoteFilters/RemoteFilters.tsx @@ -5,7 +5,6 @@ import { GrafanaTheme2, KeyValue, SelectableValue, TimeRange } from '@grafana/da import { InlineSwitch, MultiSelect, - TimeRangeInput, Select, LoadingPlaceholder, Input, @@ -19,33 +18,53 @@ import { capitalCase } from 'change-case'; import { debounce, isUndefined, omitBy, pickBy } from 'lodash-es'; import { observer } from 'mobx-react'; import moment from 'moment-timezone'; +import ReactDOM from 'react-dom'; import Emoji from 'react-emoji-render'; +import { RenderConditionally } from 'components/RenderConditionally/RenderConditionally'; import { Text } from 'components/Text/Text'; import { LabelsFilter } from 'containers/Labels/LabelsFilter'; import { RemoteSelect } from 'containers/RemoteSelect/RemoteSelect'; import { TeamName } from 'containers/TeamName/TeamName'; -import { FiltersValues } from 'models/filters/filters.types'; +import { FilterExtraInformation, FilterExtraInformationValues } from 'models/filters/filters.types'; import { GrafanaTeamStore } from 'models/grafana_team/grafana_team'; import { SelectOption, WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; import { LocationHelper } from 'utils/LocationHelper'; import { PAGE } from 'utils/consts'; import { convertTimerangeToFilterValue, getValueForDateRangeFilterType } from 'utils/datetime'; -import { allFieldsEmpty } from 'utils/utils'; import { parseFilters } from './RemoteFilters.helpers'; import { FilterOption } from './RemoteFilters.types'; +import { TimeRangePickerWrapper } from './TimeRangePickerWrapper'; interface RemoteFiltersProps extends WithStoreProps, Themeable2 { onChange: (filters: Record, isOnMount: boolean, invalidateFn: () => boolean) => void; query: KeyValue; page: PAGE; - defaultFilters?: FiltersValues; - extraFilters?: (state, setState, onFiltersValueChange) => React.ReactNode; grafanaTeamStore: GrafanaTeamStore; + extraInformation?: FilterExtraInformation; + extraFilters?: (state, setState, onFiltersValueChange) => React.ReactNode; skipFilterOptionFn?: (filterOption: FilterOption) => boolean; } + +export function filterExtraInformation(object: FilterExtraInformationValues): FilterExtraInformationValues { + const defaultValues: Partial = { + isClearable: true, + showInputLabel: true, + }; + + const result = { ...object }; + + Object.keys(defaultValues).forEach((key) => { + if (!result.hasOwnProperty(key)) { + result[key] = defaultValues[key]; + } + }); + + return result; +} + export interface RemoteFiltersState { filterOptions?: FilterOption[]; filters: FilterOption[]; @@ -86,12 +105,12 @@ class _RemoteFilters extends Component { query, page, store: { filtersStore }, - defaultFilters, skipFilterOptionFn, } = this.props; let filterOptions = await filtersStore.updateOptionsForPage(page); const currentTablePageNum = parseInt(filtersStore.currentTablePageNum[page] || query.p || 1, 10); + const defaultFilters = this.extractDefaultValuesFromExtraInformation(); if (skipFilterOptionFn) { filterOptions = filterOptions.filter((option: FilterOption) => !skipFilterOptionFn(option)); @@ -100,11 +119,11 @@ class _RemoteFilters extends Component { // set the current page from filters/query or default it to 1 filtersStore.setCurrentTablePageNum(page, currentTablePageNum); - let { filters, values } = parseFilters({ ...query, ...filtersStore.globalValues }, filterOptions, query); - - if (allFieldsEmpty(values)) { - ({ filters, values } = parseFilters(defaultFilters, filterOptions, query)); - } + let { filters, values } = parseFilters( + { ...defaultFilters, ...query, ...filtersStore.globalValues }, + filterOptions, + query + ); this.setState({ filterOptions, filters, values }, () => this.onChange(true)); } @@ -147,28 +166,8 @@ class _RemoteFilters extends Component { return (
- {filters.map((filterOption: FilterOption) => ( -
- - {filterOption.display_name || capitalCase(filterOption.name)} - {filterOption.description && ( - - - - - - )} - - {this.renderFilterOption(filterOption)} -
- ))} + {filters.map(this.renderFilterBlock)} +