Skip to content

Commit 3f45498

Browse files
authored
Updated field mapping UX; disabled windows run for cypress (opensearch-project#307)
* updated field mapping UX; disabled windows run for cypress Signed-off-by: Amardeepsingh Siglani <[email protected]> * updated workflow file Signed-off-by: Amardeepsingh Siglani <[email protected]> * added timestamp field to list of unmapped fields Signed-off-by: Amardeepsingh Siglani <[email protected]> * updated cypress test Signed-off-by: Amardeepsingh Siglani <[email protected]> Signed-off-by: Amardeepsingh Siglani <[email protected]>
1 parent a82125a commit 3f45498

File tree

7 files changed

+264
-58
lines changed

7 files changed

+264
-58
lines changed

.github/workflows/cypress-workflow.yml

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@ jobs:
1515
name: Run Cypress E2E tests
1616
strategy:
1717
matrix:
18-
os: [ubuntu-latest, windows-latest]
18+
os: [ubuntu-latest]
1919
include:
20-
- os: windows-latest
21-
cypress_cache_folder: ~/AppData/Local/Cypress/Cache
2220
- os: ubuntu-latest
2321
cypress_cache_folder: ~/.cache/Cypress
2422
runs-on: ${{ matrix.os }}
@@ -34,10 +32,6 @@ jobs:
3432
# TODO: Parse this from security analytics plugin (https://github.com/opensearch-project/security-analytics/issues/170)
3533
java-version: 11
3634

37-
- name: Enable longer filenames
38-
if: ${{ matrix.os == 'windows-latest' }}
39-
run: git config --system core.longpaths true
40-
4135
- name: Checkout security analytics
4236
uses: actions/checkout@v2
4337
with:
@@ -93,14 +87,7 @@ jobs:
9387
yarn start --no-base-path --no-watch &
9488
shell: bash
9589

96-
# Window is slow so wait longer
97-
- name: Sleep until OSD server starts - windows
98-
if: ${{ matrix.os == 'windows-latest' }}
99-
run: Start-Sleep -s 400
100-
shell: powershell
101-
102-
- name: Sleep until OSD server starts - non-windows
103-
if: ${{ matrix.os != 'windows-latest' }}
90+
- name: Sleep until OSD server starts
10491
run: sleep 300
10592
shell: bash
10693

cypress/integration/1_detectors.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ describe('Detectors', () => {
6969
cy.get('button').contains('Next').click({ force: true });
7070

7171
// Check that correct page now showing
72-
cy.contains('Required field mappings');
72+
cy.contains('Configure field mapping');
7373

7474
// Select appropriate names to map fields to
7575
for (let field_name in sample_field_mappings.properties) {

public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx

Lines changed: 90 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,20 @@
55

66
import React, { Component } from 'react';
77
import { RouteComponentProps } from 'react-router-dom';
8-
import { EuiSpacer, EuiTitle, EuiText } from '@elastic/eui';
8+
import {
9+
EuiSpacer,
10+
EuiTitle,
11+
EuiText,
12+
EuiCallOut,
13+
EuiAccordion,
14+
EuiHorizontalRule,
15+
EuiPanel,
16+
} from '@elastic/eui';
917
import FieldMappingsTable from '../components/RequiredFieldMapping';
1018
import { createDetectorSteps } from '../../../utils/constants';
1119
import { ContentPanel } from '../../../../../components/ContentPanel';
1220
import { Detector, FieldMapping } from '../../../../../../models/interfaces';
13-
import { EMPTY_FIELD_MAPPINGS } from '../utils/constants';
21+
import { EMPTY_FIELD_MAPPINGS_VIEW } from '../utils/constants';
1422
import { DetectorCreationStep } from '../../../models/types';
1523
import { GetFieldMappingViewResponse } from '../../../../../../server/models/interfaces';
1624
import FieldMappingService from '../../../../../services/FieldMappingService';
@@ -49,7 +57,7 @@ export default class ConfigureFieldMapping extends Component<
4957
});
5058
this.state = {
5159
loading: props.loading || false,
52-
mappingsData: EMPTY_FIELD_MAPPINGS,
60+
mappingsData: EMPTY_FIELD_MAPPINGS_VIEW,
5361
createdMappings,
5462
invalidMappingFieldNames: [],
5563
};
@@ -70,17 +78,21 @@ export default class ConfigureFieldMapping extends Component<
7078
Object.keys(mappingsView.response.properties).forEach((ruleFieldName) => {
7179
existingMappings[ruleFieldName] = mappingsView.response.properties[ruleFieldName].path;
7280
});
73-
this.setState({ createdMappings: existingMappings, mappingsData: mappingsView.response });
81+
this.setState({
82+
createdMappings: existingMappings,
83+
mappingsData: {
84+
...mappingsView.response,
85+
unmapped_field_aliases: [
86+
'timestamp',
87+
...(mappingsView.response.unmapped_field_aliases || []),
88+
],
89+
},
90+
});
7491
this.updateMappingSharedState(existingMappings);
7592
}
7693
this.setState({ loading: false });
7794
};
7895

