[Actionable Observability] consume tags filter (#131220)

* consume tags filter

* add getRuleTagFilter to the observability start mock

* use loadRuleTags instead of loadRuleAggregations

* add an error when rule tag fail to load

* load tags errors in a useEffect

* fix failing unit tests

* load rule errors in a useEffect

* stop loading loadRuleTagsAggs twice

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
mgiota 2022-05-13 09:03:08 +02:00 committed by GitHub
parent e474e9460d
commit 1cc8c38a98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 84 additions and 20 deletions

View file

@ -7,9 +7,9 @@
import { useEffect, useState, useCallback } from 'react';
import { isEmpty } from 'lodash';
import { loadRules } from '@kbn/triggers-actions-ui-plugin/public';
import { RULES_LOAD_ERROR } from '../pages/rules/translations';
import { FetchRulesProps, RuleState } from '../pages/rules/types';
import { loadRules, loadRuleTags } from '@kbn/triggers-actions-ui-plugin/public';
import { RULES_LOAD_ERROR, RULE_TAGS_LOAD_ERROR } from '../pages/rules/translations';
import { FetchRulesProps, RuleState, TagsState } from '../pages/rules/types';
import { OBSERVABILITY_RULE_TYPES } from '../pages/rules/config';
import { useKibana } from '../utils/kibana_react';
@ -18,6 +18,7 @@ export function useFetchRules({
ruleLastResponseFilter,
ruleStatusesFilter,
typesFilter,
tagsFilter,
setPage,
page,
sort,
@ -33,6 +34,23 @@ export function useFetchRules({
const [noData, setNoData] = useState<boolean>(true);
const [initialLoad, setInitialLoad] = useState<boolean>(true);
const [tagsState, setTagsState] = useState<TagsState>({
data: [],
error: null,
});
const loadRuleTagsAggs = useCallback(async () => {
try {
const ruleTagsAggs = await loadRuleTags({
http,
});
if (ruleTagsAggs?.ruleTags) {
setTagsState({ data: ruleTagsAggs.ruleTags, error: null });
}
} catch (e) {
setTagsState((oldState: TagsState) => ({ ...oldState, error: RULE_TAGS_LOAD_ERROR }));
}
}, [http]);
const fetchRules = useCallback(async () => {
setRulesState((oldState) => ({ ...oldState, isLoading: true }));
@ -43,10 +61,12 @@ export function useFetchRules({
page,
searchText,
typesFilter: typesFilter.length > 0 ? typesFilter : OBSERVABILITY_RULE_TYPES,
tagsFilter,
ruleExecutionStatusesFilter: ruleLastResponseFilter,
ruleStatusesFilter,
sort,
});
await loadRuleTagsAggs();
setRulesState((oldState) => ({
...oldState,
isLoading: false,
@ -60,8 +80,9 @@ export function useFetchRules({
const isFilterApplied = !(
isEmpty(searchText) &&
isEmpty(ruleLastResponseFilter) &&
isEmpty(ruleStatusesFilter) &&
isEmpty(typesFilter)
isEmpty(typesFilter) &&
isEmpty(tagsFilter) &&
isEmpty(ruleStatusesFilter)
);
setNoData(response.data.length === 0 && !isFilterApplied);
@ -75,6 +96,8 @@ export function useFetchRules({
setPage,
searchText,
ruleLastResponseFilter,
tagsFilter,
loadRuleTagsAggs,
ruleStatusesFilter,
typesFilter,
sort,
@ -89,5 +112,6 @@ export function useFetchRules({
setRulesState,
noData,
initialLoad,
tagsState,
};
}

View file

@ -39,6 +39,7 @@ const triggersActionsUiStartMock = {
getRuleStatusDropdown: jest.fn(),
getRuleTagBadge: jest.fn(),
getRuleStatusFilter: jest.fn(),
getRuleTagFilter: jest.fn(),
ruleTypeRegistry: {
has: jest.fn(),
register: jest.fn(),

View file

@ -86,7 +86,11 @@ describe('empty RulesPage', () => {
},
],
});
useFetchRules.mockReturnValue({ rulesState, noData: true });
useFetchRules.mockReturnValue({
rulesState,
noData: true,
tagsState: { data: [], error: null },
});
wrapper = mountWithIntl(<RulesPage />);
}
it('renders empty screen', async () => {
@ -138,7 +142,11 @@ describe('empty RulesPage with show only capability', () => {
ruleTaskTimeout: '1m',
},
];
useFetchRules.mockReturnValue({ rulesState, noData: true });
useFetchRules.mockReturnValue({
rulesState,
noData: true,
tagsState: { data: [], error: null },
});
useLoadRuleTypes.mockReturnValue({ ruleTypes });
wrapper = mountWithIntl(<RulesPage />);
@ -352,7 +360,7 @@ describe('RulesPage with items', () => {
ruleTypes,
ruleTypeIndex: mockedRuleTypeIndex,
});
useFetchRules.mockReturnValue({ rulesState });
useFetchRules.mockReturnValue({ rulesState, tagsState: { data: [], error: null } });
wrapper = mountWithIntl(<RulesPage />);
await act(async () => {
await nextTick();
@ -509,7 +517,7 @@ describe('RulesPage with items and show only capability', () => {
error: null,
totalItemCount: 3,
};
useFetchRules.mockReturnValue({ rulesState });
useFetchRules.mockReturnValue({ rulesState, tagsState: { data: [], error: null } });
const mockedRuleTypeIndex = new Map(
Object.entries({

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useState, useMemo, useCallback } from 'react';
import React, { useState, useMemo, useCallback, useEffect } from 'react';
import { capitalize, sortBy } from 'lodash';
import {
EuiButton,
@ -93,6 +93,7 @@ function RulesPage() {
});
const [inputText, setInputText] = useState<string | undefined>();
const [searchText, setSearchText] = useState<string | undefined>();
const [tagsFilter, setTagsFilter] = useState<string[]>([]);
const [typesFilter, setTypesFilter] = useState<string[]>([]);
const { lastResponse, setLastResponse } = useRulesPageStateContainer();
const { status, setStatus } = useRulesPageStateContainer();
@ -108,16 +109,19 @@ function RulesPage() {
setCurrentRuleToEdit(ruleItem);
};
const { rulesState, setRulesState, reload, noData, initialLoad } = useFetchRules({
const { rulesState, setRulesState, reload, noData, initialLoad, tagsState } = useFetchRules({
searchText,
ruleLastResponseFilter: lastResponse,
ruleStatusesFilter: status,
typesFilter,
tagsFilter,
page,
setPage,
sort,
});
const { data: rules, totalItemCount, error } = rulesState;
const { data: tags, error: tagsError } = tagsState;
const { ruleTypeIndex, ruleTypes } = useLoadRuleTypes({
filteredSolutions: OBSERVABILITY_SOLUTIONS,
});
@ -165,6 +169,18 @@ function RulesPage() {
},
]);
useEffect(() => {
if (tagsError) {
toasts.addDanger({
title: tagsError,
});
}
if (error)
toasts.addDanger({
title: error,
});
}, [tagsError, error, toasts]);
const getRulesTableColumns = () => {
return [
{
@ -182,11 +198,11 @@ function RulesPage() {
sortable: false,
width: '50px',
'data-test-subj': 'rulesTableCell-tagsPopover',
render: (tags: string[], item: RuleTableItem) => {
return tags.length > 0
render: (ruleTags: string[], item: RuleTableItem) => {
return ruleTags.length > 0
? triggersActionsUi.getRuleTagBadge({
isOpen: tagPopoverOpenIndex === item.index,
tags,
tags: ruleTags,
onClick: () => setTagPopoverOpenIndex(item.index),
onClose: () => setTagPopoverOpenIndex(-1),
})
@ -352,6 +368,13 @@ function RulesPage() {
)}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{triggersActionsUi.getRuleTagFilter({
tags,
selectedTags: tagsFilter,
onChange: (myTags: string[]) => setTagsFilter(myTags),
})}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<LastResponseFilter
key="rule-lastResponse-filter"
@ -415,7 +438,6 @@ function RulesPage() {
</>
);
};
return (
<ObservabilityPageTemplate
pageHeader={{
@ -478,10 +500,6 @@ function RulesPage() {
/>
{getRulesTable()}
{error &&
toasts.addDanger({
title: error,
})}
{currentRuleToEdit && <EditRuleFlyout onSave={reload} currentRule={currentRuleToEdit} />}
{createRuleFlyoutVisibility && CreateRuleFlyout}
</ObservabilityPageTemplate>

View file

@ -125,6 +125,13 @@ export const RULES_LOAD_ERROR = i18n.translate('xpack.observability.rules.loadEr
defaultMessage: 'Unable to load rules',
});
export const RULE_TAGS_LOAD_ERROR = i18n.translate(
'xpack.observability.rulesList.unableToLoadRuleTags',
{
defaultMessage: 'Unable to load rule tags',
}
);
export const RULES_SINGLE_TITLE = i18n.translate(
'xpack.observability.rules.rulesTable.singleTitle',
{

View file

@ -44,6 +44,7 @@ export interface FetchRulesProps {
ruleLastResponseFilter: string[];
ruleStatusesFilter: RuleStatus[];
typesFilter: string[];
tagsFilter: string[];
page: Pagination;
setPage: Dispatch<SetStateAction<Pagination>>;
sort: EuiTableSortingType<RuleTableItem>['sort'];
@ -66,3 +67,8 @@ export interface RuleState {
error: string | null;
totalItemCount: number;
}
export interface TagsState {
data: string[];
error: string | null;
}

View file

@ -61,7 +61,7 @@ export { muteRule } from './application/lib/rule_api/mute';
export { unmuteRule } from './application/lib/rule_api/unmute';
export { snoozeRule } from './application/lib/rule_api/snooze';
export { unsnoozeRule } from './application/lib/rule_api/unsnooze';
export { loadRuleAggregations } from './application/lib/rule_api/aggregate';
export { loadRuleAggregations, loadRuleTags } from './application/lib/rule_api/aggregate';
export { useLoadRuleTypes } from './application/hooks/use_load_rule_types';
export { loadActionTypes } from './application/lib/action_connector_api/connector_types';