Skip to content

Commit

Permalink
Merge branch 'main' into create-vpc-dashboard-suing-saved-object-client
Browse files Browse the repository at this point in the history
  • Loading branch information
amsiglan authored Jan 31, 2023
2 parents b4f8540 + a137a80 commit 49b1b68
Show file tree
Hide file tree
Showing 16 changed files with 383 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cypress-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches:
- "*"
env:
OPENSEARCH_DASHBOARDS_VERSION: '2.5'
OPENSEARCH_DASHBOARDS_VERSION: '2.5.0'
OPENSEARCH_VERSION: '2.5.0-SNAPSHOT'
SECURITY_ANALYTICS_BRANCH: '2.5'
jobs:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches:
- "*"
env:
OPENSEARCH_DASHBOARDS_VERSION: '2.5'
OPENSEARCH_DASHBOARDS_VERSION: '2.5.0'
jobs:
tests:
name: Run unit tests
Expand Down
29 changes: 29 additions & 0 deletions cypress/integration/4_findings.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,35 @@ describe('Findings', () => {
cy.get('.euiFlexItem--flexGrowZero > .euiButtonIcon').click({ force: true });
});

it('displays finding details and create an index pattern from flyout', () => {
// filter table to show only sample_detector findings
cy.get(`input[placeholder="Search findings"]`).ospSearch('sample_detector');

// Click findingId to trigger Finding details flyout
cy.getTableFirstRow('[data-test-subj="finding-details-flyout-button"]').then(($el) => {
cy.get($el).click({ force: true });
});

cy.get('[data-test-subj="finding-details-flyout-view-surrounding-documents"]')
.contains('View surrounding documents')
.click({ force: true });

cy.contains('Create index pattern to view documents');

cy.get(
`[data-test-subj="index_pattern_time_field_dropdown"] [data-test-subj="comboBoxSearchInput"]`
).type('EventTime');

cy.get('[data-test-subj="index_pattern_form_submit_button"]')
.contains('Create index pattern')
.click({ force: true });

cy.contains('cypress-test-windows* has been successfully created');

// Close Flyout
cy.get('.euiFlexItem--flexGrowZero > .euiButtonIcon').click({ force: true });
});

it('allows user to view details about rules that were triggered', () => {
// filter table to show only sample_detector findings
cy.get(`input[placeholder="Search findings"]`).ospSearch('sample_detector');
Expand Down
2 changes: 1 addition & 1 deletion opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "2.5.0.0",
"opensearchDashboardsVersion": "2.5.0",
"configPath": ["opensearch_security_analytics"],
"requiredPlugins": [],
"requiredPlugins": ["data"],
"server": true,
"ui": true
}
2 changes: 2 additions & 0 deletions public/models/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
IndexService,
RuleService,
NotificationsService,
IndexPatternsService,
} from '../services';

export interface BrowserServices {
Expand All @@ -25,6 +26,7 @@ export interface BrowserServices {
ruleService: RuleService;
notificationsService: NotificationsService;
savedObjectsService: ISavedObjectsService;
indexPatternsService: IndexPatternsService;
}

export interface RuleOptions {
Expand Down
6 changes: 5 additions & 1 deletion public/pages/CreateDetector/containers/CreateDetector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ export default class CreateDetector extends Component<CreateDetectorProps, Creat
}

componentDidMount(): void {
this.context.chrome.setBreadcrumbs([BREADCRUMBS.SECURITY_ANALYTICS, BREADCRUMBS.DETECTORS]);
this.context.chrome.setBreadcrumbs([
BREADCRUMBS.SECURITY_ANALYTICS,
BREADCRUMBS.DETECTORS,
BREADCRUMBS.DETECTORS_CREATE,
]);
this.setupRulesState();
this.getPlugins();
}
Expand Down
210 changes: 210 additions & 0 deletions public/pages/Findings/components/CreateIndexPatternForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useEffect, useState, useCallback } from 'react';
import { Formik, Form, FormikErrors } from 'formik';
import {
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiFieldText,
EuiButton,
EuiSpacer,
EuiComboBox,
EuiText,
EuiCallOut,
} from '@elastic/eui';
import { IndexPatternsService } from '../../../services';

const ILLEGAL_CHARACTERS = [' ', '\\', '/', '?', '"', '<', '>', '|'];

const containsIllegalCharacters = (pattern: string) => {
return ILLEGAL_CHARACTERS.some((char) => pattern.includes(char));
};