79-
validateMappings(mappings: ruleFieldToIndexFieldMap): boolean {
80-
// TODO: Implement validation
81-
return true; //allFieldsMapped; // && allAliasesUnique;
82-
}
83-
8496
/**
8597
* Returns the fieldName(s) that have duplicate alias assigned to them
8698
*/
@@ -110,8 +122,7 @@ export default class ConfigureFieldMapping extends Component<
110122
invalidMappingFieldNames: invalidMappingFieldNames,
111123
});
112124
this.updateMappingSharedState(newMappings);
113-
const mappingsValid = this.validateMappings(newMappings);
114-
this.props.updateDataValidState(DetectorCreationStep.CONFIGURE_FIELD_MAPPING, mappingsValid);
125+
this.props.updateDataValidState(DetectorCreationStep.CONFIGURE_FIELD_MAPPING, true);
115126
};
116127

117128
updateMappingSharedState = (createdMappings: ruleFieldToIndexFieldMap) => {
@@ -126,42 +137,52 @@ export default class ConfigureFieldMapping extends Component<
126137
};
127138

128139
render() {
129-
const { isEdit } = this.props;
130140
const { loading, mappingsData, createdMappings, invalidMappingFieldNames } = this.state;
131141
const existingMappings: ruleFieldToIndexFieldMap = {
132142
...createdMappings,
133143
};
134-
const ruleFields = [...(mappingsData.unmapped_field_aliases || [])];
135-
const indexFields = [...(mappingsData.unmapped_index_fields || [])];
136144

145+
// read only data
146+
const mappedRuleFields: string[] = [];
147+
const mappedLogFields: string[] = [];
137148
Object.keys(mappingsData.properties).forEach((ruleFieldName) => {
138-
existingMappings[ruleFieldName] = mappingsData.properties[ruleFieldName].path;
139-
ruleFields.unshift(ruleFieldName);
140-
indexFields.unshift(mappingsData.properties[ruleFieldName].path);
149+
mappedRuleFields.unshift(ruleFieldName);
150+
mappedLogFields.unshift(mappingsData.properties[ruleFieldName].path);
141151
});
142152

153+
// edit data
154+
const ruleFields = [...(mappingsData.unmapped_field_aliases || [])];
155+
const indexFields = [...(mappingsData.unmapped_index_fields || [])];
156+
143157
return (
144158
<div>
145-
{!isEdit && (
146-
<>
147-
<EuiTitle size={'m'}>
148-
<h3>{createDetectorSteps[DetectorCreationStep.CONFIGURE_FIELD_MAPPING].title}</h3>
149-
</EuiTitle>
159+
<EuiTitle size={'m'}>
160+
<h3>{createDetectorSteps[DetectorCreationStep.CONFIGURE_FIELD_MAPPING].title}</h3>
161+
</EuiTitle>
150162

151-
<EuiText size="s" color="subdued">
152-
To perform threat detection, known field names from your log data source are
153-
automatically mapped to rule field names. Additional fields that may require manual
154-
mapping will be shown below.
155-
</EuiText>
163+
<EuiText size="s" color="subdued">
164+
To perform threat detection, known field names from your log data source are automatically
165+
mapped to rule field names. Additional fields that may require manual mapping will be
166+
shown below.
167+
</EuiText>
156168

157-
<EuiSpacer size={'m'} />
158-
</>
159-
)}
169+
<EuiSpacer size={'m'} />
160170

161-
{ruleFields.length > 0 && (
171+
{ruleFields.length > 0 ? (
162172
<>
163-
<ContentPanel title={`Required field mappings (${ruleFields.length})`} titleSize={'m'}>
173+
<EuiCallOut
174+
title={`${ruleFields.length} rule fields may need manual mapping`}
175+
color={'warning'}
176+
>
177+
<p>
178+
To generate accurate findings, we recommend mapping the following security rules
179+
fields with the log field from your data source.
180+
</p>
181+
</EuiCallOut>
182+
<EuiSpacer size={'m'} />
183+
<ContentPanel title={`Manual field mappings (${ruleFields.length})`} titleSize={'m'}>
164184
<FieldMappingsTable<MappingViewType.Edit>
185+
{...this.props}
165186
loading={loading}
166187
ruleFields={ruleFields}
167188
indexFields={indexFields}
@@ -171,12 +192,48 @@ export default class ConfigureFieldMapping extends Component<
171192
invalidMappingFieldNames,
172193
onMappingCreation: this.onMappingCreation,
173194
}}
174-
{...this.props}
175195
/>
176196
</ContentPanel>
177197
<EuiSpacer size={'m'} />
178198
</>
199+
) : (
200+
<>
201+
<EuiCallOut title={'We have automatically mapped all fields'} color={'success'}>
202+
<p>
203+
Your data source(s) have been mapped with all security rule fields. No action is
204+
needed.
205+
</p>
206+
</EuiCallOut>
207+
<EuiSpacer size={'m'} />
208+
</>
179209
)}
210+
211+
<EuiPanel>
212+
<EuiAccordion
213+
buttonContent={
214+
<div data-test-subj="mapped-fields-btn">
215+
<EuiTitle>
216+
<h4>{`View mapped fields (${mappedRuleFields.length})`}</h4>
217+
</EuiTitle>
218+
</div>
219+
}
220+
buttonProps={{ style: { paddingLeft: '10px', paddingRight: '10px' } }}
221+
id={'mappedFieldsAccordion'}
222+
initialIsOpen={false}
223+
>
224+
<EuiHorizontalRule margin={'xs'} />
225+
<FieldMappingsTable<MappingViewType.Readonly>
226+
{...this.props}
227+
loading={loading}
228+
ruleFields={mappedRuleFields}
229+
indexFields={mappedLogFields}
230+
mappingProps={{
231+
type: MappingViewType.Readonly,
232+
}}
233+
/>
234+
</EuiAccordion>
235+
</EuiPanel>
236+
<EuiSpacer size={'m'} />
180237
</div>
181238
);
182239
}

