Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 64 additions & 13 deletions src/components/AppWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { styled } from '@mui/material';
import dayjs from 'dayjs';
import React, { useContext, useEffect, useState } from 'react';
import {
Navigate,
Expand Down Expand Up @@ -36,6 +37,8 @@ const RootDiv = styled('div')({
export const FILTERS_QUERY_KEY = 'filters';
export const SLIDE_QUERY_KEY = 'slide';
export const TIME_QUERY_KEY = 'time';
export const START_TIME_QUERY_KEY = 'startTime';
export const END_TIME_QUERY_KEY = 'endTime';

export const AppWrapper: React.FC = () => {
const { pathname, search } = useLocation();
Expand Down Expand Up @@ -71,32 +74,80 @@ export const AppWrapper: React.FC = () => {
}, [pathname]);

const initializeTimeSearchParams = () => {
// If date has already been set
const existingTimeParam = searchParams.get(TIME_QUERY_KEY);
const existingStartParam = searchParams.get(START_TIME_QUERY_KEY);
const existingEndParam = searchParams.get(END_TIME_QUERY_KEY);

if (existingStartParam || existingEndParam) {
setTimeSearchParam(
TimeFilterEnum.custom,
existingStartParam,
existingEndParam
);
return;
}

if (
dateFilter?.filterShortString &&
dateFilter?.filterShortString in TimeFilterEnum
existingTimeParam === TimeFilterEnum.custom &&
!existingStartParam &&
!existingEndParam
) {
setTimeSearchParam(dateFilter.filterShortString);
setTimeSearchParam(TimeFilterEnum['24hours']);
return;
}
// If time param is invalid
const existingTimeParam = searchParams.get(TIME_QUERY_KEY);

if (existingTimeParam === null || !(existingTimeParam in TimeFilterEnum)) {
setTimeSearchParam(TimeFilterEnum['24hours']);
} else {
// Set filter string for components to consume
setDateFilter(getTimeFilterObject(existingTimeParam as TimeFilterEnum));
return;
}

setDateFilter(getTimeFilterObject(existingTimeParam as TimeFilterEnum));
};

const setTimeSearchParam = (timeFilter: TimeFilterEnum) => {
const setTimeSearchParam = (
timeFilter: TimeFilterEnum,
startTime?: string | null,
endTime?: string | null
) => {
const validStartTime =
startTime && dayjs(startTime).isValid()
? dayjs(startTime).toISOString()
: undefined;
const validEndTime =
endTime && dayjs(endTime).isValid()
? dayjs(endTime).toISOString()
: undefined;
if (timeFilter === TimeFilterEnum.custom) {
if (validStartTime) {
searchParams.set(START_TIME_QUERY_KEY, validStartTime);
} else {
searchParams.delete(START_TIME_QUERY_KEY);
}

if (validEndTime) {
searchParams.set(END_TIME_QUERY_KEY, validEndTime);
} else {
searchParams.delete(END_TIME_QUERY_KEY);
}
} else {
searchParams.delete(START_TIME_QUERY_KEY);
searchParams.delete(END_TIME_QUERY_KEY);
}
searchParams.set(TIME_QUERY_KEY, timeFilter);
setSearchParams(searchParams, { replace: true });

const nextDateFilter = getTimeFilterObject(timeFilter, {
startTime: validStartTime,
endTime: validEndTime,
});

if (
getTimeFilterObject(timeFilter).filterShortString !==
dateFilter?.filterShortString
nextDateFilter.filterShortString !== dateFilter?.filterShortString ||
nextDateFilter.filterString !== dateFilter?.filterString ||
nextDateFilter.startTime !== dateFilter?.startTime ||
nextDateFilter.endTime !== dateFilter?.endTime
) {
setDateFilter(getTimeFilterObject(timeFilter));
setDateFilter(nextDateFilter);
}
};

Expand Down
176 changes: 167 additions & 9 deletions src/components/Pickers/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,160 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { MenuItem, TextField } from '@mui/material';
import React, { useContext, useMemo } from 'react';
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
MenuItem,
Stack,
TextField,
Typography,
} from '@mui/material';
import dayjs from 'dayjs';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { DateFilterContext } from '../../contexts/DateFilterContext';
import { TimeFilterEnum } from '../../interfaces';
import { TIME_QUERY_KEY } from '../AppWrapper';
import {
END_TIME_QUERY_KEY,
START_TIME_QUERY_KEY,
TIME_QUERY_KEY,
} from '../AppWrapper';

const formatInputValue = (value?: string | null) => {
if (!value) {
return '';
}
const parsed = dayjs(value);
return parsed.isValid() ? parsed.format('YYYY-MM-DDTHH:mm') : '';
};

export const DatePicker: React.FC = () => {
const { t } = useTranslation();
const { setTimeSearchParam, searchParams } = useContext(DateFilterContext);

const getCurrentTimeFilter = (): TimeFilterEnum => {
const currentParam = searchParams.get(TIME_QUERY_KEY);
if (currentParam && currentParam in TimeFilterEnum) {
return currentParam as TimeFilterEnum;
}
return TimeFilterEnum['24hours'];
};

const [selectedValue, setSelectedValue] = useState<TimeFilterEnum | ''>(
getCurrentTimeFilter()
);
const [lastAppliedValue, setLastAppliedValue] = useState<TimeFilterEnum | ''>(
getCurrentTimeFilter()
);
const [customStart, setCustomStart] = useState(
formatInputValue(searchParams.get(START_TIME_QUERY_KEY))
);
const [customEnd, setCustomEnd] = useState(
formatInputValue(searchParams.get(END_TIME_QUERY_KEY))
);
const [isDialogOpen, setIsDialogOpen] = useState(false);

useEffect(() => {
const currentFilter = getCurrentTimeFilter();
setSelectedValue(currentFilter);
setLastAppliedValue(currentFilter);
setCustomStart(formatInputValue(searchParams.get(START_TIME_QUERY_KEY)));
setCustomEnd(formatInputValue(searchParams.get(END_TIME_QUERY_KEY)));
}, [searchParams]);

const createdQueryOptions = useMemo(
() => [
{
value: '1hour',
value: TimeFilterEnum['1hour'],
label: t('last1Hour'),
},
{
value: '24hours',
value: TimeFilterEnum['24hours'],
label: t('last24Hours'),
},
{
value: '7days',
value: TimeFilterEnum['7days'],
label: t('last7Days'),
},
{
value: '30days',
value: TimeFilterEnum['30days'],
label: t('last30Days'),
},
{
value: TimeFilterEnum.custom,
label: t('customRange'),
},
],
[t]
);

const startMoment = customStart ? dayjs(customStart) : null;
const endMoment = customEnd ? dayjs(customEnd) : null;
const hasStart = !!customStart;
const hasEnd = !!customEnd;
const rangeError =
startMoment !== null &&
endMoment !== null &&
startMoment.isValid() &&
endMoment.isValid() &&
startMoment.isAfter(endMoment);
const missingRange = !hasStart && !hasEnd;
const applyDisabled =
rangeError ||
(startMoment !== null && !startMoment.isValid()) ||
(endMoment !== null && !endMoment.isValid()) ||
missingRange;

const openCustomDialog = () => {
setSelectedValue(TimeFilterEnum.custom);
if (!customStart && !customEnd) {
const now = dayjs();
setCustomEnd(now.format('YYYY-MM-DDTHH:mm'));
setCustomStart(now.subtract(24, 'hours').format('YYYY-MM-DDTHH:mm'));
}
setIsDialogOpen(true);
};

const handleSelectChange = (value: string) => {
if (value === TimeFilterEnum.custom) {
openCustomDialog();
return;
}
setSelectedValue(value as TimeFilterEnum);
setTimeSearchParam(value as TimeFilterEnum);
};

const handleCloseDialog = () => {
setIsDialogOpen(false);
setSelectedValue(lastAppliedValue);
setCustomStart(formatInputValue(searchParams.get(START_TIME_QUERY_KEY)));
setCustomEnd(formatInputValue(searchParams.get(END_TIME_QUERY_KEY)));
};

const handleApplyCustom = () => {
if (applyDisabled) {
return;
}
const startISO =
startMoment && startMoment.isValid() ? startMoment.toISOString() : null;
const endISO =
endMoment && endMoment.isValid() ? endMoment.toISOString() : null;
setTimeSearchParam(TimeFilterEnum.custom, startISO, endISO);
setIsDialogOpen(false);
};

return (
<>
<TextField
select
size="small"
variant="outlined"
value={searchParams.get(TIME_QUERY_KEY) ?? ''}
value={selectedValue ?? ''}
onChange={(event) => {
setTimeSearchParam(event.target.value as TimeFilterEnum);
handleSelectChange(event.target.value);
}}
sx={{ pr: 2 }}
>
Expand All @@ -64,11 +176,57 @@ export const DatePicker: React.FC = () => {
sx={{ fontSize: '16px' }}
key={item.value}
value={item.value}
onClick={
item.value === TimeFilterEnum.custom
? () => openCustomDialog()
: undefined
}
>
{item.label}
</MenuItem>
))}
</TextField>
<Dialog open={isDialogOpen} onClose={handleCloseDialog} fullWidth>
<DialogTitle>{t('customTimeRange')}</DialogTitle>
<DialogContent>
<Stack spacing={2} pt={1}>
<TextField
label={t('startTime')}
type="datetime-local"
value={customStart}
onChange={(event) => setCustomStart(event.target.value)}
fullWidth
InputLabelProps={{ shrink: true }}
error={rangeError}
/>
<TextField
label={t('endTime')}
type="datetime-local"
value={customEnd}
onChange={(event) => setCustomEnd(event.target.value)}
fullWidth
InputLabelProps={{ shrink: true }}
error={rangeError}
helperText={rangeError ? t('endTimeBeforeStart') : undefined}
/>
{missingRange && (
<Typography color="error" variant="caption">
{t('enterStartOrEndTime')}
</Typography>
)}
</Stack>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseDialog}>{t('cancel')}</Button>
<Button
variant="contained"
onClick={handleApplyCustom}
disabled={applyDisabled}
>
{t('apply')}
</Button>
</DialogActions>
</Dialog>
</>
);
};
6 changes: 5 additions & 1 deletion src/contexts/DateFilterContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import { ITimeFilterObject, TimeFilterEnum } from '../interfaces';
export interface IDateFilterContext {
searchParams: URLSearchParams;
dateFilter: ITimeFilterObject | undefined;
setTimeSearchParam: (timeFilter: TimeFilterEnum) => void;
setTimeSearchParam: (
timeFilter: TimeFilterEnum,
startTime?: string | null,
endTime?: string | null
) => void;
}

