Skip to content

Commit

Permalink
[FEATURE] Implement date/time picker on the overview page (opensearch…
Browse files Browse the repository at this point in the history
…-project#232) (opensearch-project#240)

* [FEATURE] Implement date/time picker on the overview page opensearch-project#133

Signed-off-by: Jovan Cvetkovic <[email protected]>

* [FEATURE] Implement date/time picker on the overview page opensearch-project#133

Signed-off-by: Jovan Cvetkovic <[email protected]>

Signed-off-by: Jovan Cvetkovic <[email protected]>
(cherry picked from commit 3dc90dd)

Co-authored-by: Jovan Cvetkovic <[email protected]>
Signed-off-by: AWSHurneyt <[email protected]>
  • Loading branch information
2 people authored and AWSHurneyt committed Feb 22, 2023
1 parent e792820 commit e82abef
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 19 deletions.
20 changes: 17 additions & 3 deletions public/pages/Overview/components/Widgets/Summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiStat } from '@elastic/eui';
import React, { useCallback, useEffect, useState } from 'react';
import { WidgetContainer } from './WidgetContainer';
import { summaryGroupByOptions } from '../../utils/constants';
import { getOverviewVisualizationSpec, getTimeWithMinPrecision } from '../../utils/helpers';
import {
getDateFormatByTimeUnit,
getOverviewVisualizationSpec,
getTimeWithMinPrecision,
} from '../../utils/helpers';
import { AlertItem, FindingItem } from '../../models/interfaces';
import { createSelectComponent, renderVisualization } from '../../../../utils/helpers';
import { ROUTES } from '../../../../utils/constants';
Expand All @@ -26,7 +30,14 @@ export interface SummaryData {
logType?: string;
}