public/pages/CreateDetector/components/ConfigureFieldMapping/utils/constants.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,22 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { GetFieldMappingViewResponse } from '../../../../../../server/models/interfaces';
6+
import {
7+
FieldMappingPropertyMap,
8+
GetFieldMappingViewResponse,
9+
} from '../../../../../../server/models/interfaces';
710

811
export const STATUS_ICON_PROPS = {
912
unmapped: { type: 'alert', color: 'danger' },
1013
mapped: { type: 'checkInCircleFilled', color: 'success' },
1114
};
1215

13-
export const EMPTY_FIELD_MAPPINGS: GetFieldMappingViewResponse = {
16+
export const EMPTY_FIELD_MAPPINGS_VIEW: GetFieldMappingViewResponse = {
1417
properties: {},
1518
unmapped_field_aliases: [],
1619
unmapped_index_fields: [],
1720
};
21+
22+
export const EMPTY_FIELD_MAPPINGS: FieldMappingPropertyMap = {
23+
properties: {},
24+
};

public/pages/Detectors/components/FieldMappingsView/FieldMappingsView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ export const FieldMappingsView: React.FC<FieldMappingsViewProps> = ({
5252
async (indexName: string) => {
5353
const getMappingRes = await services?.fieldMappingService.getMappings(indexName);
5454
if (getMappingRes?.ok) {
55-
const mappings = getMappingRes.response[detector.detector_type.toLowerCase()];
56-
if (mappings) {
55+
const mappingsData = getMappingRes.response[indexName];
56+
if (mappingsData) {
5757
let items: FieldMappingsTableItem[] = [];
58-
Object.entries(mappings.mappings.properties).forEach((entry) => {
58+
Object.entries(mappingsData.mappings.properties).forEach((entry) => {
5959
items.push({
6060
ruleFieldName: entry[0],
6161
logFieldName: entry[1].path,

public/pages/Detectors/components/UpdateFieldMappings/UpdateFieldMappings.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import React, { Component } from 'react';
77
import { RouteComponentProps } from 'react-router-dom';
88
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
9-
import ConfigureFieldMapping from '../../../CreateDetector/components/ConfigureFieldMapping';
109
import { Detector, FieldMapping } from '../../../../../models/interfaces';
1110
import FieldMappingService from '../../../../services/FieldMappingService';
1211
import { DetectorHit, SearchDetectorsResponse } from '../../../../../server/models/interfaces';
@@ -15,6 +14,7 @@ import { DetectorsService } from '../../../../services';
1514
import { ServerResponse } from '../../../../../server/models/types';
1615
import { NotificationsStart } from 'opensearch-dashboards/public';
1716
import { errorNotificationToast, successNotificationToast } from '../../../../utils/helpers';
17+
import EditFieldMappings from '../../containers/FieldMappings/EditFieldMapping';
1818

1919
export interface UpdateFieldMappingsProps
2020
extends RouteComponentProps<any, any, { detectorHit: DetectorHit }> {
@@ -159,14 +159,12 @@ export default class UpdateFieldMappings extends Component<
159159
<EuiSpacer size={'xxl'} />
160160

161161
{!loading && (
162-
<ConfigureFieldMapping
162+
<EditFieldMappings
163163
{...this.props}
164-
isEdit={true}
165164
detector={detector}
166165
fieldMappings={fieldMappings}
167166
filedMappingService={filedMappingService}
168167
replaceFieldMappings={this.replaceFieldMappings}
169-
updateDataValidState={() => {}}
170168
loading={loading}
171169
/>
172170
)}

0 commit comments

Comments
 (0)