Skip to content

Commit

Permalink
Specify filters for Office365 search bar (#3533)
Browse files Browse the repository at this point in the history
* feat(filter-office365): Added specified filters to searchbar on Office365

* feat(configuration-view): Separated responsibilities of use-value-suggestion. Added new helper.

* feat(configuration-view): PR comments

* feat(configuration-view): Improvements TS on helper

* feat(filter-office365): Replaced switch by dictionary, added another example of custom field

* doc(changelog): Update

* feat(filter-office365): Added options and filterByKey property for custom filters.

* feat: add multi-select filter

* remove DEVELOPER GUIDE

* fix: no filters found incorrectly displayed

* feat(filter-office365): Fixed onChange from searchBar and added onRemove handler.

* feat(filter-office365): Fixed onSearch on multi-select component. added support to use query on se-value-suggestion for predefined options.

* feat(filter-office365): Implement multiSelect for all filters.

* feat(filter-office365): PR comments

* delete combobox component and fix is one of filters

Co-authored-by: Franco Charriol <francocharriol@gmail.com>
Co-authored-by: Ezequiel Airaudo <36004787+eze9252@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 10, 2021
1 parent 2b7a899 commit 17b7105
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 62 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ All notable changes to the Wazuh app project will be documented in this file.
- Added a new hook for getting value suggestions [#3506](https://github.com/wazuh/wazuh-kibana-app/pull/3506)
- Added configuration viewer for Module Office365 on Management > Configuration [3524](https://github.com/wazuh/wazuh-kibana-app/pull/3524)
- Added base Module Panel view with Office365 setup [#3518](https://github.com/wazuh/wazuh-kibana-app/pull/3518)
- Added specifics and custom filters for Office365 search bar [#3533](https://github.com/wazuh/wazuh-kibana-app/pull/3533)
- Adding Pagination and filter to drilldown tables at Office pannel [#3544](https://github.com/wazuh/wazuh-kibana-app/issues/3544).

### Changed
Expand Down
1 change: 1 addition & 0 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,4 @@ export const UI_TOAST_COLOR = {
WARNING: 'warning',
DANGER: 'danger',
};

26 changes: 0 additions & 26 deletions public/components/common/custom-search-bar/components/combobox.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
* Find more information about this on the LICENSE file.
*/

export { Combobox } from './combobox';
export { MultiSelect } from './multi-select';
160 changes: 160 additions & 0 deletions public/components/common/custom-search-bar/components/multi-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Wazuh app - React component Multi-select
* Copyright (C) 2015-2021 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/

import React, { useEffect, useState } from 'react';
import {
EuiFieldSearch,
EuiFilterButton,
EuiFilterGroup,
EuiFilterSelectItem,
EuiIcon,
EuiLoadingChart,
EuiPopover,
EuiPopoverTitle,
EuiSpacer,
FilterChecked,
} from '@elastic/eui';
import { IValueSuggestion, useValueSuggestion } from '../../hooks';

const ON = 'on';
const OFF = 'off';

export const MultiSelect = ({ item, onChange, selectedOptions, onRemove }) => {
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
const { suggestedValues, isLoading, setQuery }: IValueSuggestion = useValueSuggestion(
item.key,
item?.options
);
const [items, setItems] = useState<
{ key: any; label: any; value: any; checked: FilterChecked }[]
>([]);
const [activeFilters, setActiveFilters] = useState<number>(0);

useEffect(() => {
if (!isLoading) {
setItems(
suggestedValues
.map((value, key) => ({
key: key,
label: value,
value: item.key,
filterByKey: item.filterByKey,
checked: OFF as FilterChecked,
}))
.sort((a, b) => a.label - b.label)
);
}
}, [suggestedValues, isLoading]);

useEffect(() => {
setItems(
items.map((item) => ({
...item,
checked: selectedOptions.find((element) => element.label === filterBy(item)) ? ON : OFF,
}))
);
setActiveFilters(selectedOptions.length);
}, [selectedOptions]);

const filterBy = (item) => {
return item.filterByKey ? item.key.toString() : item.label;
};

const toggleFilter = (item) => {
item.checked = item.checked === ON ? OFF : ON;
updateFilters(item.value);
};

const updateFilters = (id) => {
const selectedItems = items.filter((item) => item.checked === ON);
setActiveFilters(selectedItems.length);
if (selectedItems.length) {
onChange(selectedItems,id);
} else {
onRemove(item.key);
}
};

const onSearch = (selectedOptions) => {
setQuery(selectedOptions.target.value);
};

const onButtonClick = () => {
setIsPopoverOpen(!isPopoverOpen);
};

const closePopover = () => {
setIsPopoverOpen(false);
setQuery('');
};

const button = (
<EuiFilterButton
iconType="arrowDown"
onClick={onButtonClick}
isSelected={isPopoverOpen}
numFilters={selectedOptions.length}
hasActiveFilters={activeFilters > 0}
numActiveFilters={activeFilters}
>
{item.placeholder}
</EuiFilterButton>
);

return (
<EuiFilterGroup data-test-subj={`multiSelect-${item.key}`}>
<EuiPopover
id={`popoverMultiSelect-${item.key}`}
ownFocus
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
withTitle
>
<EuiPopoverTitle>
<EuiFieldSearch onChange={onSearch} />
</EuiPopoverTitle>
<div className="euiFilterSelect__items">
{items.map((item) => (
<EuiFilterSelectItem
checked={item.checked}
key={item.key}
onClick={() => toggleFilter(item)}
showIcons={item.checked === ON}
>
{item.label}
</EuiFilterSelectItem>
))}
{isLoading && (
<div className="euiFilterSelect__note">
<div className="euiFilterSelect__noteContent">
<EuiLoadingChart size="m" />
<EuiSpacer size="xs" />
<p>Loading filters</p>
</div>
</div>
)}
{suggestedValues.length === 0 && (
<div className="euiFilterSelect__note">
<div className="euiFilterSelect__noteContent">
<EuiIcon type="minusInCircle" />
<EuiSpacer size="xs" />
<p>No filters found</p>
</div>
</div>
)}
</div>
</EuiPopover>
</EuiFilterGroup>
);
};
36 changes: 22 additions & 14 deletions public/components/common/custom-search-bar/custom-search-bar.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import React, { useState, useEffect } from 'react';

import { Filter } from '../../../../../../src/plugins/data/public/';
import {
FilterMeta,
FilterState,
FilterStateStore,
} from '../../../../../../src/plugins/data/common';

import { AppState } from '../../../react-services/app-state';

import { EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui';

//@ts-ignore
import { KbnSearchBar } from '../../kbn-search-bar';
import { Combobox } from './components';
import { useFilterManager } from '../hooks';
import { MultiSelect } from './components';
import { useFilterManager, useIndexPattern } from '../hooks';

export const CustomSearchBar = ({ filtersValues, ...props }) => {
const { filterManager, filters } = useFilterManager();
const indexPattern = useIndexPattern();
const defaultSelectedOptions = () => {
const array = [];
filtersValues.forEach((item) => {
Expand All @@ -27,7 +28,7 @@ export const CustomSearchBar = ({ filtersValues, ...props }) => {
};
const [avancedFiltersState, setAvancedFiltersState] = useState(false);
const [selectedOptions, setSelectedOptions] = useState(defaultSelectedOptions);
const [values, setValues] = useState([]);
const [values, setValues] = useState(Array);
const [selectReference, setSelectReference] = useState('');

useEffect(() => {
Expand All @@ -51,11 +52,11 @@ export const CustomSearchBar = ({ filtersValues, ...props }) => {
const newFilters = values.map((element) => ({
match_phrase: {
[element.value]: {
query: element.label,
query: element.filterByKey ? element.key : element.label,
},
},
}));
const params = values.map((item) => item.label);
const params = values.map((item) => item.filterByKey ? item.key : item.label);
const meta: FilterMeta = {
disabled: false,
negate: false,
Expand Down Expand Up @@ -118,24 +119,31 @@ export const CustomSearchBar = ({ filtersValues, ...props }) => {
}
};

const onChange = (values: any[]) => {
const onChange = (values: any[], id: string) => {
setSelectReference(id)
setValues(values);
};

const onRemove = (filter) => {
const currentFilters = filterManager.getFilters().filter((item) => item.meta.key != filter);
filterManager.removeAll();
filterManager.addFilters(currentFilters);
refreshCustomSelectedFilter();
};

const getComponent = (item: any) => {
const types = {
const types: { [key: string]: object } = {
default: <></>,
combobox: (
<Combobox
id={item.key}
multiSelect: (
<MultiSelect
item={item}
selectedOptions={selectedOptions[item.key] || []}
onChange={onChange}
onClick={() => setSelectReference(item.key)}
onRemove={onRemove}
/>
),
};
return types[item.type] || types['default'];
return types[item.type] || types.default;
};

return (
Expand All @@ -145,7 +153,7 @@ export const CustomSearchBar = ({ filtersValues, ...props }) => {
alignItems="center"
style={{ margin: '0 8px' }}
>
{avancedFiltersState === false
{!avancedFiltersState
? filtersValues.map((item, key) => (
<EuiFlexItem key={key}>{getComponent(item)}</EuiFlexItem>
))
Expand Down
2 changes: 1 addition & 1 deletion public/components/common/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ export * from './use-app-config';
export * from './useRootScope';
export * from './use_async_action';
export { useEsSearch } from './use-es-search';
export { useValueSuggestions, IValueSuggestiions } from './use-value-suggestions';
export { useValueSuggestion, IValueSuggestion } from './use-value-suggestion';
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
*
* Find more information about this on the LICENSE file.
*/
import { useState, useEffect } from 'react';

import React, { useEffect, useState } from 'react';
import { getDataPlugin } from '../../../kibana-services';
import { useIndexPattern } from '.';
import { useIndexPattern } from '../hooks';
import { IFieldType, IIndexPattern } from 'src/plugins/data/public';
import React from 'react';
import {
UI_ERROR_SEVERITIES,
UIErrorLog,
Expand All @@ -23,19 +23,37 @@ import {
import { UI_LOGGER_LEVELS } from '../../../../common/constants';
import { getErrorOrchestrator } from '../../../react-services/common-services';

export interface IValueSuggestiions {
export interface IValueSuggestion {
suggestedValues: string[] | boolean[];
isLoading: boolean;
setQuery: React.Dispatch<React.SetStateAction<string>>;
}

export const useValueSuggestions = (filterField: string, type: 'string' | 'boolean' = 'string') : IValueSuggestiions => {
export const useValueSuggestion = (
filterField: string,
options?: string[],
type: 'string' | 'boolean' = 'string'
): IValueSuggestion => {
const [suggestedValues, setSuggestedValues] = useState<string[] | boolean[]>([]);
const [query, setQuery] = useState<string>('');
const [isLoading, setIsLoading] = useState(true);
const data = getDataPlugin();
const indexPattern = useIndexPattern();

const getOptions = (): string[] => {
return options?.filter((element) => element.toLowerCase().includes(query.toLowerCase())) || [];
};

const getValueSuggestions = async (field) => {
return options
? getOptions()
: await data.autocomplete.getValueSuggestions({
query,
indexPattern: indexPattern as IIndexPattern,
field,
});
};

useEffect(() => {
if (indexPattern) {
setIsLoading(true);
Expand All @@ -46,16 +64,10 @@ export const useValueSuggestions = (filterField: string, type: 'string' | 'boole
aggregatable: true,
} as IFieldType;
try {
setSuggestedValues(
await data.autocomplete.getValueSuggestions({
query,
indexPattern: indexPattern as IIndexPattern,
field,
})
);
setSuggestedValues(await getValueSuggestions(field));
} catch (error) {
const options: UIErrorLog = {
context: `${useValueSuggestions.name}.valueSuggestions`,
context: `${useValueSuggestion.name}.getValueSuggestions`,
level: UI_LOGGER_LEVELS.ERROR as UILogLevel,
severity: UI_ERROR_SEVERITIES.UI as UIErrorSeverity,
error: {
Expand Down
Loading

0 comments on commit 17b7105

Please sign in to comment.