export const Summary: React.FC<SummaryProps> = ({ alerts, findings, loading = false }) => {
export const Summary: React.FC<SummaryProps> = ({
alerts,
findings,
startTime,
endTime,
timeUnit,
loading = false,
}) => {
const [groupBy, setGroupBy] = useState('');
const [summaryData, setSummaryData] = useState<SummaryData[]>([]);
const [activeAlerts, setActiveAlerts] = useState(0);
Expand All @@ -51,7 +62,10 @@ export const Summary: React.FC<SummaryProps> = ({ alerts, findings, loading = fa
);

const generateVisualizationSpec = useCallback((summaryData, groupBy) => {
return getOverviewVisualizationSpec(summaryData, groupBy);
return getOverviewVisualizationSpec(summaryData, groupBy, {
timeUnit: timeUnit,
dateFormat: getDateFormatByTimeUnit(startTime, endTime),
});
}, []);

useEffect(() => {
Expand Down
52 changes: 45 additions & 7 deletions public/pages/Overview/containers/Overview/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@
*/

import {
EuiButton,
EuiButtonEmpty,
EuiFlexGrid,
EuiFlexGroup,
EuiFlexItem,
EuiPopover,
EuiSuperDatePicker,
EuiTitle,
} from '@elastic/eui';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { BREADCRUMBS } from '../../../../utils/constants';
import {
BREADCRUMBS,
DEFAULT_DATE_RANGE,
MAX_RECENTLY_USED_TIME_RANGES,
} from '../../../../utils/constants';
import { OverviewProps, OverviewState } from '../../models/interfaces';
import { CoreServicesContext } from '../../../../../public/components/core_services';
import { RecentAlertsWidget } from '../../components/Widgets/RecentAlertsWidget';
Expand All @@ -24,6 +28,7 @@ import { ServicesContext } from '../../../../services';
import { Summary } from '../../components/Widgets/Summary';
import { TopRulesWidget } from '../../components/Widgets/TopRulesWidget';
import { GettingStartedPopup } from '../../components/GettingStarted/GettingStartedPopup';
import { getChartTimeUnit } from '../../utils/helpers';

export const Overview: React.FC<OverviewProps> = (props) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
Expand All @@ -36,7 +41,12 @@ export const Overview: React.FC<OverviewProps> = (props) => {
alerts: [],
},
});
const [startTime, setStartTime] = useState(DEFAULT_DATE_RANGE.start);
const [endTime, setEndTime] = useState(DEFAULT_DATE_RANGE.end);
const [recentlyUsedRanges, setRecentlyUsedRanges] = useState([DEFAULT_DATE_RANGE]);
const [loading, setLoading] = useState(true);
const [timeUnit, setTimeUnit] = useState('yearmonthdatehoursminutes');

const context = useContext(CoreServicesContext);
const services = useContext(ServicesContext);

Expand All @@ -58,7 +68,7 @@ export const Overview: React.FC<OverviewProps> = (props) => {
overviewViewModelActor.registerRefreshHandler(updateState);

const updateModel = async () => {
await overviewViewModelActor.onRefresh();
await overviewViewModelActor.onRefresh(startTime, endTime);
setInitialLoadingFinished(true);
};

Expand All @@ -75,16 +85,33 @@ export const Overview: React.FC<OverviewProps> = (props) => {
}
}, [initialLoadingFinished, state.overviewViewModel, props.getStartedDismissedOnce]);

const onTimeChange = ({ start, end }: { start: string; end: string }) => {
// TODO: NYI
const onTimeChange = async ({ start, end }: { start: string; end: string }) => {
let usedRanges = recentlyUsedRanges.filter(
(range) => !(range.start === start && range.end === end)
);
usedRanges.unshift({ start: start, end: end });
if (usedRanges.length > MAX_RECENTLY_USED_TIME_RANGES)
usedRanges = usedRanges.slice(0, MAX_RECENTLY_USED_TIME_RANGES);

const endTime = start === end ? DEFAULT_DATE_RANGE.end : end;
const timeUnit = getChartTimeUnit(start, endTime);
setStartTime(start);
setEndTime(endTime);
setTimeUnit(timeUnit);
setRecentlyUsedRanges(usedRanges);
};

useEffect(() => {
overviewViewModelActor.onRefresh(startTime, endTime);
}, [startTime, endTime]);

const onRefresh = async () => {
setLoading(true);
overviewViewModelActor.onRefresh();
await overviewViewModelActor.onRefresh(startTime, endTime);
};

const onButtonClick = () => setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen);

const closePopover = () => {
setIsPopoverOpen(false);
props.onGetStartedDismissed();
Expand Down Expand Up @@ -116,14 +143,25 @@ export const Overview: React.FC<OverviewProps> = (props) => {
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton onClick={onRefresh}>Refresh</EuiButton>
<EuiSuperDatePicker
start={startTime}
end={endTime}
recentlyUsedRanges={recentlyUsedRanges}
isLoading={loading}
onTimeChange={onTimeChange}
onRefresh={onRefresh}
updateButtonProps={{ fill: false }}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<Summary
alerts={state.overviewViewModel.alerts}
findings={state.overviewViewModel.findings}
startTime={startTime}
endTime={endTime}
timeUnit={timeUnit}
loading={loading}
/>
</EuiFlexItem>
Expand Down
27 changes: 23 additions & 4 deletions public/pages/Overview/models/OverviewViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { RuleService } from '../../../services';
import { DEFAULT_EMPTY_DATA } from '../../../utils/constants';
import { NotificationsStart } from 'opensearch-dashboards/public';
import { errorNotificationToast } from '../../../utils/helpers';
import dateMath from '@elastic/datemath';
import moment from 'moment';

export interface OverviewViewModel {
detectors: DetectorHit[];
Expand Down Expand Up @@ -114,11 +116,14 @@ export class OverviewViewModelActor {
const ids = finding.queries.map((query) => query.id);
ids.forEach((id) => ruleIds.add(id));

const findingTime = new Date(finding.timestamp);
findingTime.setMilliseconds(0);
findingTime.setSeconds(0);
return {
detector: detectorName,
findingName: finding.id,
id: finding.id,
time: finding.timestamp,
time: findingTime,
logType: logType || '',
ruleId: finding.queries[0].id,
ruleName: '',
Expand All @@ -141,7 +146,7 @@ export class OverviewViewModelActor {
ruleSeverity: rulesRes[item.ruleId]?.level || DEFAULT_EMPTY_DATA,
}));

this.overviewViewModel.findings = findingItems;
this.overviewViewModel.findings = this.filterChartDataByTime(findingItems);
}

private async updateAlerts() {
Expand Down Expand Up @@ -175,7 +180,7 @@ export class OverviewViewModelActor {
errorNotificationToast(this.notifications, 'retrieve', 'alerts', e);
}

this.overviewViewModel.alerts = alertItems;
this.overviewViewModel.alerts = this.filterChartDataByTime(alertItems);
}

public getOverviewViewModel() {
Expand All @@ -186,7 +191,13 @@ export class OverviewViewModelActor {
this.refreshHandlers.push(handler);
}

public async onRefresh() {
startTime = 'now-15m';
endTime = 'now';

public async onRefresh(startTime: string, endTime: string) {
this.startTime = startTime;
this.endTime = endTime;

if (this.refreshState === 'InProgress') {
return;
}
Expand All @@ -202,4 +213,12 @@ export class OverviewViewModelActor {

this.refreshState = 'Complete';
}

private filterChartDataByTime = (chartData) => {
const startMoment = dateMath.parse(this.startTime);
const endMoment = dateMath.parse(this.endTime);
return chartData.filter((dataItem) => {
return moment(dataItem.time).isBetween(moment(startMoment), moment(endMoment));
});
};
}
27 changes: 22 additions & 5 deletions public/pages/Overview/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,19 @@ function getVisualizationSpec(description: string, data: any, layers: any[]): To
export function getOverviewVisualizationSpec(
visualizationData: SummaryData[],
groupBy: string,
dynamicTimeUnit: string = 'yearmonthdatehoursminutes'
dateOpts: DateOpts = {
timeUnit: 'yearmonthdatehoursminutes',
dateFormat: '%Y-%m-%d %H:%M',
}
): TopLevelSpec {
const timeUnit = dynamicTimeUnit;
const aggregate = 'sum';
const findingsEncoding: { [x: string]: any } = {
x: { timeUnit, field: 'time', title: '', axis: { grid: false, ticks: false } },
x: {
timeUnit: dateOpts.timeUnit,
field: 'time',
title: '',
axis: { grid: false, ticks: false, format: dateOpts.dateFormat },
},
y: {
aggregate,
field: 'finding',
Expand Down Expand Up @@ -91,8 +98,18 @@ export function getOverviewVisualizationSpec(
},
},
encoding: {
x: { timeUnit, field: 'time', title: '', axis: { grid: false, ticks: false } },
y: { aggregate, field: 'alert', title: 'Count', axis: { grid: true, ticks: false } },
x: {
timeUnit: dateOpts.timeUnit,
field: 'time',
title: '',
axis: { grid: false, ticks: false, format: dateOpts.dateFormat },
},
y: {
aggregate: 'sum',
field: 'alert',
title: 'Count',
axis: { grid: true, ticks: false },
},
tooltip: [{ field: 'alert', aggregate: 'sum', title: 'Alerts' }],
},
},
Expand Down

0 comments on commit e82abef

Please sign in to comment.