Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance log type filters correlations #745

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 78 additions & 15 deletions public/pages/Correlations/components/FilterGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,32 @@ import {
EuiPopoverTitle,
EuiFieldSearch,
FilterChecked,
EuiPopoverFooter,
EuiButtonEmpty,
} from '@elastic/eui';

export type FilterItem = { name: string | React.ReactNode; id: string; checked?: FilterChecked };
export type FilterItem = {
name: string | React.ReactNode;
id: string;
visible: boolean;
childOptionIds?: Set<string>;
checked?: FilterChecked;
};
export interface LogTypeFilterGroupProps {
groupName: string;
items: FilterItem[];
hasGroupOptions?: boolean;
hasFooter?: boolean;
setItems: (items: FilterItem[]) => void;
}

export const FilterGroup: React.FC<LogTypeFilterGroupProps> = ({ groupName, items, setItems }) => {
export const FilterGroup: React.FC<LogTypeFilterGroupProps> = ({
groupName,
items,
hasGroupOptions,
hasFooter,
setItems,
}) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const [showActiveFilters, setShowActiveFilters] = useState(false);

Expand All @@ -33,7 +49,7 @@ export const FilterGroup: React.FC<LogTypeFilterGroupProps> = ({ groupName, item
setIsPopoverOpen(false);
};

function updateItem(index: number) {
function toggleItem(index: number) {
if (!items[index]) {
return;
}
Expand All @@ -54,7 +70,35 @@ export const FilterGroup: React.FC<LogTypeFilterGroupProps> = ({ groupName, item
checked: 'on',
};
}
const childIds = newItems[index].childOptionIds;
if (childIds) {
newItems.forEach((item) => {
if (childIds.has(item.id)) {
item.checked = newItems[index].checked;
}
});
}

setItems(newItems);
setShowActiveFilters(true);
}

function toggleAll(state: 'on' | undefined) {
amsiglan marked this conversation as resolved.
Show resolved Hide resolved
const newItems = items.map((item) => ({
...item,
checked: state,
}));

setItems(newItems);
setShowActiveFilters(!!state);
}

function search(term: string) {
const newItems = [...items];
term = term.toLowerCase();
items.forEach((item) => {
item.visible = item.id.toLowerCase().includes(term);
});
setItems(newItems);
setShowActiveFilters(true);
}
Expand Down Expand Up @@ -83,20 +127,39 @@ export const FilterGroup: React.FC<LogTypeFilterGroupProps> = ({ groupName, item
panelPaddingSize="none"
>
<EuiPopoverTitle paddingSize="s">
<EuiFieldSearch compressed />
<EuiFieldSearch compressed onSearch={search} />
</EuiPopoverTitle>
<div className="ouiFilterSelect__items">
{items.map((item, index) => (
<EuiFilterSelectItem
checked={item.checked}
key={index}
onClick={() => updateItem(index)}
showIcons={true}
>
{item.name}
</EuiFilterSelectItem>
))}
<div
className="ouiFilterSelect__items"
style={hasFooter ? { maxHeight: 400, overflow: 'scroll' } : undefined}
>
{items.map((item, index) => {
const itemStyle: any = {};
itemStyle['paddingLeft'] =
hasGroupOptions && !item.childOptionIds ? 20 : itemStyle['paddingLeft'];
itemStyle['display'] = !item.visible ? 'none' : itemStyle['display'];

return (
<EuiFilterSelectItem
checked={item.checked}
key={index}
onClick={() => toggleItem(index)}
showIcons={true}
style={itemStyle}
>
{item.name}
</EuiFilterSelectItem>
);
})}
</div>
{hasFooter && (
<EuiPopoverFooter>
<div>
<EuiButtonEmpty onClick={() => toggleAll('on')}>Select all</EuiButtonEmpty>
<EuiButtonEmpty onClick={() => toggleAll(undefined)}>Deselect all</EuiButtonEmpty>
</div>
</EuiPopoverFooter>
)}
</EuiPopover>
</EuiFilterGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,8 @@ export class Correlations extends React.Component<CorrelationsProps, Correlation
<FilterGroup
groupName="Log types"
items={this.state.logTypeFilterOptions}
hasGroupOptions={true}
hasFooter={true}
setItems={this.onLogTypeFilterChange}
/>
</EuiFilterGroup>
Expand Down
39 changes: 32 additions & 7 deletions public/pages/Correlations/utils/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import React from 'react';
import { CorrelationGraphData } from '../../../../types';
import { ruleSeverity, ruleTypes } from '../../Rules/utils/constants';
import { FilterItem } from '../components/FilterGroup';
import { EuiIcon } from '@elastic/eui';
import { EuiIcon, EuiTitle } from '@elastic/eui';
import { logTypeCategories, logTypesByCategories } from '../../../utils/constants';
import _ from 'lodash';

export const graphRenderOptions = {
nodes: {
Expand Down Expand Up @@ -47,12 +49,34 @@ export const graphRenderOptions = {
},
};

export const getDefaultLogTypeFilterItemOptions: () => FilterItem[] = () =>
Object.values(ruleTypes).map((type) => ({
name: `${type.label}`,
id: type.label.toLowerCase(),
checked: 'on',
}));
export const getDefaultLogTypeFilterItemOptions: () => FilterItem[] = () => {
const options: FilterItem[] = [];
logTypeCategories.forEach((category) => {
const logTypes = logTypesByCategories[category];
options.push({
name: (
<EuiTitle size="xxs">
<h4>{category}</h4>
</EuiTitle>
),
id: category,
checked: 'on',
childOptionIds: new Set(logTypes.map(({ name }) => name)),
visible: true,
});

logTypes.forEach(({ name }) => {
options.push({
name: _.capitalize(name),
id: name,
checked: 'on',
visible: true,
});
});
});

return options;
};

export const defaultSeverityFilterItemOptions: FilterItem[] = Object.values(ruleSeverity).map(
(sev) => {
Expand All @@ -64,6 +88,7 @@ export const defaultSeverityFilterItemOptions: FilterItem[] = Object.values(rule
),
id: sev.value,
checked: 'on',
visible: true,
};
}
);
Expand Down
4 changes: 2 additions & 2 deletions public/pages/LogTypes/components/LogTypeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import React from 'react';
import { LOG_TYPE_NAME_REGEX, validateName } from '../../../utils/validation';
import { NotificationsStart } from 'opensearch-dashboards/public';
import { useState } from 'react';
import { logTypeCategoryOptions } from '../../../utils/constants';
import { getLogTypeCategoryOptions } from '../../../utils/helpers';

export interface LogTypeFormProps {
logTypeDetails: LogTypeItem;
Expand Down Expand Up @@ -119,7 +119,7 @@ export const LogTypeForm: React.FC<LogTypeFormProps> = ({
<EuiSpacer />
<EuiFormRow label="Category" isInvalid={!!categoryError} error={categoryError}>
<EuiSuperSelect
options={logTypeCategoryOptions.map((option) => ({
options={getLogTypeCategoryOptions().map((option) => ({
...option,
disabled: !isEditMode || (isEditMode && !!logTypeDetails.detectionRulesCount),
}))}
Expand Down
10 changes: 4 additions & 6 deletions public/pages/LogTypes/utils/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { LogType } from '../../../../types';
import { capitalize } from 'lodash';
import { Search } from '@opensearch-project/oui/src/eui_components/basic_table';
import { ruleSource } from '../../Rules/utils/constants';
import { logTypesByCategories } from '../../../utils/constants';
import { logTypeCategories } from '../../../utils/constants';

export const getLogTypesTableColumns = (
showDetails: (id: string) => void,
Expand Down Expand Up @@ -71,11 +71,9 @@ export const getLogTypesTableSearchConfig = (): Search => {
field: 'category',
name: 'Category',
multiSelect: 'or',
options: Object.keys(logTypesByCategories)
.map((category) => ({
value: category,
}))
.sort((a, b) => (a.value < b.value ? -1 : a.value > b.value ? 1 : 0)),
options: logTypeCategories.map((category) => ({
value: category,
})),
},
{
type: 'field_value_selection',
Expand Down
3 changes: 0 additions & 3 deletions public/store/CorrelationsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,6 @@ export class CorrelationsStore implements ICorrelationsStore {
if (findingRes.ok) {
findingRes.response.findings.forEach((f) => {
const rule = allRules.find((rule) => rule._id === f.queries[0].id);
if (!rule) {
console.log(f.queries[0].id);
}
findings[f.id] = {
...f,
id: f.id,
Expand Down
16 changes: 15 additions & 1 deletion public/store/LogTypeStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import LogTypeService from '../services/LogTypeService';
import { errorNotificationToast } from '../utils/helpers';
import { DataStore } from './DataStore';
import { ruleTypes } from '../pages/Rules/utils/constants';
import { logTypesByCategories } from '../utils/constants';
import { logTypeCategories, logTypesByCategories } from '../utils/constants';

export class LogTypeStore {
constructor(private service: LogTypeService, private notifications: NotificationsStart) {}
Expand Down Expand Up @@ -73,6 +73,20 @@ export class LogTypeStore {
logTypesByCategories[logType.category] = logTypesByCategories[logType.category] || [];
logTypesByCategories[logType.category].push(logType);
});
logTypeCategories.splice(
0,
logTypeCategories.length,
...Object.keys(logTypesByCategories).sort((a, b) => {
if (a === 'Other') {
return 1;
} else if (b === 'Other') {
return -1;
} else {
return a < b ? -1 : a > b ? 1 : 0;
}
})
);

return logTypes;
}

Expand Down
18 changes: 2 additions & 16 deletions public/utils/constants.tsx → public/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { SimpleSavedObject } from 'opensearch-dashboards/public';
import { Detector, LogType, ServerResponse } from '../../types';
import { DetectorInput, PeriodSchedule } from '../../models/interfaces';
import { DetectorHit } from '../../server/models/interfaces';
import { EuiText } from '@elastic/eui';
import _ from 'lodash';

export const DATE_MATH_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ';
Expand Down Expand Up @@ -177,7 +175,7 @@ export const pendingDashboardCreations: {
[detectorId: string]: undefined | Promise<void | ServerResponse<SimpleSavedObject<unknown>>>;
} = {};

const logTypeCategoryInfo = [
export const logTypeCategoryDescription: { name: string; description: string }[] = [
{ name: 'Access Management', description: 'User access, authentication, group management' },
{ name: 'Applications', description: 'Application lifecycle, API, and web resources activities' },
{ name: 'Cloud Services', description: 'Services managed by cloud providers' },
Expand All @@ -186,17 +184,5 @@ const logTypeCategoryInfo = [
{ name: 'Other', description: 'Logs not covered in other categories' },
];

export const logTypeCategoryOptions: any[] = logTypeCategoryInfo.map(({ name, description }) => ({
value: name,
inputDisplay: name,
dropdownDisplay: (
<>
<strong>{name}</strong>
<EuiText size="s" color="subdued">
<p className="ouiTextColor--subdued">{description}</p>
</EuiText>
</>
),
}));

export const logTypeCategories: string[] = [];
export const logTypesByCategories: { [category: string]: LogType[] } = {};
Loading
Loading