diff --git a/package.json b/package.json
index 7c94813f2..e1b9f5e13 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"plugin-helpers": "node ../../scripts/plugin_helpers",
"test:jest": "../../node_modules/.bin/jest --config ./test/jest.config.js",
"test:jest:dev": "../../node_modules/.bin/jest --watch --config ./test/jest.config.js",
+ "test:jest:update-snapshots": "yarn run test:jest -u",
"build": "yarn plugin-helpers build",
"postbuild": "echo Renaming build artifact to [$npm_package_config_zip_name-$npm_package_version.zip] && mv build/$npm_package_config_id*.zip build/$npm_package_config_zip_name-$npm_package_version.zip"
},
diff --git a/public/app.scss b/public/app.scss
index 1682d852e..ffbc4e3a0 100644
--- a/public/app.scss
+++ b/public/app.scss
@@ -116,3 +116,7 @@ $euiTextColor: $euiColorDarkestShade !default;
}
}
}
+
+.sa-overview-widget-empty tbody > .euiTableRow > .euiTableRowCell {
+ border-bottom: none;
+}
diff --git a/public/pages/Overview/components/Widgets/DetectorsWidget.tsx b/public/pages/Overview/components/Widgets/DetectorsWidget.tsx
index 03c8f9ecc..262cfb448 100644
--- a/public/pages/Overview/components/Widgets/DetectorsWidget.tsx
+++ b/public/pages/Overview/components/Widgets/DetectorsWidget.tsx
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { EuiBasicTableColumn, EuiButton, EuiLink } from '@elastic/eui';
+import { EuiBasicTableColumn, EuiButton, EuiEmptyPrompt, EuiLink } from '@elastic/eui';
import { ROUTES } from '../../../../utils/constants';
import React, { useCallback } from 'react';
import { DetectorItem } from '../../models/interfaces';
@@ -71,6 +71,23 @@ export const DetectorsWidget: React.FC = ({
});
}, []);
+ const widgetEmptyMessage =
+ detectors.length === 0 ? (
+
+ No security detectors.Create a detector to
+ generate findings.
+
+ }
+ actions={[
+
+ Create detector
+ ,
+ ]}
+ />
+ ) : undefined;
+
const actions = React.useMemo(
() => [
View all detectors,
@@ -85,6 +102,8 @@ export const DetectorsWidget: React.FC = ({
columns={getColumns(detectorIdToHit, showDetectorDetails)}
items={detectors}
loading={loading}
+ message={widgetEmptyMessage}
+ className={widgetEmptyMessage ? 'sa-overview-widget-empty' : undefined}
/>
);
diff --git a/public/pages/Overview/components/Widgets/RecentAlertsWidget.tsx b/public/pages/Overview/components/Widgets/RecentAlertsWidget.tsx
index 398291a3c..62979c10a 100644
--- a/public/pages/Overview/components/Widgets/RecentAlertsWidget.tsx
+++ b/public/pages/Overview/components/Widgets/RecentAlertsWidget.tsx
@@ -3,8 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { EuiBasicTableColumn, EuiButton } from '@elastic/eui';
-import { DEFAULT_EMPTY_DATA, ROUTES } from '../../../../utils/constants';
+import { EuiBasicTableColumn, EuiButton, EuiEmptyPrompt } from '@elastic/eui';
+import { DEFAULT_EMPTY_DATA, ROUTES, SortDirection } from '../../../../utils/constants';
import React, { useEffect, useState } from 'react';
import { AlertItem } from '../../models/interfaces';
import { TableWidget } from './TableWidget';
@@ -45,6 +45,9 @@ export const RecentAlertsWidget: React.FC = ({
loading = false,
}) => {
const [alertItems, setAlertItems] = useState([]);
+ const [widgetEmptyMessage, setwidgetEmptyMessage] = useState(
+ undefined
+ );
useEffect(() => {
items.sort((a, b) => {
@@ -53,6 +56,18 @@ export const RecentAlertsWidget: React.FC = ({
return timeA - timeB;
});
setAlertItems(items.slice(0, 20));
+ setwidgetEmptyMessage(
+ items.length > 0 ? undefined : (
+
+ No recent alerts.Adjust the time range to
+ see more results.
+
+ }
+ />
+ )
+ );
}, [items]);
const actions = React.useMemo(
@@ -62,7 +77,14 @@ export const RecentAlertsWidget: React.FC = ({
return (
-
+
);
};
diff --git a/public/pages/Overview/components/Widgets/RecentFindingsWidget.tsx b/public/pages/Overview/components/Widgets/RecentFindingsWidget.tsx
index 2e0bbe1df..aa9d658e5 100644
--- a/public/pages/Overview/components/Widgets/RecentFindingsWidget.tsx
+++ b/public/pages/Overview/components/Widgets/RecentFindingsWidget.tsx
@@ -3,8 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { EuiBasicTableColumn, EuiButton } from '@elastic/eui';
-import { ROUTES } from '../../../../utils/constants';
+import { EuiBasicTableColumn, EuiButton, EuiEmptyPrompt } from '@elastic/eui';
+import { ROUTES, SortDirection } from '../../../../utils/constants';
import React, { useEffect, useState } from 'react';
import { FindingItem } from '../../models/interfaces';
import { TableWidget } from './TableWidget';
@@ -52,12 +52,27 @@ export const RecentFindingsWidget: React.FC = ({
loading = false,
}) => {
const [findingItems, setFindingItems] = useState([]);
+ const [widgetEmptyMessage, setWidgetEmptyMessage] = useState(
+ undefined
+ );
useEffect(() => {
items.sort((a, b) => {
return a.time - b.time;
});
setFindingItems(items.slice(0, 20));
+ setWidgetEmptyMessage(
+ items.length > 0 ? undefined : (
+
+ No recent findings.Adjust the time range to
+ see more results.
+
+ }
+ />
+ )
+ );
}, [items]);
const actions = React.useMemo(
@@ -67,7 +82,14 @@ export const RecentFindingsWidget: React.FC = ({
return (
-
+
);
};
diff --git a/public/pages/Overview/components/Widgets/Summary.tsx b/public/pages/Overview/components/Widgets/Summary.tsx
index 91d96d26e..56ae1e54d 100644
--- a/public/pages/Overview/components/Widgets/Summary.tsx
+++ b/public/pages/Overview/components/Widgets/Summary.tsx
@@ -3,13 +3,18 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiStat } from '@elastic/eui';
-import { euiPaletteColorBlind } from '@elastic/eui';
+import {
+ EuiEmptyPrompt,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLink,
+ EuiLinkColor,
+ EuiStat,
+} from '@elastic/eui';
import React, { useCallback, useEffect, useState } from 'react';
import { WidgetContainer } from './WidgetContainer';
import { summaryGroupByOptions } from '../../utils/constants';
import {
- alertsDefaultColor,
getChartTimeUnit,
getDomainRange,
getOverviewVisualizationSpec,
@@ -46,8 +51,8 @@ export const Summary: React.FC = ({
}) => {
const [groupBy, setGroupBy] = useState('');
const [summaryData, setSummaryData] = useState([]);
- const [activeAlerts, setActiveAlerts] = useState(0);
- const [totalFindings, setTotalFindings] = useState(0);
+ const [activeAlerts, setActiveAlerts] = useState(undefined);
+ const [totalFindings, setTotalFindings] = useState(undefined);
const onGroupByChange = useCallback((event) => {
setGroupBy(event.target.value);
@@ -113,44 +118,62 @@ export const Summary: React.FC = ({
renderVisualization(generateVisualizationSpec(summaryData, groupBy), 'summary-view');
}, [summaryData, groupBy]);
+ const createStatComponent = useCallback(
+ (description: string, urlData: { url: string; color: EuiLinkColor }, stat?: number) => (
+
+
+ {stat}
+
+ )
+ }
+ description={description}
+ textAlign="left"
+ titleSize="l"
+ titleColor="subdued"
+ isLoading={stat === undefined}
+ />
+
+ ),
+ []
+ );
+
return (
-
-
- {activeAlerts}
-
- }
- description="Total active alerts"
- textAlign="left"
- titleColor="primary"
- isLoading={!activeAlerts}
- />
-
-
-
- {totalFindings}
-
- }
- description="Total findings"
- textAlign="left"
- titleColor="primary"
- isLoading={!totalFindings}
- />
-
+ {createStatComponent(
+ 'Total active alerts',
+ { url: ROUTES.ALERTS, color: 'danger' },
+ activeAlerts
+ )}
+ {createStatComponent(
+ 'Total findings',
+ { url: ROUTES.FINDINGS, color: 'primary' },
+ totalFindings
+ )}
-
+ {activeAlerts === 0 && totalFindings === 0 ? (
+ No alerts and findings found}
+ body={
+
+ Adjust the time range to see more results or{' '}
+ create a detector to
+ generate findings.{' '}
+
+ }
+ />
+ ) : (
+
+ )}
diff --git a/public/pages/Overview/components/Widgets/TableWidget.tsx b/public/pages/Overview/components/Widgets/TableWidget.tsx
index f59bd4da9..1d9915258 100644
--- a/public/pages/Overview/components/Widgets/TableWidget.tsx
+++ b/public/pages/Overview/components/Widgets/TableWidget.tsx
@@ -9,16 +9,19 @@ import { TableWidgetItem, TableWidgetProps } from '../../models/types';
export class TableWidget extends React.Component> {
render() {
- const { columns, items, loading = false } = this.props;
+ const { columns, items, sorting, message, className, loading = false } = this.props;
return (
+ className={className}
compressed
columns={columns}
items={items}
itemId={(item: T) => `${item.id}`}
pagination={{ pageSize: 10, pageSizeOptions: [10] }}
+ sorting={sorting}
loading={loading}
+ message={message}
/>
);
}
diff --git a/public/pages/Overview/components/Widgets/TopRulesWidget.tsx b/public/pages/Overview/components/Widgets/TopRulesWidget.tsx
index 40de1f699..0404f3086 100644
--- a/public/pages/Overview/components/Widgets/TopRulesWidget.tsx
+++ b/public/pages/Overview/components/Widgets/TopRulesWidget.tsx
@@ -9,6 +9,7 @@ import { FindingItem } from '../../models/interfaces';
import { WidgetContainer } from './WidgetContainer';
import { getTopRulesVisualizationSpec } from '../../utils/helpers';
import { ChartContainer } from '../../../../components/Charts/ChartContainer';
+import { EuiEmptyPrompt } from '@elastic/eui';
export interface TopRulesWidgetProps {
findings: FindingItem[];
@@ -35,7 +36,21 @@ export const TopRulesWidget: React.FC = ({ findings, loadin
return (
-
+ {findings.length === 0 ? (
+
+
+ No findings with detection rules.Adjust
+ the time range to see more results.
+
+
+ }
+ />
+ ) : (
+
+ )}
);
};
diff --git a/public/pages/Overview/models/types.ts b/public/pages/Overview/models/types.ts
index d5d6b7786..83ed06681 100644
--- a/public/pages/Overview/models/types.ts
+++ b/public/pages/Overview/models/types.ts
@@ -4,6 +4,7 @@
*/
import { EuiBasicTableColumn } from '@elastic/eui';
+import { SortDirection } from '../../../utils/constants';
import { AlertItem, DetectorItem, FindingItem } from './interfaces';
export type TableWidgetItem = FindingItem | AlertItem | DetectorItem;
@@ -11,5 +12,13 @@ export type TableWidgetItem = FindingItem | AlertItem | DetectorItem;
export type TableWidgetProps = {
columns: EuiBasicTableColumn[];
items: T[];
+ sorting?: {
+ sort: {
+ field: string;
+ direction: SortDirection;
+ };
+ };
+ className?: string;
loading?: boolean;
+ message?: React.ReactNode;
};