export interface CreateIndexPatternFormModel {
name: string;
timeField: string;
}

export interface CreateIndexPatternFormProps {
initialValue: {
name: string;
};
created: (values: string) => void;
close: () => void;
indexPatternsService: IndexPatternsService;
}

export const CreateIndexPatternForm: React.FC<CreateIndexPatternFormProps> = ({
initialValue,
created,
close,
indexPatternsService,
}) => {
const [timeFields, setTimeFields] = useState<string[]>([]);
const [createdIndex, setCreatedIndex] = useState<{ id?: string; title: string }>();

const getTimeFields = useCallback(
async (name: string): Promise<string[]> => {
if (!indexPatternsService) {
return [];
}

return indexPatternsService
.getFieldsForWildcard({
pattern: `${name}`,
metaFields: ['_source', '_id', '_type', '_index', '_score'],
params: {},
})
.then((res) => {
return res.filter((f) => f.type === 'date').map((f) => f.name);
})
.catch(() => {
return [];
});
},
[initialValue]
);

useEffect(() => {
getTimeFields(initialValue.name).then((fields) => {
setTimeFields(fields);
});
}, [initialValue.name]);

return createdIndex ? (
<>
<EuiCallOut title={`${createdIndex?.title} has been successfully created`} color="success">
<p>You may now view surrounding documents within the index</p>
</EuiCallOut>
<EuiSpacer />
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButton
fill
onClick={() => {
created(createdIndex?.id || '');
}}
>
View surrounding documents
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</>
) : (
<Formik
initialValues={{ ...initialValue, timeField: '' }}
validate={(values) => {
const errors: FormikErrors<CreateIndexPatternFormModel> = {};

if (!values.name) {
errors.name = 'Index patter name is required';
}

if (!values.timeField) {
errors.timeField = 'Time field is required';
}

if (containsIllegalCharacters(values.name)) {
errors.name =
'A index pattern cannot contain spaces or the characters: , /, ?, ", <, >, |';
}

return errors;
}}
onSubmit={async (values, { setSubmitting }) => {
try {
const newIndex = await indexPatternsService.createAndSave({
title: values.name,
timeFieldName: values.timeField,
});
setCreatedIndex({ id: newIndex.id, title: newIndex.title });
} catch (e) {
console.warn(e);
}
setSubmitting(false);
}}
>
{(props) => (
<Form>
<EuiText>
An index pattern is required to view all surrounding documents within the index. Create
an index pattern to continue.
</EuiText>
<EuiSpacer />
<EuiFormRow
label={
<EuiText size={'s'}>
<strong>Specify index pattern name</strong>
</EuiText>
}
isInvalid={props.touched.name && !!props.errors?.name}
error={props.errors.name}
>
<EuiFieldText
isInvalid={props.touched.name && !!props.errors.name}
placeholder="Enter index pattern name"
data-test-subj={'index_pattern_name_field'}
onChange={async (e) => {
props.handleChange('name')(e);
const fields = await getTimeFields(e.target.value);
setTimeFields(fields);
props.setFieldValue('timeField', '');
}}
onBlur={props.handleBlur('name')}
value={props.values.name}
/>
</EuiFormRow>

<EuiFormRow
label={
<EuiText size={'s'}>
<strong>Time filed</strong>
</EuiText>
}
isInvalid={props.touched.timeField && !!props.errors?.timeField}
error={props.errors.timeField}
>
<EuiComboBox
isInvalid={props.touched.timeField && !!props.errors.timeField}
placeholder="Select a time field"
data-test-subj={'index_pattern_time_field_dropdown'}
options={timeFields.map((field: string) => ({ value: field, label: field }))}
singleSelection={{ asPlainText: true }}
onChange={(e) => {
props.handleChange('timeField')(e[0]?.value ? e[0].value : '');
}}
onBlur={props.handleBlur('timeField')}
selectedOptions={
props.values.timeField
? [{ value: props.values.timeField, label: props.values.timeField }]
: []
}
/>
</EuiFormRow>

<EuiSpacer />

<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButton onClick={() => close()}>Cancel</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj={'index_pattern_form_submit_button'}
isLoading={props.isSubmitting}
fill
onClick={() => props.handleSubmit()}
>
Create index pattern
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</Form>
)}
</Formik>
);
};
Loading

0 comments on commit 49b1b68

Please sign in to comment.