diff --git a/frontend/src/constants/query.ts b/frontend/src/constants/query.ts index ec8dd54e2b..7325252ca8 100644 --- a/frontend/src/constants/query.ts +++ b/frontend/src/constants/query.ts @@ -14,4 +14,6 @@ export enum QueryParams { resourceAttributes = 'resourceAttribute', graphType = 'graphType', widgetId = 'widgetId', + order = 'order', + q = 'q', } diff --git a/frontend/src/container/LogControls/index.tsx b/frontend/src/container/LogControls/index.tsx index e98a15e13d..d0dfe4f27b 100644 --- a/frontend/src/container/LogControls/index.tsx +++ b/frontend/src/container/LogControls/index.tsx @@ -7,6 +7,7 @@ import { getMinMax } from 'container/TopNav/AutoRefresh/config'; import dayjs from 'dayjs'; import { Pagination } from 'hooks/queryPagination'; import { FlatLogData } from 'lib/logs/flatLogData'; +import { OrderPreferenceItems } from 'pages/Logs/config'; import * as Papa from 'papaparse'; import { memo, useCallback, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -31,6 +32,7 @@ function LogControls(): JSX.Element | null { isLoading: isLogsLoading, isLoadingAggregate, logs, + order, } = useSelector((state) => state.logs); const globalTime = useSelector( (state) => state.globalTime, @@ -160,6 +162,7 @@ function LogControls(): JSX.Element | null { loading={isLoading} size="small" type="link" + disabled={order === OrderPreferenceItems.ASC} onClick={handleGoToLatest} > Go to latest diff --git a/frontend/src/container/LogDetailedView/ActionItem.tsx b/frontend/src/container/LogDetailedView/ActionItem.tsx index 28e429d7e2..2d2350a324 100644 --- a/frontend/src/container/LogDetailedView/ActionItem.tsx +++ b/frontend/src/container/LogDetailedView/ActionItem.tsx @@ -2,6 +2,7 @@ import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; import { Button, Col, Popover } from 'antd'; import getStep from 'lib/getStep'; import { generateFilterQuery } from 'lib/logs/generateFilterQuery'; +import { getIdConditions } from 'pages/Logs/utils'; import { memo, useMemo } from 'react'; import { connect, useDispatch, useSelector } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; @@ -45,6 +46,7 @@ function ActionItem({ idStart, liveTail, idEnd, + order, } = useSelector((store) => store.logs); const dispatch = useDispatch>(); @@ -72,11 +74,10 @@ function ActionItem({ q: updatedQueryString, limit: logLinesPerPage, orderBy: 'timestamp', - order: 'desc', + order, timestampStart: minTime, timestampEnd: maxTime, - ...(idStart ? { idGt: idStart } : {}), - ...(idEnd ? { idLt: idEnd } : {}), + ...getIdConditions(idStart, idEnd, order), }); getLogsAggregate({ timestampStart: minTime, diff --git a/frontend/src/container/LogsSearchFilter/index.tsx b/frontend/src/container/LogsSearchFilter/index.tsx index d3de26658b..4b857aa83b 100644 --- a/frontend/src/container/LogsSearchFilter/index.tsx +++ b/frontend/src/container/LogsSearchFilter/index.tsx @@ -2,6 +2,7 @@ import { Input, InputRef, Popover } from 'antd'; import useUrlQuery from 'hooks/useUrlQuery'; import getStep from 'lib/getStep'; import debounce from 'lodash-es/debounce'; +import { getIdConditions } from 'pages/Logs/utils'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { connect, useDispatch, useSelector } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; @@ -33,7 +34,7 @@ function SearchFilter({ const [searchText, setSearchText] = useState(queryString); const [showDropDown, setShowDropDown] = useState(false); const searchRef = useRef(null); - const { logLinesPerPage, idEnd, idStart, liveTail } = useSelector< + const { logLinesPerPage, idEnd, idStart, liveTail, order } = useSelector< AppState, ILogsReducer >((state) => state.logs); @@ -99,11 +100,10 @@ function SearchFilter({ q: customQuery, limit: logLinesPerPage, orderBy: 'timestamp', - order: 'desc', + order, timestampStart: minTime, timestampEnd: maxTime, - ...(idStart ? { idGt: idStart } : {}), - ...(idEnd ? { idLt: idEnd } : {}), + ...getIdConditions(idStart, idEnd, order), }); getLogsAggregate({ @@ -128,6 +128,7 @@ function SearchFilter({ logLinesPerPage, globalTime, getLogsFields, + order, ], ); @@ -160,6 +161,7 @@ function SearchFilter({ dispatch, globalTime.maxTime, globalTime.minTime, + order, ]); const onPopOverChange = useCallback( diff --git a/frontend/src/container/LogsSearchFilter/useSearchParser.ts b/frontend/src/container/LogsSearchFilter/useSearchParser.ts index 54b483f4e7..00da96856d 100644 --- a/frontend/src/container/LogsSearchFilter/useSearchParser.ts +++ b/frontend/src/container/LogsSearchFilter/useSearchParser.ts @@ -1,3 +1,4 @@ +import { QueryParams } from 'constants/query'; import { getMinMax } from 'container/TopNav/AutoRefresh/config'; import useUrlQuery from 'hooks/useUrlQuery'; import history from 'lib/history'; @@ -25,6 +26,7 @@ export function useSearchParser(): { const dispatch = useDispatch>(); const { searchFilter: { parsedQuery, queryString }, + order, } = useSelector((store) => store.logs); const urlQuery = useUrlQuery(); @@ -39,7 +41,7 @@ export function useSearchParser(): { (updatedQueryString: string) => { history.replace({ pathname: history.location.pathname, - search: `?q=${updatedQueryString}`, + search: `?${QueryParams.q}=${updatedQueryString}&${QueryParams.order}=${order}`, }); const globalTime = getMinMax(selectedTime, minTime, maxTime); diff --git a/frontend/src/pages/Logs/config.ts b/frontend/src/pages/Logs/config.ts index 60f46195bd..39a251cb05 100644 --- a/frontend/src/pages/Logs/config.ts +++ b/frontend/src/pages/Logs/config.ts @@ -25,3 +25,24 @@ export const logsOptions = ['raw', 'table']; export const defaultSelectStyle: CSSProperties = { minWidth: '6rem', }; + +export enum OrderPreferenceItems { + DESC = 'desc', + ASC = 'asc', +} + +export const orderItems: OrderPreference[] = [ + { + name: 'Descending', + enum: OrderPreferenceItems.DESC, + }, + { + name: 'Ascending', + enum: OrderPreferenceItems.ASC, + }, +]; + +export interface OrderPreference { + name: string; + enum: OrderPreferenceItems; +} diff --git a/frontend/src/pages/Logs/index.tsx b/frontend/src/pages/Logs/index.tsx index 76433a7c4d..4810d3e00b 100644 --- a/frontend/src/pages/Logs/index.tsx +++ b/frontend/src/pages/Logs/index.tsx @@ -1,4 +1,5 @@ import { Button, Col, Divider, Popover, Row, Select, Space } from 'antd'; +import { QueryParams } from 'constants/query'; import LogControls from 'container/LogControls'; import LogDetailedView from 'container/LogDetailedView'; import LogLiveTail from 'container/LogLiveTail'; @@ -6,20 +7,31 @@ import LogsAggregate from 'container/LogsAggregate'; import LogsFilters from 'container/LogsFilters'; import LogsSearchFilter from 'container/LogsSearchFilter'; import LogsTable from 'container/LogsTable'; +import history from 'lib/history'; import { useCallback, useMemo } from 'react'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; import { Dispatch } from 'redux'; +import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; -import { SET_DETAILED_LOG_DATA } from 'types/actions/logs'; +import { SET_DETAILED_LOG_DATA, SET_LOGS_ORDER } from 'types/actions/logs'; import { ILog } from 'types/api/logs/log'; +import { ILogsReducer } from 'types/reducer/logs'; -import { defaultSelectStyle, logsOptions } from './config'; +import { + defaultSelectStyle, + logsOptions, + orderItems, + OrderPreferenceItems, +} from './config'; import { useSelectedLogView } from './hooks'; import PopoverContent from './PopoverContent'; import SpaceContainer from './styles'; function Logs(): JSX.Element { const dispatch = useDispatch>(); + const { order } = useSelector((store) => store.logs); + const location = useLocation(); const showExpandedLog = useCallback( (logData: ILog) => { @@ -67,6 +79,16 @@ function Logs(): JSX.Element { [handleViewModeOptionChange], ); + const handleChangeOrder = (value: OrderPreferenceItems): void => { + dispatch({ + type: SET_LOGS_ORDER, + payload: value, + }); + const params = new URLSearchParams(location.search); + params.set(QueryParams.order, value); + history.push({ search: params.toString() }); + }; + return ( <> Format )} + + diff --git a/frontend/src/pages/Logs/utils.ts b/frontend/src/pages/Logs/utils.ts index 2d8f317a9a..8fffa3bb21 100644 --- a/frontend/src/pages/Logs/utils.ts +++ b/frontend/src/pages/Logs/utils.ts @@ -1,7 +1,29 @@ import { LogViewMode } from 'container/LogsTable'; -import { viewModeOptionList } from './config'; +import { OrderPreferenceItems, viewModeOptionList } from './config'; export const isLogViewMode = (value: unknown): value is LogViewMode => typeof value === 'string' && viewModeOptionList.some((option) => option.key === value); + +export const getIdConditions = ( + idStart: string, + idEnd: string, + order: OrderPreferenceItems, +): Record => { + const idConditions: Record = {}; + + if (idStart && order === OrderPreferenceItems.ASC) { + idConditions.idLt = idStart; + } else if (idStart) { + idConditions.idGt = idStart; + } + + if (idEnd && order === OrderPreferenceItems.ASC) { + idConditions.idGt = idEnd; + } else if (idEnd) { + idConditions.idLt = idEnd; + } + + return idConditions; +}; diff --git a/frontend/src/store/reducers/logs.ts b/frontend/src/store/reducers/logs.ts index 06c6b41b70..6572834fde 100644 --- a/frontend/src/store/reducers/logs.ts +++ b/frontend/src/store/reducers/logs.ts @@ -1,4 +1,5 @@ import { parseQuery } from 'lib/logql'; +import { OrderPreferenceItems } from 'pages/Logs/config'; import { ADD_SEARCH_FIELD_QUERY_STRING, FLUSH_LOGS, @@ -17,6 +18,7 @@ import { SET_LOG_LINES_PER_PAGE, SET_LOGS, SET_LOGS_AGGREGATE_SERIES, + SET_LOGS_ORDER, SET_SEARCH_QUERY_PARSED_PAYLOAD, SET_SEARCH_QUERY_STRING, SET_VIEW_MODE, @@ -49,6 +51,10 @@ const initialState: ILogsReducer = { liveTailStartRange: 15, selectedLogId: null, detailedLog: null, + order: + (new URLSearchParams(window.location.search).get( + 'order', + ) as ILogsReducer['order']) ?? OrderPreferenceItems.DESC, }; export const LogsReducer = ( @@ -129,6 +135,17 @@ export const LogsReducer = ( logs: logsData, }; } + + case SET_LOGS_ORDER: { + const order = action.payload; + return { + ...state, + order, + idStart: '', + idEnd: '', + }; + } + case SET_LOG_LINES_PER_PAGE: { return { ...state, diff --git a/frontend/src/types/actions/logs.ts b/frontend/src/types/actions/logs.ts index 15b2663a6f..fbaa14a345 100644 --- a/frontend/src/types/actions/logs.ts +++ b/frontend/src/types/actions/logs.ts @@ -1,6 +1,7 @@ import { LogViewMode } from 'container/LogsTable'; import { Pagination } from 'hooks/queryPagination'; import { ILogQLParsedQueryItem } from 'lib/logql/types'; +import { OrderPreferenceItems } from 'pages/Logs/config'; import { IField, IFieldMoveToSelected, IFields } from 'types/api/logs/fields'; import { TLogsLiveTailState } from 'types/api/logs/liveTail'; import { ILog } from 'types/api/logs/log'; @@ -34,6 +35,7 @@ export const SET_LINES_PER_ROW = 'SET_LINES_PER_ROW'; export const SET_VIEW_MODE = 'SET_VIEW_MODE'; export const UPDATE_SELECTED_FIELDS = 'LOGS_UPDATE_SELECTED_FIELDS'; export const UPDATE_INTERESTING_FIELDS = 'LOGS_UPDATE_INTERESTING_FIELDS'; +export const SET_LOGS_ORDER = 'SET_LOGS_ORDER'; export interface GetFields { type: typeof GET_FIELDS; @@ -141,6 +143,11 @@ export interface UpdateSelectedInterestFields { }; } +export interface SetLogsOrder { + type: typeof SET_LOGS_ORDER; + payload: OrderPreferenceItems; +} + export type LogsActions = | GetFields | SetFields @@ -164,4 +171,5 @@ export type LogsActions = | SetLiveTailStartTime | SetLinesPerRow | SetViewMode - | UpdateSelectedInterestFields; + | UpdateSelectedInterestFields + | SetLogsOrder; diff --git a/frontend/src/types/reducer/logs.ts b/frontend/src/types/reducer/logs.ts index 2a873122f4..52a216f62f 100644 --- a/frontend/src/types/reducer/logs.ts +++ b/frontend/src/types/reducer/logs.ts @@ -1,6 +1,7 @@ import { LogViewMode } from 'container/LogsTable'; import { Pagination } from 'hooks/queryPagination'; import { ILogQLParsedQueryItem } from 'lib/logql/types'; +import { OrderPreferenceItems } from 'pages/Logs/config'; import { IFields } from 'types/api/logs/fields'; import { TLogsLiveTailState } from 'types/api/logs/liveTail'; import { ILog } from 'types/api/logs/log'; @@ -25,6 +26,7 @@ export interface ILogsReducer { detailedLog: null | ILog; liveTail: TLogsLiveTailState; liveTailStartRange: number; // time in minutes + order: OrderPreferenceItems; } export default ILogsReducer;