Skip to content

Commit 9812408

Browse files
committed
chips refactoring for more options
1 parent 832cdf7 commit 9812408

File tree

4 files changed

+157
-41
lines changed

4 files changed

+157
-41
lines changed

web/locales/en/plugin__netobserv-plugin.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,10 +379,11 @@
379379
"Enable": "Enable",
380380
"group filter": "group filter",
381381
"filter": "filter",
382+
"Swap": "Swap",
383+
"Remove": "Remove",
382384
"Edit filters": "Edit filters",
383385
"Reset defaults": "Reset defaults",
384386
"Clear all": "Clear all",
385-
"Swap": "Swap",
386387
"Swap source and destination filters": "Swap source and destination filters",
387388
"Back and forth": "Back and forth",
388389
"One way": "One way",

web/src/components/toolbar/filters-toolbar.css

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,19 @@ div#filter-toolbar-search-filters {
8181

8282
.custom-chip {
8383
min-height: 2em;
84-
padding: 0;
85-
padding-left: 1em;
84+
padding: 0 1em 0 1em;
8685
margin-right: 0.5em;
8786
color: #000;
8887
background-color: #fff;
8988
}
9089

90+
.custom-chip::before,
91+
.custom-chip::after {
92+
border: none !important;
93+
border-block-start: none !important;
94+
border-block-end: none !important;
95+
}
96+
9197
.custom-chip-group :nth-child(1 of .custom-chip) {
9298
margin-left: 1em;
9399
}
@@ -158,7 +164,7 @@ div#filter-toolbar-search-filters {
158164

159165
.custom-chip-group>button,
160166
.custom-chip>button {
161-
padding: 0.3em 0.5em 0.3em 0.5em;
167+
padding: 0 0 0 1em;
162168
}
163169

164170
.custom-chip-group>p,

