Skip to content

Commit

Permalink
Added categories for log types (#741) (#742)
Browse files Browse the repository at this point in the history
* added categories for log types

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fixed cypress tests

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

---------

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
(cherry picked from commit 3ce112b)

Co-authored-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
opensearch-trigger-bot[bot] and amsiglan authored Oct 5, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 9c53b77 commit 5d3afed
Showing 20 changed files with 261 additions and 90 deletions.
12 changes: 7 additions & 5 deletions cypress/integration/2_rules.spec.js
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ const SAMPLE_RULE = {
logType: 'windows',
description: 'This is a rule used to test the rule creation workflow.',
detectionLine: ['condition: Selection_1', 'Selection_1:', 'FieldKey|contains:', '- FieldValue'],
severity: 'critical',
severity: 'Critical',
tags: ['attack.persistence', 'attack.privilege_escalation', 'attack.t1543.003'],
references: 'https://nohello.com',
falsePositive: 'unknown',
@@ -31,7 +31,7 @@ const YAML_RULE_LINES = [
`- ${SAMPLE_RULE.tags[2]}`,
`falsepositives:`,
`- ${SAMPLE_RULE.falsePositive}`,
`level: ${SAMPLE_RULE.severity}`,
`level: ${SAMPLE_RULE.severity.toLowerCase()}`,
`status: ${SAMPLE_RULE.status}`,
`references:`,
`- '${SAMPLE_RULE.references}'`,
@@ -67,7 +67,9 @@ const checkRulesFlyout = () => {
cy.get('[data-test-subj="rule_flyout_rule_source"]').contains('Custom');

// Validate severity
cy.get('[data-test-subj="rule_flyout_rule_severity"]').contains(SAMPLE_RULE.severity);
cy.get('[data-test-subj="rule_flyout_rule_severity"]').contains(
SAMPLE_RULE.severity.toLowerCase()
);

// Validate tags
SAMPLE_RULE.tags.forEach((tag) =>
@@ -159,7 +161,7 @@ const fillCreateForm = () => {
getAuthorField().type(`${SAMPLE_RULE.author}`);

// rule details
getLogTypeField().type(SAMPLE_RULE.logType);
getLogTypeField().selectComboboxItem(SAMPLE_RULE.logType);
getRuleLevelField().selectComboboxItem(SAMPLE_RULE.severity);

// rule detection
@@ -548,7 +550,7 @@ describe('Rules', () => {
SAMPLE_RULE.logType = 'dns';
YAML_RULE_LINES[2] = `product: ${SAMPLE_RULE.logType}`;
YAML_RULE_LINES[3] = `title: ${SAMPLE_RULE.name}`;
getLogTypeField().type(SAMPLE_RULE.logType).type('{enter}');
getLogTypeField().selectComboboxItem(SAMPLE_RULE.logType);
getLogTypeField().containsValue(SAMPLE_RULE.logType).contains(SAMPLE_RULE.logType);

SAMPLE_RULE.description += ' edited';
2 changes: 1 addition & 1 deletion cypress/integration/3_alerts.spec.js
Original file line number Diff line number Diff line change
@@ -118,7 +118,7 @@ describe('Alerts', () => {
expect($tr, `timestamp`).to.contain(date);
expect($tr, `rule name`).to.contain('Cypress USB Rule');
expect($tr, `detector name`).to.contain(testDetector.name);
expect($tr, `log type`).to.contain('windows');
expect($tr, `log type`).to.contain('System Activity: Windows');
});

// Close the flyout
2 changes: 1 addition & 1 deletion cypress/integration/4_findings.spec.js
Original file line number Diff line number Diff line change
@@ -52,7 +52,7 @@ describe('Findings', () => {
cy.contains('No items found').should('not.exist');

// Check for expected findings
cy.contains('windows');
cy.contains('System Activity: Windows');
cy.contains('High');
});

5 changes: 4 additions & 1 deletion cypress/support/helpers.js
Original file line number Diff line number Diff line change
@@ -62,7 +62,10 @@ Cypress.Commands.add(
items = [items];
}
Cypress.log({ message: `Select combobox items: ${items.join(' | ')}` });
items.map((item) => cy.wrap(subject).type(item).type('{enter}'));
items.map((item) => {
cy.wrap(subject).type(item);
cy.get(`[title="${item}"]`).click({ force: true });
});
}
);

21 changes: 21 additions & 0 deletions public/components/Utility/LogCategoryOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { EuiTitle } from '@elastic/eui';
import React, { useEffect, useRef } from 'react';

export const LogCategoryOptionView: React.FC<{ categoryName: string }> = ({ categoryName }) => {
const inputRef = useRef<HTMLHeadingElement | null>(null);

useEffect(() => {
inputRef.current?.closest('button.euiFilterSelectItem')?.setAttribute('disabled', 'true');
}, [inputRef.current]);

return (
<EuiTitle size="xxs">
<h4 ref={inputRef}>{categoryName}</h4>
</EuiTitle>
);
};
14 changes: 9 additions & 5 deletions public/pages/Correlations/containers/CreateCorrelationRule.tsx
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ import { CoreServicesContext } from '../../../components/core_services';
import { RouteComponentProps, useParams } from 'react-router-dom';
import { validateName } from '../../../utils/validation';
import { FieldMappingService, IndexService } from '../../../services';
import { errorNotificationToast } from '../../../utils/helpers';
import { errorNotificationToast, getLogTypeOptions } from '../../../utils/helpers';

export interface CreateCorrelationRuleProps {
indexService: IndexService;
@@ -103,6 +103,7 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
...correlationRuleStateDefaultValue,
});
const [action, setAction] = useState<CorrelationRuleAction>('Create');
const [logTypeOptions, setLogTypeOptions] = useState<any[]>([]);

useEffect(() => {
if (props.history.location.state?.rule) {
@@ -119,6 +120,12 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
setAction('Edit');
setInitialRuleValues();
}

const setupLogTypeOptions = async () => {
const options = await getLogTypeOptions();
setLogTypeOptions(options);
};
setupLogTypeOptions();
}, []);

const submit = async (values: any) => {
@@ -274,10 +281,7 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
isInvalid={isInvalidInputForQuery('logType')}
placeholder="Select a log type"
data-test-subj={'rule_type_dropdown'}
options={ruleTypes.map(({ label }) => ({
value: label.toLowerCase(),
label,
}))}
options={logTypeOptions}
singleSelection={{ asPlainText: true }}
onChange={(e) => {
props.handleChange(`queries[${queryIdx}].logType`)(
8 changes: 2 additions & 6 deletions public/pages/Correlations/utils/helpers.tsx
Original file line number Diff line number Diff line change
@@ -12,9 +12,8 @@ import {
CorrelationRuleQuery,
} from '../../../../types';
import { Search } from '@opensearch-project/oui/src/eui_components/basic_table';
import { ruleTypes } from '../../Rules/utils/constants';
import { FieldClause } from '@opensearch-project/oui/src/eui_components/search_bar/query/ast';
import { formatRuleType } from '../../../utils/helpers';
import { formatRuleType, getLogTypeFilterOptions } from '../../../utils/helpers';

export const getCorrelationRulesTableColumns = (
onRuleNameClick: (rule: CorrelationRule) => void,
@@ -110,10 +109,7 @@ export const getCorrelationRulesTableSearchConfig = (
field: 'logTypes',
name: 'Log Types',
multiSelect: 'or',
options: ruleTypes.map(({ value, label }) => ({
value,
name: label,
})),
options: getLogTypeFilterOptions(),
},
],
};
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import { RuleItem } from '../DetectionRules/types/interfaces';
import { ruleTypes } from '../../../../../Rules/utils/constants';
import ConfigureFieldMapping from '../../../ConfigureFieldMapping';
import { ConfigureFieldMappingProps } from '../../../ConfigureFieldMapping/containers/ConfigureFieldMapping';
import { getLogTypeOptions } from '../../../../../../utils/helpers';

interface DetectorTypeProps {
detectorType: string;
@@ -30,18 +31,23 @@ interface DetectorTypeState {
}

export default class DetectorType extends Component<DetectorTypeProps, DetectorTypeState> {
private detectorTypeOptions: { value: string; label: string }[];
private detectorTypeOptions: { value: string; label: string }[] = [];
constructor(props: DetectorTypeProps) {
super(props);

this.detectorTypeOptions = ruleTypes.map(({ label }) => ({ value: label, label }));
const detectorTypeIds = this.detectorTypeOptions.map((option) => option.value);
this.state = {
fieldTouched: false,
detectorTypeIds,
detectorTypeIds: [],
};
}

async componentDidMount(): Promise<void> {
this.detectorTypeOptions = await getLogTypeOptions();
this.setState({
detectorTypeIds: ruleTypes.map((option) => option.value),
});
}

onChange = (detectorType: string) => {
this.setState({ fieldTouched: true });
this.props.onDetectorTypeChange(detectorType);
@@ -66,7 +72,13 @@ export default class DetectorType extends Component<DetectorTypeProps, DetectorT
const { detectorType } = this.props;

return (
<ContentPanel title={'Detection'} titleSize={'m'}>
<ContentPanel
title={'Detection'}
subTitleText={
'The available detection rules are automatically populated based on your selected log type. Use the toggles to select detection rules for this detector'
}
titleSize={'m'}
>
<EuiFormRow
label={
<div>
8 changes: 3 additions & 5 deletions public/pages/Detectors/containers/Detectors/Detectors.tsx
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@ import {
capitalizeFirstLetter,
errorNotificationToast,
formatRuleType,
getLogTypeFilterOptions,
renderTime,
} from '../../../../utils/helpers';
import { CoreServicesContext } from '../../../../components/core_services';
@@ -298,11 +299,11 @@ export default class Detectors extends Component<DetectorsProps, DetectorsState>
detectorHits.map((detector) => (detector._source.enabled ? 'Active' : 'Inactive'))
),
];
const logType = [...new Set(detectorHits.map((detector) => detector._source.detector_type))];
const search = {
box: {
placeholder: 'Search threat detectors',
schema: true,
incremental: true,
},
filters: [
{
@@ -319,10 +320,7 @@ export default class Detectors extends Component<DetectorsProps, DetectorsState>
type: 'field_value_selection',
field: 'logType',
name: 'Log type',
options: logType.map((logType) => ({
value: logType,
name: formatRuleType(logType),
})),
options: getLogTypeFilterOptions(),
multiSelect: 'or',
} as FieldValueSelectionFilterConfigType,
],
Original file line number Diff line number Diff line change
@@ -578,6 +578,7 @@ exports[`<Detectors /> spec renders the component 1`] = `
search={
Object {
"box": Object {
"incremental": true,
"placeholder": "Search threat detectors",
"schema": true,
},
@@ -598,12 +599,7 @@ exports[`<Detectors /> spec renders the component 1`] = `
"field": "logType",
"multiSelect": "or",
"name": "Log type",
"options": Array [
Object {
"name": "-",
"value": "detector_type",
},
],
"options": Array [],
"type": "field_value_selection",
},
],
@@ -628,6 +624,7 @@ exports[`<Detectors /> spec renders the component 1`] = `
<EuiSearchBar
box={
Object {
"incremental": true,
"placeholder": "Search threat detectors",
"schema": Object {
"fields": Object {
@@ -669,12 +666,7 @@ exports[`<Detectors /> spec renders the component 1`] = `
"field": "logType",
"multiSelect": "or",
"name": "Log type",
"options": Array [
Object {
"name": "-",
"value": "detector_type",
},
],
"options": Array [],
"type": "field_value_selection",
},
]
@@ -697,18 +689,18 @@ exports[`<Detectors /> spec renders the component 1`] = `
className="euiFlexItem euiSearchBar__searchHolder"
>
<EuiSearchBox
incremental={false}
incremental={true}
isInvalid={false}
onSearch={[Function]}
placeholder="Search threat detectors"
query=""
>
<EuiFieldSearch
aria-label="This is a search bar. After typing your query, hit enter to filter the results lower in the page."
aria-label="This is a search bar. As you type, the results lower in the page will automatically filter."
compressed={false}
defaultValue=""
fullWidth={true}
incremental={false}
incremental={true}
inputRef={[Function]}
isClearable={true}
isInvalid={false}
@@ -732,7 +724,7 @@ exports[`<Detectors /> spec renders the component 1`] = `
isInvalid={false}
>
<input
aria-label="This is a search bar. After typing your query, hit enter to filter the results lower in the page."
aria-label="This is a search bar. As you type, the results lower in the page will automatically filter."
className="euiFieldSearch euiFieldSearch--fullWidth"
defaultValue=""
onKeyUp={[Function]}
@@ -800,12 +792,7 @@ exports[`<Detectors /> spec renders the component 1`] = `
"field": "logType",
"multiSelect": "or",
"name": "Log type",
"options": Array [
Object {
"name": "-",
"value": "detector_type",
},
],
"options": Array [],
"type": "field_value_selection",
},
]
@@ -975,12 +962,7 @@ exports[`<Detectors /> spec renders the component 1`] = `
"field": "logType",
"multiSelect": "or",
"name": "Log type",
"options": Array [
Object {
"name": "-",
"value": "detector_type",
},
],
"options": Array [],
"type": "field_value_selection",
}
}
36 changes: 31 additions & 5 deletions public/pages/LogTypes/components/LogTypeForm.tsx
Original file line number Diff line number Diff line change
@@ -12,13 +12,15 @@ import {
EuiFlexItem,
EuiFormRow,
EuiSpacer,
EuiSuperSelect,
EuiTextArea,
} from '@elastic/eui';
import { LogTypeItem } from '../../../../types';
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';

export interface LogTypeFormProps {
logTypeDetails: LogTypeItem;
@@ -40,17 +42,21 @@ export const LogTypeForm: React.FC<LogTypeFormProps> = ({
onConfirm,
}) => {
const [nameError, setNameError] = useState('');
const [categoryError, setCategoryError] = useState('');
const [categoryTouched, setCategoryTouched] = useState(false);

const updateErrors = (details = logTypeDetails) => {
const updateErrors = (details: LogTypeItem, onSubmit = false) => {
const nameInvalid = !validateName(details.name, LOG_TYPE_NAME_REGEX, false /* shouldTrim */);
const categoryInvalid = (categoryTouched || onSubmit) && !details.category;
setNameError(nameInvalid ? 'Invalid name' : '');
setCategoryError(categoryInvalid ? 'Select category to assign' : '');

return { nameInvalid };
return { nameInvalid, categoryInvalid };
};
const onConfirmClicked = () => {
const { nameInvalid } = updateErrors();
const { nameInvalid, categoryInvalid } = updateErrors(logTypeDetails, true);

if (nameInvalid) {
if (nameInvalid || categoryInvalid) {
notifications?.toasts.addDanger({
title: `Failed to ${confirmButtonText.toLowerCase()}`,
text: `Fix the marked errors.`,
@@ -83,7 +89,6 @@ export const LogTypeForm: React.FC<LogTypeFormProps> = ({
setLogTypeDetails(newLogType);
updateErrors(newLogType);
}}
placeholder="Enter name for the log type"
readOnly={!isEditMode}
disabled={isEditMode && !!logTypeDetails.detectionRulesCount}
/>
@@ -111,6 +116,27 @@ export const LogTypeForm: React.FC<LogTypeFormProps> = ({
readOnly={!isEditMode}
/>
</EuiFormRow>
<EuiSpacer />
<EuiFormRow label="Category" isInvalid={!!categoryError} error={categoryError}>
<EuiSuperSelect
options={logTypeCategoryOptions.map((option) => ({
...option,
disabled: !isEditMode || (isEditMode && !!logTypeDetails.detectionRulesCount),
}))}
valueOfSelected={logTypeDetails?.category}
onChange={(value) => {
const newLogType = {
...logTypeDetails,
category: value,
};
setCategoryTouched(true);
setLogTypeDetails(newLogType);
updateErrors(newLogType);
}}
hasDividers
itemLayoutAlign="top"
></EuiSuperSelect>
</EuiFormRow>
{isEditMode ? (
<EuiBottomBar>
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">
1 change: 1 addition & 0 deletions public/pages/LogTypes/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -21,4 +21,5 @@ export const defaultLogType: LogTypeBase = {
description: '',
source: 'Custom',
tags: null,
category: '',
};
17 changes: 17 additions & 0 deletions public/pages/LogTypes/utils/helpers.tsx
Original file line number Diff line number Diff line change
@@ -9,6 +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';

export const getLogTypesTableColumns = (
showDetails: (id: string) => void,
@@ -27,6 +28,11 @@ export const getLogTypesTableColumns = (
name: 'Description',
truncateText: false,
},
{
field: 'category',
name: 'Category',
truncateText: false,
},
{
field: 'source',
name: 'Source',
@@ -60,6 +66,17 @@ export const getLogTypesTableSearchConfig = (): Search => {
schema: true,
},
filters: [
{
type: 'field_value_selection',
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)),
},
{
type: 'field_value_selection',
field: 'source',
12 changes: 5 additions & 7 deletions public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx
Original file line number Diff line number Diff line change
@@ -23,15 +23,15 @@ import {
} from '@elastic/eui';
import { ContentPanel } from '../../../../components/ContentPanel';
import { FieldTextArray } from './components/FieldTextArray';
import { ruleStatus, ruleTypes } from '../../utils/constants';
import { ruleStatus } from '../../utils/constants';
import { AUTHOR_REGEX, validateDescription, validateName } from '../../../../utils/validation';
import { RuleEditorFormModel } from './RuleEditorFormModel';
import { FormSubmissionErrorToastNotification } from './FormSubmitionErrorToastNotification';
import { YamlRuleEditorComponent } from './components/YamlRuleEditorComponent/YamlRuleEditorComponent';
import { mapFormToRule, mapRuleToForm } from './mappers';
import { DetectionVisualEditor } from './DetectionVisualEditor';
import { useCallback } from 'react';
import { DataStore } from '../../../../store/DataStore';
import { getLogTypeOptions } from '../../../../utils/helpers';

export interface VisualRuleEditorProps {
initialValue: RuleEditorFormModel;
@@ -65,17 +65,15 @@ export const RuleEditorForm: React.FC<VisualRuleEditorProps> = ({
}) => {
const [selectedEditorType, setSelectedEditorType] = useState('visual');
const [isDetectionInvalid, setIsDetectionInvalid] = useState(false);
const [logTypeOptions, setLogTypeOptions] = useState(
ruleTypes.map(({ value, label }) => ({ value, label }))
);
const [logTypeOptions, setLogTypeOptions] = useState<any[]>([]);

const onEditorTypeChange = (optionId: string) => {
setSelectedEditorType(optionId);
};

const refreshLogTypeOptions = useCallback(async () => {
const logTypes = await DataStore.logTypes.getLogTypes();
setLogTypeOptions(logTypes.map(({ id, name }) => ({ value: id, label: name })));
const logTypeOptions = await getLogTypeOptions();
setLogTypeOptions(logTypeOptions);
}, []);

const validateTags = (fields: string[]) => {
2 changes: 1 addition & 1 deletion public/pages/Rules/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@

import { euiPaletteForStatus } from '@elastic/eui';

export const ruleTypes: { label: string; value: string; id: string }[] = [];
export const ruleTypes: { label: string; value: string; id: string; category: string }[] = [];

const paletteColors = euiPaletteForStatus(5);

19 changes: 9 additions & 10 deletions public/pages/Rules/utils/helpers.tsx
Original file line number Diff line number Diff line change
@@ -5,14 +5,18 @@

import { EuiBasicTableColumn, EuiBreadcrumb, EuiLink, EuiBadge } from '@elastic/eui';
import React from 'react';
import { errorNotificationToast } from '../../../utils/helpers';
import { ruleSeverity, ruleSource, ruleTypes } from './constants';
import {
errorNotificationToast,
formatRuleType,
getLogTypeFilterOptions,
} from '../../../utils/helpers';
import { ruleSeverity, ruleSource } from './constants';
import { Search } from '@opensearch-project/oui/src/eui_components/basic_table';
import { Rule } from '../../../../models/interfaces';
import { NotificationsStart } from 'opensearch-dashboards/public';
import { AUTHOR_REGEX, validateDescription, validateName } from '../../../utils/validation';
import { dump, load } from 'js-yaml';
import { BREADCRUMBS, DEFAULT_EMPTY_DATA } from '../../../utils/constants';
import { BREADCRUMBS } from '../../../utils/constants';
import { RuleItemInfoBase, RulesTableColumnFields } from '../../../../types';
import { getSeverityColor, getSeverityLabel } from '../../Correlations/utils/constants';

@@ -65,9 +69,7 @@ export const getRulesTableColumns = (
sortable: true,
width: '10%',
truncateText: true,
render: (category: string) =>
ruleTypes.find((ruleType) => ruleType.label.toLowerCase() === category)?.label ||
DEFAULT_EMPTY_DATA,
render: (category: string) => formatRuleType(category),
},
source: {
field: 'source',
@@ -107,10 +109,7 @@ export const getRulesTableSearchConfig = (): Search => {
field: 'category',
name: 'Log type',
multiSelect: 'or',
options: ruleTypes.map(({ value, label }) => ({
value,
name: label,
})),
options: getLogTypeFilterOptions(),
},
{
type: 'field_value_selection',
28 changes: 23 additions & 5 deletions public/store/LogTypeStore.ts
Original file line number Diff line number Diff line change
@@ -9,6 +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';

export class LogTypeStore {
constructor(private service: LogTypeService, private notifications: NotificationsStart) {}
@@ -53,16 +54,25 @@ export class LogTypeStore {
0,
ruleTypes.length,
...logTypes
.map((logType) => ({
label: logType.name,
value: logType.name,
id: logType.id,
.map(({ category, id, name }) => ({
label: name,
value: name,
id,
category,
}))
.sort((a, b) => {
return a.label < b.label ? -1 : a.label > b.label ? 1 : 0;
})
);

// Set log category types
for (const key in logTypesByCategories) {
delete logTypesByCategories[key];
}
logTypes.forEach((logType) => {
logTypesByCategories[logType.category] = logTypesByCategories[logType.category] || [];
logTypesByCategories[logType.category].push(logType);
});
return logTypes;
}

@@ -79,12 +89,20 @@ export class LogTypeStore {
return createRes.ok;
}

public async updateLogType({ id, name, description, source, tags }: LogType): Promise<boolean> {
public async updateLogType({
category,
id,
name,
description,
source,
tags,
}: LogType): Promise<boolean> {
const updateRes = await this.service.updateLogType(id, {
name,
description,
source,
tags,
category,
});

if (!updateRes.ok) {
29 changes: 28 additions & 1 deletion public/utils/constants.ts → public/utils/constants.tsx
Original file line number Diff line number Diff line change
@@ -3,10 +3,13 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { SimpleSavedObject } from 'opensearch-dashboards/public';
import { Detector, ServerResponse } from '../../types';
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';
export const MAX_RECENTLY_USED_TIME_RANGES = 5;
@@ -173,3 +176,27 @@ export const logTypesWithDashboards = new Set(['network', 'cloudtrail', 's3']);
export const pendingDashboardCreations: {
[detectorId: string]: undefined | Promise<void | ServerResponse<SimpleSavedObject<unknown>>>;
} = {};

const logTypeCategoryInfo = [
{ 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' },
{ name: 'Network Activity', description: 'DNS, HTTP, Email, SSH, FTP, DHCP, RDP' },
{ name: 'System Activity', description: 'System monitoring logs' },
{ 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 logTypesByCategories: { [category: string]: LogType[] } = {};
74 changes: 70 additions & 4 deletions public/utils/helpers.tsx
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ import {
import moment from 'moment';
import { PeriodSchedule } from '../../models/interfaces';
import React from 'react';
import { DEFAULT_EMPTY_DATA, scheduleUnitText } from './constants';
import { DEFAULT_EMPTY_DATA, logTypesByCategories, scheduleUnitText } from './constants';
import {
RuleItem,
RuleItemInfo,
@@ -31,6 +31,9 @@ import { OpenSearchService } from '../services';
import { ruleSeverity, ruleTypes } from '../pages/Rules/utils/constants';
import { Handler } from 'vega-tooltip';
import _ from 'lodash';
import { LogType } from '../../types';
import { DataStore } from '../store/DataStore';
import { LogCategoryOptionView } from '../components/Utility/LogCategoryOption';

export const parseStringsToOptions = (strings: string[]) => {
return strings.map((str) => ({ id: str, label: str }));
@@ -295,10 +298,15 @@ export const getPlugins = async (opensearchService: OpenSearchService) => {
};

export const formatRuleType = (matchingRuleType: string) => {
return (
ruleTypes.find((ruleType) => ruleType.label.toLowerCase() === matchingRuleType.toLowerCase())
?.label || DEFAULT_EMPTY_DATA
const logType = ruleTypes.find(
(ruleType) => ruleType.label.toLowerCase() === matchingRuleType.toLowerCase()
);

if (logType) {
return `${logType.category}: ${_.capitalize(logType.label)}`;
}

return DEFAULT_EMPTY_DATA;
};

export const getSeverityBadge = (severity: string) => {
@@ -309,3 +317,61 @@ export const getSeverityBadge = (severity: string) => {
</EuiBadge>
);
};

export function formatToLogTypeOptions(logTypesByCategories: { [category: string]: LogType[] }) {
return Object.entries(logTypesByCategories)
.map(([category, logTypes]) => {
return {
label: category,
value: category,
options: logTypes
.map(({ name }) => ({
label: name,
value: name.toLowerCase(),
}))
.sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0)),
};
})
.sort((a, b) => {
if (a.label === 'Other') {
return 1;
} else if (b.label === 'Other') {
return -1;
} else {
return a.label < b.label ? -1 : a.label > b.label ? 1 : 0;
}
});
}

export async function getLogTypeOptions() {
await DataStore.logTypes.getLogTypes();
return formatToLogTypeOptions(logTypesByCategories);
}

export function getLogTypeFilterOptions() {
const options: any[] = [];
formatToLogTypeOptions(logTypesByCategories).forEach((categoryData) => {
const categoryName = categoryData.label;
const logTypes = categoryData.options;

for (let i = 0; i < logTypes.length; i++) {
if (i === 0) {
options.push({
value: logTypes.map((logType) => logType.value).join(' or '),
view: <LogCategoryOptionView categoryName={categoryName} />,
});
}

options.push({
value: logTypes[i].value,
view: (
<span className="euiFlexItem euiFilterSelectItem__content" style={{ paddingLeft: 20 }}>
{_.capitalize(logTypes[i].label)}
</span>
),
});
}
});

return options;
}
1 change: 1 addition & 0 deletions types/LogTypes.ts
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ export interface LogTypeBase {
name: string;
description: string;
source: string;
category: string;
tags: {
correlation_id: number;
} | null;

0 comments on commit 5d3afed

Please sign in to comment.