Skip to content

Commit 190d20c

Browse files
[FEATURE] Implement date/time picker on the overview page (#232) (#240)
* [FEATURE] Implement date/time picker on the overview page #133 Signed-off-by: Jovan Cvetkovic <[email protected]> * [FEATURE] Implement date/time picker on the overview page #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]>
1 parent b491ee8 commit 190d20c

File tree

4 files changed

+107
-19
lines changed

4 files changed

+107
-19
lines changed

public/pages/Overview/components/Widgets/Summary.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiStat } from '@elastic/eui';
77
import React, { useCallback, useEffect, useState } from 'react';
88
import { WidgetContainer } from './WidgetContainer';
99
import { summaryGroupByOptions } from '../../utils/constants';
10-
import { getOverviewVisualizationSpec, getTimeWithMinPrecision } from '../../utils/helpers';
10+
import {
11+
getDateFormatByTimeUnit,
12+
getOverviewVisualizationSpec,
13+
getTimeWithMinPrecision,
14+
} from '../../utils/helpers';
1115
import { AlertItem, FindingItem } from '../../models/interfaces';
1216
import { createSelectComponent, renderVisualization } from '../../../../utils/helpers';
1317
import { ROUTES } from '../../../../utils/constants';
@@ -26,7 +30,14 @@ export interface SummaryData {
2630
logType?: string;
2731
}
2832

