Skip to content

Commit

Permalink
Improve errors and rename asset classification to criticality
Browse files Browse the repository at this point in the history
  • Loading branch information
machadoum committed Apr 8, 2024
1 parent e2cf892 commit 52e3082
Show file tree
Hide file tree
Showing 16 changed files with 441 additions and 134 deletions.
4 changes: 2 additions & 2 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ export const BLOCKLIST_PATH = `${MANAGEMENT_PATH}/blocklist` as const;
export const RESPONSE_ACTIONS_HISTORY_PATH = `${MANAGEMENT_PATH}/response_actions_history` as const;
export const ENTITY_ANALYTICS_PATH = '/entity_analytics' as const;
export const ENTITY_ANALYTICS_MANAGEMENT_PATH = `/entity_analytics_management` as const;
export const ENTITY_ANALYTICS_ASSET_CLASSIFICATION_PATH =
`/entity_analytics_asset_classification` as const;
export const ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH =
`/entity_analytics_asset_criticality` as const;
export const APP_OVERVIEW_PATH = `${APP_PATH}${OVERVIEW_PATH}` as const;
export const APP_LANDING_PATH = `${APP_PATH}${LANDING_PATH}` as const;
export const APP_DETECTION_RESPONSE_PATH = `${APP_PATH}${DETECTION_RESPONSE_PATH}` as const;
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/security_solution/public/app/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ export const ENTITY_ANALYTICS_RISK_SCORE = i18n.translate(
}
);

export const ASSET_CRITICALITY = i18n.translate(
'xpack.securitySolution.navigation.entityRiskScore',
{
defaultMessage: 'Asset criticality',
}
);