export const DateFilterContext = createContext({} as IDateFilterContext);
7 changes: 5 additions & 2 deletions src/interfaces/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@

export interface ITimeFilterObject {
filterString: string;
filterShortString: any;
filterShortString: TimeFilterEnum;
filterTime: number;
startTime?: number;
endTime?: number;
}

export const times = ['1hour', '24hours', '7days', '30days'];
export const times = ['1hour', '24hours', '7days', '30days', 'custom'];

export enum TimeFilterEnum {
'1hour' = '1hour',
'24hours' = '24hours',
'7days' = '7days',
'30days' = '30days',
'custom' = 'custom',
}

export const ApiFilters = ['id', 'name', 'interface'];
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Activity/views/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,14 @@ export const ActivityEvents: () => JSX.Element = () => {
// Histogram
useEffect(() => {
setIsHistLoading(true);
const currentTime = dayjs().unix();
const histEndTime = dateFilter?.endTime ?? dayjs().unix();
isMounted &&
dateFilter &&
fetchCatcher(
`${FF_Paths.nsPrefix}/${selectedNamespace}${FF_Paths.chartsHistogram(
BucketCollectionEnum.Events,
dateFilter.filterTime,
currentTime,
histEndTime,
BucketCountEnum.Large
)}`
)
Expand Down
Loading