29-
export const Summary: React.FC<SummaryProps> = ({ alerts, findings, loading = false }) => {
33+
export const Summary: React.FC<SummaryProps> = ({
34+
alerts,
35+
findings,
36+
startTime,
37+
endTime,
38+
timeUnit,
39+
loading = false,
40+
}) => {
3041
const [groupBy, setGroupBy] = useState('');
3142
const [summaryData, setSummaryData] = useState<SummaryData[]>([]);
3243
const [activeAlerts, setActiveAlerts] = useState(0);
@@ -51,7 +62,10 @@ export const Summary: React.FC<SummaryProps> = ({ alerts, findings, loading = fa
5162
);
5263

5364
const generateVisualizationSpec = useCallback((summaryData, groupBy) => {
54-
return getOverviewVisualizationSpec(summaryData, groupBy);
65+
return getOverviewVisualizationSpec(summaryData, groupBy, {
66+
timeUnit: timeUnit,
67+
dateFormat: getDateFormatByTimeUnit(startTime, endTime),
68+
});
5569
}, []);
5670

5771
useEffect(() => {

public/pages/Overview/containers/Overview/Overview.tsx

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@
44
*/
55

66
import {
7-
EuiButton,
87
EuiButtonEmpty,
98
EuiFlexGrid,
109
EuiFlexGroup,
1110
EuiFlexItem,
1211
EuiPopover,
12+
EuiSuperDatePicker,
1313
EuiTitle,
1414
} from '@elastic/eui';
1515
import React, { useContext, useEffect, useMemo, useState } from 'react';
16-
import { BREADCRUMBS } from '../../../../utils/constants';
16+
import {
17+
BREADCRUMBS,
18+
DEFAULT_DATE_RANGE,
19+
MAX_RECENTLY_USED_TIME_RANGES,
20+
} from '../../../../utils/constants';
1721
import { OverviewProps, OverviewState } from '../../models/interfaces';
1822
import { CoreServicesContext } from '../../../../../public/components/core_services';
1923
import { RecentAlertsWidget } from '../../components/Widgets/RecentAlertsWidget';
@@ -24,6 +28,7 @@ import { ServicesContext } from '../../../../services';
2428
import { Summary } from '../../components/Widgets/Summary';
2529
import { TopRulesWidget } from '../../components/Widgets/TopRulesWidget';
2630
import { GettingStartedPopup } from '../../components/GettingStarted/GettingStartedPopup';
31+
import { getChartTimeUnit } from '../../utils/helpers';
2732

2833
export const Overview: React.FC<OverviewProps> = (props) => {
2934
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
@@ -36,7 +41,12 @@ export const Overview: React.FC<OverviewProps> = (props) => {
3641
alerts: [],
3742
},
3843
});
44+
const [startTime, setStartTime] = useState(DEFAULT_DATE_RANGE.start);
45+
const [endTime, setEndTime] = useState(DEFAULT_DATE_RANGE.end);
46+
const [recentlyUsedRanges, setRecentlyUsedRanges] = useState([DEFAULT_DATE_RANGE]);
3947
const [loading, setLoading] = useState(true);
48+
const [timeUnit, setTimeUnit] = useState('yearmonthdatehoursminutes');
49+
4050
const context = useContext(CoreServicesContext);
4151
const services = useContext(ServicesContext);
4252

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

6070
const updateModel = async () => {
61-
await overviewViewModelActor.onRefresh();
71+
await overviewViewModelActor.onRefresh(startTime, endTime);
6272
setInitialLoadingFinished(true);
6373
};
6474

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

78-
const onTimeChange = ({ start, end }: { start: string; end: string }) => {
79-
// TODO: NYI
88+
const onTimeChange = async ({ start, end }: { start: string; end: string }) => {
89+
let usedRanges = recentlyUsedRanges.filter(
90+
(range) => !(range.start === start && range.end === end)
91+
);
92+
usedRanges.unshift({ start: start, end: end });
93+
if (usedRanges.length > MAX_RECENTLY_USED_TIME_RANGES)
94+
usedRanges = usedRanges.slice(0, MAX_RECENTLY_USED_TIME_RANGES);
95+
96+
const endTime = start === end ? DEFAULT_DATE_RANGE.end : end;
97+
const timeUnit = getChartTimeUnit(start, endTime);
98+
setStartTime(start);
99+
setEndTime(endTime);
100+
setTimeUnit(timeUnit);
101+
setRecentlyUsedRanges(usedRanges);
80102
};
81103

104+
useEffect(() => {
105+
overviewViewModelActor.onRefresh(startTime, endTime);
106+
}, [startTime, endTime]);
107+
82108
const onRefresh = async () => {
83109
setLoading(true);
84-
overviewViewModelActor.onRefresh();
110+
await overviewViewModelActor.onRefresh(startTime, endTime);
85111
};
86112

87113
const onButtonClick = () => setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen);
114+
88115
const closePopover = () => {
89116
setIsPopoverOpen(false);
90117
props.onGetStartedDismissed();
@@ -116,14 +143,25 @@ export const Overview: React.FC<OverviewProps> = (props) => {
116143
</EuiTitle>
117144
</EuiFlexItem>
118145
<EuiFlexItem grow={false}>
119-
<EuiButton onClick={onRefresh}>Refresh</EuiButton>
146+
<EuiSuperDatePicker
147+
start={startTime}
148+
end={endTime}
149+
recentlyUsedRanges={recentlyUsedRanges}
150+
isLoading={loading}
151+
onTimeChange={onTimeChange}
152+
onRefresh={onRefresh}
153+
updateButtonProps={{ fill: false }}
154+
/>
120155
</EuiFlexItem>
121156
</EuiFlexGroup>
122157
</EuiFlexItem>
123158
<EuiFlexItem>
124159
<Summary
125160
alerts={state.overviewViewModel.alerts}
126161
findings={state.overviewViewModel.findings}
162+
startTime={startTime}
163+
endTime={endTime}
164+
timeUnit={timeUnit}
127165
loading={loading}
128166
/>
129167
</EuiFlexItem>

public/pages/Overview/models/OverviewViewModel.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { RuleService } from '../../../services';
1010
import { DEFAULT_EMPTY_DATA } from '../../../utils/constants';
1111
import { NotificationsStart } from 'opensearch-dashboards/public';
1212
import { errorNotificationToast } from '../../../utils/helpers';
13+
import dateMath from '@elastic/datemath';
14+
import moment from 'moment';
1315

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

119+
const findingTime = new Date(finding.timestamp);
120+
findingTime.setMilliseconds(0);
121+
findingTime.setSeconds(0);
117122
return {
118123
detector: detectorName,
119124
findingName: finding.id,
120125
id: finding.id,
121-
time: finding.timestamp,
126+
time: findingTime,
122127
logType: logType || '',
123128
ruleId: finding.queries[0].id,
124129
ruleName: '',
@@ -141,7 +146,7 @@ export class OverviewViewModelActor {
141146
ruleSeverity: rulesRes[item.ruleId]?.level || DEFAULT_EMPTY_DATA,
142147
}));
143148

144-
this.overviewViewModel.findings = findingItems;
149+
this.overviewViewModel.findings = this.filterChartDataByTime(findingItems);
145150
}
146151

147152
private async updateAlerts() {
@@ -175,7 +180,7 @@ export class OverviewViewModelActor {
175180
errorNotificationToast(this.notifications, 'retrieve', 'alerts', e);
176181
}
177182

178-
this.overviewViewModel.alerts = alertItems;
183+
this.overviewViewModel.alerts = this.filterChartDataByTime(alertItems);
179184
}
180185

181186
public getOverviewViewModel() {
@@ -186,7 +191,13 @@ export class OverviewViewModelActor {
186191
this.refreshHandlers.push(handler);
187192
}
188193

189-
public async onRefresh() {
194+
startTime = 'now-15m';
195+
endTime = 'now';
196+
197+
public async onRefresh(startTime: string, endTime: string) {
198+
this.startTime = startTime;
199+
this.endTime = endTime;
200+
190201
if (this.refreshState === 'InProgress') {
191202
return;
192203
}
@@ -202,4 +213,12 @@ export class OverviewViewModelActor {
202213

203214
this.refreshState = 'Complete';
204215
}
216+
217+
private filterChartDataByTime = (chartData) => {
218+
const startMoment = dateMath.parse(this.startTime);
219+
const endMoment = dateMath.parse(this.endTime);
220+
return chartData.filter((dataItem) => {
221+
return moment(dataItem.time).isBetween(moment(startMoment), moment(endMoment));
222+
});
223+
};
205224
}

public/pages/Overview/utils/helpers.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,19 @@ function getVisualizationSpec(description: string, data: any, layers: any[]): To
4545
export function getOverviewVisualizationSpec(
4646
visualizationData: SummaryData[],
4747
groupBy: string,
48-
dynamicTimeUnit: string = 'yearmonthdatehoursminutes'
48+
dateOpts: DateOpts = {
49+
timeUnit: 'yearmonthdatehoursminutes',
50+
dateFormat: '%Y-%m-%d %H:%M',
51+
}
4952
): TopLevelSpec {
50-
const timeUnit = dynamicTimeUnit;
5153
const aggregate = 'sum';
5254
const findingsEncoding: { [x: string]: any } = {
53-
x: { timeUnit, field: 'time', title: '', axis: { grid: false, ticks: false } },
55+
x: {
56+
timeUnit: dateOpts.timeUnit,
57+
field: 'time',
58+
title: '',
59+
axis: { grid: false, ticks: false, format: dateOpts.dateFormat },
60+
},
5461
y: {
5562
aggregate,
5663
field: 'finding',
@@ -91,8 +98,18 @@ export function getOverviewVisualizationSpec(
9198
},
9299
},
93100
encoding: {
94-
x: { timeUnit, field: 'time', title: '', axis: { grid: false, ticks: false } },
95-
y: { aggregate, field: 'alert', title: 'Count', axis: { grid: true, ticks: false } },
101+
x: {
102+
timeUnit: dateOpts.timeUnit,
103+
field: 'time',
104+
title: '',
105+
axis: { grid: false, ticks: false, format: dateOpts.dateFormat },
106+
},
107+
y: {
108+
aggregate: 'sum',
109+
field: 'alert',
110+
title: 'Count',
111+
axis: { grid: true, ticks: false },
112+
},
96113
tooltip: [{ field: 'alert', aggregate: 'sum', title: 'Alerts' }],
97114
},
98115
},

0 commit comments

Comments
 (0)