export const DETECTION_RESPONSE = i18n.translate(
'xpack.securitySolution.navigation.detectionResponse',
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { SVGProps } from 'react';
import React from 'react';
export const IconAssetCriticality: React.FC<SVGProps<SVGSVGElement>> = ({ ...props }) => (
<svg
fill="none"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 32 32"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4 24C6.20928 24 8 22.2093 8 20C8 17.7907 6.20928 16 4 16C1.79072 16 0 17.7907 0 20C0 22.2093 1.79072 24 4 24ZM4 18C5.10472 18 6 18.8953 6 20C6 21.1047 5.10472 22 4 22C2.89528 22 2 21.1047 2 20C2 18.8953 2.89528 18 4 18Z"
fill="#535766"
/>
<path d="M3 26H5V31H3V26Z" fill="#00BFB3" />
<path d="M3 1H5V17H3V1Z" fill="#535766" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 14C18.2093 14 20 12.2093 20 10C20 7.79072 18.2093 6 16 6C13.7907 6 12 7.79072 12 10C12 12.2093 13.7907 14 16 14ZM16 8C17.1047 8 18 8.89528 18 10C18 11.1047 17.1047 12 16 12C14.8953 12 14 11.1047 14 10C14 8.89528 14.8953 8 16 8Z"
fill="#535766"
/>
<path d="M15 16H17V31H15V16Z" fill="#00BFB3" />
<path d="M15 1H17V7H15V1Z" fill="#535766" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M28 24C30.2093 24 32 22.2093 32 20C32 17.7907 30.2093 16 28 16C25.7907 16 24 17.7907 24 20C24 22.2093 25.7907 24 28 24ZM28 18C29.1047 18 30 18.8953 30 20C30 21.1047 29.1047 22 28 22C26.8953 22 26 21.1047 26 20C26 18.8953 26.8953 18 28 18Z"
fill="#535766"
/>
<path d="M27 26H29V31H27V26Z" fill="#00BFB3" />
<path d="M27 1H29V17H27V1Z" fill="#535766" />
</svg>
);
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const coreMock = {
settings: {
client: {
get: () => {},
get$: () => new Subject(),
set: () => {},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ export const useEntityAnalyticsRoutes = () => {
version: '1',
method: 'POST',
headers: {
// 'Content-Type': 'text/csv',
'Content-Type': undefined,
},
body,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@ import React from 'react';
import { ThemeProvider } from 'styled-components';
import { euiLightVars } from '@kbn/ui-theme';
import { TestProvider } from '@kbn/expandable-flyout/src/test/provider';
import { EuiPanel, EuiSpacer } from '@elastic/eui';
import { StorybookProviders } from '../../../common/mock/storybook_providers';
import { AssetCriticalityFileUploader } from './asset_criticality_file_uploader';
import { AssetCriticalityResultStep } from './components/result_step';
import { AssetCriticalityValidationStep } from './components/validation_step';
import { AssetCriticalityFilePickerStep } from './components/file_picker_step';

addDecorator((storyFn) => (
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>{storyFn()}</ThemeProvider>
));

const validLinesAsText = `user-001,low_impact\nuser-002,medium_impact,user\nuser-003,medium_impact,user\nhost-001,extreme_impact,host\nhost-002,extreme_impact,host`;
const invalidLinesAsText = `user,user-001,wow_impact\nbleh,user-002,medium_impact\nuser,user-003,medium_impact,extra_column`;

export default {
component: AssetCriticalityFileUploader,
title: 'Entity Analytics/AssetCriticalityFileUploader',
Expand All @@ -27,8 +34,117 @@ export const Default: Story<void> = () => {
return (
<StorybookProviders>
<TestProvider>
<div style={{ maxWidth: '800px', backgroundColor: 'white' }}>
<AssetCriticalityFileUploader />
<div style={{ maxWidth: '800px' }}>
<EuiPanel>
<AssetCriticalityFileUploader />
</EuiPanel>
</div>
</TestProvider>
</StorybookProviders>
);
};

export const FilePickerStep: Story<void> = () => {
return (
<StorybookProviders>
<TestProvider>
<div style={{ maxWidth: '800px' }}>
<EuiPanel>
<AssetCriticalityFilePickerStep onFileChange={() => {}} isLoading={true} />
</EuiPanel>
</div>
</TestProvider>
</StorybookProviders>
);
};

export const ValidationStep: Story<void> = () => {
return (
<StorybookProviders>
<TestProvider>
<div style={{ maxWidth: '800px' }}>
<EuiPanel>
<AssetCriticalityValidationStep
validLinesCount={5}
invalidLinesCount={3}
fileName="test.csv"
onConfirm={() => {}}
onReturn={() => {}}
validLinesAsText={validLinesAsText}
invalidLinesAsText={invalidLinesAsText}
invalidLinesErrors={[
{ error: 'error message 1', index: 1 },
{ error: 'error message 2', index: 2 },
{ error: 'error message 3', index: 3 },
]}
/>
</EuiPanel>
</div>
</TestProvider>
</StorybookProviders>
);
};

export const ResultsStep: Story<void> = () => {
return (
<StorybookProviders>
<TestProvider>
<div style={{ maxWidth: '800px' }}>
<b>{'Results Step - Success'}</b>
<EuiSpacer size="s" />

<EuiPanel>
<AssetCriticalityResultStep
onReturn={() => {}}
validLinesAsText={validLinesAsText}
result={{
errors: [],
stats: {
total: 10,
updated: 5,
created: 5,
errors: 0,
},
}}
/>
</EuiPanel>
<EuiSpacer size="xl" />

<b>{'Results Step - Partial error'}</b>
<EuiSpacer size="s" />

<EuiPanel>
<AssetCriticalityResultStep
onReturn={() => {}}
validLinesAsText={validLinesAsText}
result={{
errors: [
{ message: 'error message 1', index: 1 },
{ message: 'error message 2', index: 3 },
{ message: 'error message 3', index: 5 },
],
stats: {
total: 5,
updated: 0,
created: 2,
errors: 3,
},
}}
/>
</EuiPanel>

<EuiSpacer size="xl" />

<b>{'Results Step - Complete failure'}</b>
<EuiSpacer size="s" />

<EuiPanel>
<AssetCriticalityResultStep
onReturn={() => {}}
validLinesAsText=""
errorMessage="Something went wrong"
/>
</EuiPanel>
</div>
</TestProvider>
</StorybookProviders>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,6 @@ import { getStepStatus } from './helpers';
import { AssetCriticalityResultStep } from './components/result_step';
import { useEntityAnalyticsRoutes } from '../../api/api';

// TODO
// Should we support headers?
// result step UI
// Add the info icon with the error message

// WHAT DO I NEED FROM DESIGN?
// 3. serverless navigation
// 5. Rename steps

export const AssetCriticalityFileUploader: React.FC = () => {
const [state, dispatch] = useReducer(reducer, { isLoading: false, step: 1 });
const formatBytes = useFormatBytes();
Expand All @@ -43,8 +34,7 @@ export const AssetCriticalityFileUploader: React.FC = () => {
const onFileChange = (fileList: FileList | null) => {
if (fileList?.length === 0) {
dispatch({
type: 'goToStep',
payload: { step: 1 },
type: 'resetState',
});
}

Expand All @@ -71,13 +61,13 @@ export const AssetCriticalityFileUploader: React.FC = () => {
dynamicTyping: true,
skipEmptyLines: true,
complete(parsedFile, returnedFile) {
const { invalid, valid, error } = validateParsedContent(parsedFile.data);

if (error) {
dispatch({ type: 'fileError', payload: { message: error, file } });
if (parsedFile.data.length === 0) {
dispatch({ type: 'fileError', payload: { message: 'The file is empty', file } }); // TODO i18n
return;
}

const { invalid, valid, errors } = validateParsedContent(parsedFile.data);

const validLinesAsText = Papa.unparse(valid);
const invalidLinesAsText = Papa.unparse(invalid);

Expand All @@ -87,6 +77,7 @@ export const AssetCriticalityFileUploader: React.FC = () => {
fileName: returnedFile?.name ?? '',
validLinesAsText,
invalidLinesAsText,
invalidLinesErrors: errors,
validLinesCount: valid.length,
invalidLinesCount: invalid.length,
},
Expand Down Expand Up @@ -115,7 +106,9 @@ export const AssetCriticalityFileUploader: React.FC = () => {
),
status: getStepStatus(1, state.step),
onClick: () => {
dispatch({ type: 'goToStep', payload: { step: 1 } });
if (state.step === 2) {
dispatch({ type: 'resetState' }); // User can only go back to the first step from the second step
}
},
},
{
Expand All @@ -126,9 +119,7 @@ export const AssetCriticalityFileUploader: React.FC = () => {
}
),
status: getStepStatus(2, state.step),
onClick: () => {
dispatch({ type: 'goToStep', payload: { step: 2 } });
},
onClick: () => {}, // This prevents the user from going back to the previous step
},
{
title: i18n.translate(
Expand Down Expand Up @@ -163,32 +154,47 @@ export const AssetCriticalityFileUploader: React.FC = () => {
invalidLinesCount={state.invalidLinesCount}
validLinesAsText={state.validLinesAsText}
invalidLinesAsText={state.invalidLinesAsText}
invalidLinesErrors={state.invalidLinesErrors ?? []}
fileName={state.fileName ?? ''}
onReturn={() => {
dispatch({ type: 'goToStep', payload: { step: 1 } });
dispatch({ type: 'resetState' });
}}
onConfirm={async () => {
if (state.validLinesAsText) {
dispatch({
type: 'uploadingFile',
});

const result = await uploadAssetCriticalityFile(
state.validLinesAsText,
state.fileName
);

dispatch({
type: 'fileUploaded',
payload: result,
});
try {
const result = await uploadAssetCriticalityFile(
state.validLinesAsText,
state.fileName
);

dispatch({
type: 'fileUploaded',
payload: { response: result },
});
} catch (e) {
dispatch({
type: 'fileUploaded',
payload: { errorMessage: e.message },
});
}
}
}}
/>
)}

{state.step === 3 && state.fileUploadResponse && (
<AssetCriticalityResultStep result={state.fileUploadResponse} />
{state.step === 3 && (
<AssetCriticalityResultStep
result={state.fileUploadResponse}
errorMessage={state.fileUploadError}
validLinesAsText={state.validLinesAsText ?? ''}
onReturn={() => {
dispatch({ type: 'resetState' });
}}
/>
)}
</div>
</div>
Expand Down
Loading

0 comments on commit 52e3082

Please sign in to comment.