web/src/components/toolbar/filters/filters-chips.tsx

Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
1-
import { Button, Text, TextContent, TextVariants, ToolbarGroup, ToolbarItem, Tooltip } from '@patternfly/react-core';
2-
import { LongArrowAltDownIcon, LongArrowAltUpIcon, TimesCircleIcon, TimesIcon } from '@patternfly/react-icons';
1+
import {
2+
Button,
3+
Dropdown,
4+
DropdownItem,
5+
DropdownList,
6+
MenuToggle,
7+
MenuToggleElement,
8+
Text,
9+
TextContent,
10+
TextVariants,
11+
ToolbarGroup,
12+
ToolbarItem,
13+
Tooltip
14+
} from '@patternfly/react-core';
15+
import {
16+
ArrowsAltVIcon,
17+
BanIcon,
18+
CheckIcon,
19+
LongArrowAltDownIcon,
20+
LongArrowAltUpIcon,
21+
TimesCircleIcon,
22+
TimesIcon
23+
} from '@patternfly/react-icons';
324
import * as _ from 'lodash';
425
import * as React from 'react';
526
import { useTranslation } from 'react-i18next';
@@ -13,7 +34,7 @@ import {
1334
} from '../../../model/filters';
1435
import { QuickFilter } from '../../../model/quick-filters';
1536
import { autoCompleteCache } from '../../../utils/autocomplete-cache';
16-
import { getFilterFullName, hasSrcDstFilters, swapFilters } from '../../../utils/filters-helper';
37+
import { getFilterFullName, hasSrcDstFilters, swapFilters, swapFilterValue } from '../../../utils/filters-helper';
1738
import { getPathWithParams, netflowTrafficPath } from '../../../utils/url';
1839
import { navigate } from '../../dynamic-loader/dynamic-loader';
1940
import { LinksOverflow } from '../links-overflow';
@@ -40,6 +61,8 @@ export const FiltersChips: React.FC<FiltersChipsProps> = ({
4061
}) => {
4162
const { t } = useTranslation('plugin__netobserv-plugin');
4263

64+
const [openedDropdown, setOpenedDropdown] = React.useState<string>();
65+
4366
const setFiltersList = React.useCallback(
4467
(list: Filter[]) => {
4568
setFilters({ ...filters, list: list });
@@ -101,40 +124,93 @@ export const FiltersChips: React.FC<FiltersChipsProps> = ({
101124
</Text>
102125
</Tooltip>
103126
{chipFilter.values.map((chipFilterValue, fvIndex) => {
127+
if (isForced || chipFilterValue.disabled) {
128+
return (
129+
<div key={fvIndex} className={`custom-chip ${chipFilterValue.disabled ? 'disabled-value' : ''}`}>
130+
<Tooltip
131+
content={`${chipFilterValue.disabled ? t('Enable') : t('Disable')} ${fullName} '${
132+
chipFilterValue.display || chipFilterValue.v
133+
}' ${t('filter')}`}
134+
>
135+
<Text
136+
component={TextVariants.p}
137+
onClick={() => {
138+
chipFilterValue.disabled = !chipFilterValue.disabled;
139+
setFilters(_.cloneDeep(filters));
140+
}}
141+
>
142+
{chipFilterValue.display ? chipFilterValue.display : chipFilterValue.v}
143+
</Text>
144+
</Tooltip>
145+
</div>
146+
);
147+
}
148+
149+
const dropdownId = `${chipFilter.def.id}-${fvIndex}`;
104150
return (
105-
<div key={fvIndex} className={`custom-chip ${chipFilterValue.disabled ? 'disabled-value' : ''}`}>
106-
<Tooltip
107-
content={`${chipFilterValue.disabled ? t('Enable') : t('Disable')} ${fullName} '${
108-
chipFilterValue.display || chipFilterValue.v
109-
}' ${t('filter')}`}
110-
>
111-
<Text
112-
component={TextVariants.p}
151+
<Dropdown
152+
key={fvIndex}
153+
isOpen={dropdownId === openedDropdown}
154+
onOpenChange={(isOpen: boolean) => setOpenedDropdown(isOpen ? dropdownId : undefined)}
155+
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
156+
<MenuToggle
157+
ref={toggleRef}
158+
className={`custom-chip ${chipFilterValue.disabled ? 'disabled-value' : ''}`}
159+
isExpanded={dropdownId === openedDropdown}
160+
onClick={() => setOpenedDropdown(openedDropdown === dropdownId ? undefined : dropdownId)}
161+
>
162+
{chipFilterValue.display ? chipFilterValue.display : chipFilterValue.v}
163+
</MenuToggle>
164+
)}
165+
>
166+
<DropdownList>
167+
<DropdownItem
168+
key="disable"
113169
onClick={() => {
114-
//switch value
115170
chipFilterValue.disabled = !chipFilterValue.disabled;
116171
setFilters(_.cloneDeep(filters));
172+
setOpenedDropdown(undefined);
117173
}}
118174
>
119-
{chipFilterValue.display ? chipFilterValue.display : chipFilterValue.v}
120-
</Text>
121-
</Tooltip>
122-
{!isForced && (
123-
<Button
124-
variant="plain"
175+
{chipFilterValue.disabled && <CheckIcon />}
176+
{!chipFilterValue.disabled && <BanIcon />}
177+
&nbsp;{chipFilterValue.disabled ? t('Enable') : t('Disable')}
178+
</DropdownItem>
179+
{(chipFilter.def.id.startsWith('src_') || chipFilter.def.id.startsWith('dst_')) && (
180+
<DropdownItem
181+
key="swap"
182+
onClick={() => {
183+
const swapped = swapFilterValue(
184+
filterDefinitions,
185+
filters!.list,
186+
chipFilter.def.id,
187+
chipFilterValue
188+
);
189+
setFilters({ ...filters!, list: swapped });
190+
setOpenedDropdown(undefined);
191+
}}
192+
>
193+
<ArrowsAltVIcon style={{ transform: 'rotate(90deg)' }} />
194+
&nbsp;{t('Swap')}
195+
</DropdownItem>
196+
)}
197+
<DropdownItem
198+
key="remove"
125199
onClick={() => {
126200
chipFilter.values = chipFilter.values.filter(val => val.v !== chipFilterValue.v);
127201
if (_.isEmpty(chipFilter.values)) {
128202
setFiltersList(removeFromFilters(filters.list, chipFilter));
129203
} else {
130204
setFilters(_.cloneDeep(filters));
131205
}
206+
setOpenedDropdown(undefined);
132207
}}
133208
>
134209
<TimesIcon />
135-
</Button>
136-
)}
137-
</div>
210+
&nbsp;{t('Remove')}
211+
</DropdownItem>
212+
</DropdownList>
213+
</Dropdown>
138214
);
139215
})}
140216
{!isForced && (

web/src/utils/filters-helper.ts

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { TFunction } from 'i18next';
2-
import { Filter, FilterDefinition, FilterId } from '../model/filters';
2+
import { Filter, FilterDefinition, FilterId, FilterValue } from '../model/filters';
33
import { findFilter } from './filter-definitions';
44

55
export type Indicator = 'default' | 'success' | 'warning' | 'error' | undefined;
@@ -41,20 +41,53 @@ export const hasSrcDstFilters = (filters: Filter[]): boolean => {
4141
return filters.some(f => f.def.id.startsWith('src_') || f.def.id.startsWith('dst_'));
4242
};
4343

44-
export const swapFilters = (filterDefinitions: FilterDefinition[], filters: Filter[]): Filter[] => {
45-
return filters.map(f => {
46-
let swappedId: FilterId | undefined;
47-
if (f.def.id.startsWith('src_')) {
48-
swappedId = f.def.id.replace('src_', 'dst_') as FilterId;
49-
} else if (f.def.id.startsWith('dst_')) {
50-
swappedId = f.def.id.replace('dst_', 'src_') as FilterId;
51-
}
52-
if (swappedId) {
53-
const def = findFilter(filterDefinitions, swappedId);
54-
if (def) {
55-
return { ...f, def };
56-
}
44+
export const swapFilter = (filterDefinitions: FilterDefinition[], filter: Filter): Filter => {
45+
let swappedId: FilterId | undefined;
46+
if (filter.def.id.startsWith('src_')) {
47+
swappedId = filter.def.id.replace('src_', 'dst_') as FilterId;
48+
} else if (filter.def.id.startsWith('dst_')) {
49+
swappedId = filter.def.id.replace('dst_', 'src_') as FilterId;
50+
}
51+
if (swappedId) {
52+
const def = findFilter(filterDefinitions, swappedId);
53+
if (def) {
54+
return { ...filter, def };
5755
}
58-
return f;
59-
});
56+
}
57+
return filter;
58+
};
59+
60+
export const swapFilters = (filterDefinitions: FilterDefinition[], filters: Filter[]): Filter[] => {
61+
return filters.map(f => swapFilter(filterDefinitions, f));
62+
};
63+
64+
export const swapFilterValue = (
65+
filterDefinitions: FilterDefinition[],
66+
filters: Filter[],
67+
id: FilterId,
68+
value: FilterValue
69+
): Filter[] => {
70+
// remove value from existing filter
71+
const found = filters.find(f => f.def.id === id);
72+
if (!found) {
73+
console.error("Can't find filter id", id);
74+
return filters;
75+
}
76+
found.values = found.values.filter(val => val.v !== value.v);
77+
78+
// remove filter if no more values
79+
if (!found.values.length) {
80+
filters = filters.filter(f => f !== found);
81+
}
82+
83+
// add new swapped filter
84+
const swapped = swapFilter(filterDefinitions, { ...found, values: [value] });
85+
const existing = filters.find(f => f.def.id === swapped.def.id);
86+
if (existing) {
87+
existing.values.push(swapped.values[0]);
88+
} else {
89+
filters.push(swapped);
90+
}
91+
92+
return filters;
6093
};

0 commit comments

Comments
 (0)