Skip to content

Commit

Permalink
Improved field mapping UX (opensearch-project#330) (opensearch-projec…
Browse files Browse the repository at this point in the history
…t#337)

* fixed issues; improved UX

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* removed unwanted change

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* override default mapping with created

Signed-off-by: Amardeepsingh Siglani <[email protected]>

Signed-off-by: Amardeepsingh Siglani <[email protected]>
(cherry picked from commit d0e184d)

Co-authored-by: Amardeepsingh Siglani <[email protected]>
Signed-off-by: AWSHurneyt <[email protected]>
  • Loading branch information
2 people authored and AWSHurneyt committed Oct 12, 2023
1 parent 09c6449 commit d857a5f
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import React, { Component } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import {
CriteriaWithPagination,
EuiBasicTableColumn,
EuiEmptyPrompt,
EuiIcon,
Expand Down Expand Up @@ -42,12 +43,27 @@ interface FieldMappingsTableProps<T extends MappingViewType> extends RouteCompon
mappingProps: MappingProps[T];
}

interface FieldMappingsTableState {}
interface FieldMappingsTableState {
pageIndex: number;
}

export default class FieldMappingsTable<T extends MappingViewType> extends Component<
FieldMappingsTableProps<T>,
FieldMappingsTableState
> {
constructor(props: FieldMappingsTableProps<T>) {
super(props);
this.state = {
pageIndex: 0,
};
}

private onTableChange = (nextValues: CriteriaWithPagination<FieldMappingsTableItem>) => {
this.setState({
pageIndex: nextValues.page.index,
});
};

render() {
const { loading, indexFields, ruleFields } = this.props;
let items: FieldMappingsTableItem[];
Expand All @@ -70,7 +86,6 @@ export default class FieldMappingsTable<T extends MappingViewType> extends Compo
{
field: 'ruleFieldName',
name: 'Rule field name',
sortable: true,
dataType: 'string',
width: '25%',
render: (ruleFieldName: string) => ruleFieldName || DEFAULT_EMPTY_DATA,
Expand All @@ -85,7 +100,6 @@ export default class FieldMappingsTable<T extends MappingViewType> extends Compo
{
field: 'logFieldName',
name: 'Log field name',
sortable: true,
dataType: 'string',
width: '45%',
render: (logFieldName: string, entry: FieldMappingsTableItem) => {
Expand Down Expand Up @@ -150,9 +164,12 @@ export default class FieldMappingsTable<T extends MappingViewType> extends Compo
loading={loading}
items={items}
columns={columns}
pagination={true}
pagination={{
pageIndex: this.state.pageIndex,
}}
sorting={sorting}
isSelectable={false}
onTableChange={this.onTableChange}
message={
<EuiEmptyPrompt
style={{ maxWidth: '45em' }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,13 @@ export default class ConfigureFieldMapping extends Component<
if (mappingsView.ok) {
const existingMappings = { ...this.state.createdMappings };
Object.keys(mappingsView.response.properties).forEach((ruleFieldName) => {
existingMappings[ruleFieldName] = mappingsView.response.properties[ruleFieldName].path;
existingMappings[ruleFieldName] =
this.state.createdMappings[ruleFieldName] ||
mappingsView.response.properties[ruleFieldName].path;
});
this.setState({
createdMappings: existingMappings,
mappingsData: {
...mappingsView.response,
unmapped_field_aliases: [
'timestamp',
...(mappingsView.response.unmapped_field_aliases || []),
],
},
mappingsData: mappingsView.response,
});
this.updateMappingSharedState(existingMappings);
}
Expand All @@ -111,11 +107,16 @@ export default class ConfigureFieldMapping extends Component<
return invalidFields;
}

onMappingCreation = (ruleFieldName: string, indxFieldName: string): void => {
onMappingCreation = (ruleFieldName: string, indexFieldName: string): void => {
const newMappings: ruleFieldToIndexFieldMap = {
...this.state.createdMappings,
[ruleFieldName]: indxFieldName,
[ruleFieldName]: indexFieldName,
};

if (!indexFieldName) {
delete newMappings[ruleFieldName];
}

const invalidMappingFieldNames = this.getInvalidMappingFieldNames(newMappings);
this.setState({
createdMappings: newMappings,
Expand All @@ -142,17 +143,28 @@ export default class ConfigureFieldMapping extends Component<
...createdMappings,
};

// read only data
const mappedRuleFields: string[] = [];
const mappedLogFields: string[] = [];
const logFields: Set<string> = new Set(mappingsData.unmapped_index_fields || []);
let pendingCount = mappingsData.unmapped_field_aliases?.length || 0;
const unmappedRuleFields = [...(mappingsData.unmapped_field_aliases || [])];

Object.keys(mappingsData.properties).forEach((ruleFieldName) => {
mappedRuleFields.unshift(ruleFieldName);
mappedLogFields.unshift(mappingsData.properties[ruleFieldName].path);

// Need this check to avoid adding undefined value
// When user removes existing mapping for default mapped values, the mapping will be undefined
if (existingMappings[ruleFieldName]) {
logFields.add(existingMappings[ruleFieldName]);
}
});

// edit data
const ruleFields = [...(mappingsData.unmapped_field_aliases || [])];
const indexFields = [...(mappingsData.unmapped_index_fields || [])];
Object.keys(existingMappings).forEach((mappedRuleField) => {
if (unmappedRuleFields.includes(mappedRuleField)) {
pendingCount--;
}
});

const indexFieldOptions = Array.from(logFields);

return (
<div>
Expand All @@ -168,24 +180,31 @@ export default class ConfigureFieldMapping extends Component<

<EuiSpacer size={'m'} />

{ruleFields.length > 0 ? (
{unmappedRuleFields.length > 0 ? (
<>
<EuiCallOut
title={`${ruleFields.length} rule fields may need manual mapping`}
color={'warning'}
>
<p>
To generate accurate findings, we recommend mapping the following security rules
fields with the log field from your data source.
</p>
</EuiCallOut>
{pendingCount > 0 ? (
<EuiCallOut
title={`${pendingCount} rule fields may need manual mapping`}
color={'warning'}
>
<p>
To generate accurate findings, we recommend mapping the following security rules
fields with the log fields in your data source.
</p>
</EuiCallOut>
) : (
<EuiCallOut title={`All rule fields have been mapped`} color={'success'}>
<p>Your data source have been mapped with all security rule fields.</p>
</EuiCallOut>
)}

<EuiSpacer size={'m'} />
<ContentPanel title={`Manual field mappings (${ruleFields.length})`} titleSize={'m'}>
<ContentPanel title={`Pending field mappings`} titleSize={'m'}>
<FieldMappingsTable<MappingViewType.Edit>
{...this.props}
loading={loading}
ruleFields={ruleFields}
indexFields={indexFields}
ruleFields={unmappedRuleFields}
indexFields={indexFieldOptions}
mappingProps={{
type: MappingViewType.Edit,
existingMappings,
Expand Down Expand Up @@ -213,7 +232,7 @@ export default class ConfigureFieldMapping extends Component<
buttonContent={
<div data-test-subj="mapped-fields-btn">
<EuiTitle>
<h4>{`View mapped fields (${mappedRuleFields.length})`}</h4>
<h4>{`Default mapped fields (${mappedRuleFields.length})`}</h4>
</EuiTitle>
</div>
}
Expand All @@ -222,13 +241,16 @@ export default class ConfigureFieldMapping extends Component<
initialIsOpen={false}
>
<EuiHorizontalRule margin={'xs'} />
<FieldMappingsTable<MappingViewType.Readonly>
<FieldMappingsTable<MappingViewType.Edit>
{...this.props}
loading={loading}
ruleFields={mappedRuleFields}
indexFields={mappedLogFields}
indexFields={indexFieldOptions}
mappingProps={{
type: MappingViewType.Readonly,
type: MappingViewType.Edit,
existingMappings,
invalidMappingFieldNames,
onMappingCreation: this.onMappingCreation,
}}
/>
</EuiAccordion>
Expand Down

0 comments on commit d857a5f

Please sign in to comment.