feat: global time is updated (#2013)

This commit is contained in:
Palash Gupta 2023-02-02 11:12:12 +05:30 committed by GitHub
parent 48659a2957
commit 17f32e9765
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 429 additions and 224 deletions

View File

@ -2,7 +2,9 @@ import { Button, Popover } from 'antd';
import { generateFilterQuery } from 'lib/logs/generateFilterQuery'; import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
import React, { memo, useCallback, useMemo } from 'react'; import React, { memo, useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs'; import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs';
import { ILogsReducer } from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
@ -14,7 +16,7 @@ function AddToQueryHOC({
const { const {
searchFilter: { queryString }, searchFilter: { queryString },
} = useSelector<AppState, ILogsReducer>((store) => store.logs); } = useSelector<AppState, ILogsReducer>((store) => store.logs);
const dispatch = useDispatch(); const dispatch = useDispatch<Dispatch<AppActions>>();
const generatedQuery = useMemo( const generatedQuery = useMemo(
() => generateFilterQuery({ fieldKey, fieldValue, type: 'IN' }), () => generateFilterQuery({ fieldKey, fieldValue, type: 'IN' }),
@ -31,7 +33,9 @@ function AddToQueryHOC({
} }
dispatch({ dispatch({
type: SET_SEARCH_QUERY_STRING, type: SET_SEARCH_QUERY_STRING,
payload: updatedQueryString, payload: {
searchQueryString: updatedQueryString,
},
}); });
}, [dispatch, generatedQuery, queryString]); }, [dispatch, generatedQuery, queryString]);

View File

@ -101,15 +101,9 @@ function LogItem({ logData }: LogItemProps): JSX.Element {
{'{'} {'{'}
<LogContainer> <LogContainer>
<> <>
<LogGeneralField <LogGeneralField fieldKey="log" fieldValue={flattenLogData.body} />
fieldKey="log"
fieldValue={flattenLogData.body as never}
/>
{flattenLogData.stream && ( {flattenLogData.stream && (
<LogGeneralField <LogGeneralField fieldKey="stream" fieldValue={flattenLogData.stream} />
fieldKey="stream"
fieldValue={flattenLogData.stream as never}
/>
)} )}
<LogGeneralField <LogGeneralField
fieldKey="timestamp" fieldKey="timestamp"

View File

@ -37,6 +37,7 @@ const themeColors = {
matterhornGrey: '#555555', matterhornGrey: '#555555',
whiteCream: '#ffffffd5', whiteCream: '#ffffffd5',
black: '#000000', black: '#000000',
lightgrey: '#ddd',
}; };
export { themeColors }; export { themeColors };

View File

@ -4,6 +4,8 @@ import {
RightOutlined, RightOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Button, Divider, Select } from 'antd'; import { Button, Divider, Select } from 'antd';
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
import React, { memo, useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
@ -13,6 +15,7 @@ import {
RESET_ID_START_AND_END, RESET_ID_START_AND_END,
SET_LOG_LINES_PER_PAGE, SET_LOG_LINES_PER_PAGE,
} from 'types/actions/logs'; } from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime';
import { ILogsReducer } from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import { ITEMS_PER_PAGE_OPTIONS } from './config'; import { ITEMS_PER_PAGE_OPTIONS } from './config';
@ -28,18 +31,33 @@ function LogControls(): JSX.Element | null {
isLoadingAggregate, isLoadingAggregate,
logs, logs,
} = useSelector<AppState, ILogsReducer>((state) => state.logs); } = useSelector<AppState, ILogsReducer>((state) => state.logs);
const globalTime = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleLogLinesPerPageChange = (e: number): void => { const handleLogLinesPerPageChange = (e: number): void => {
dispatch({ dispatch({
type: SET_LOG_LINES_PER_PAGE, type: SET_LOG_LINES_PER_PAGE,
payload: e, payload: {
logLinesPerPage: e,
},
}); });
}; };
const handleGoToLatest = (): void => { const handleGoToLatest = (): void => {
const { maxTime, minTime } = getMinMax(
globalTime.selectedTime,
globalTime.minTime,
globalTime.maxTime,
);
dispatch({ dispatch({
type: RESET_ID_START_AND_END, type: RESET_ID_START_AND_END,
payload: getGlobalTime(globalTime.selectedTime, {
maxTime,
minTime,
}),
}); });
}; };

View File

@ -4,7 +4,7 @@ import getStep from 'lib/getStep';
import { generateFilterQuery } from 'lib/logs/generateFilterQuery'; import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
import React, { memo, useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux'; import { connect, useDispatch, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { getLogs } from 'store/actions/logs/getLogs'; import { getLogs } from 'store/actions/logs/getLogs';
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate'; import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
@ -46,7 +46,7 @@ function ActionItem({
liveTail, liveTail,
idEnd, idEnd,
} = useSelector<AppState, ILogsReducer>((store) => store.logs); } = useSelector<AppState, ILogsReducer>((store) => store.logs);
const dispatch = useDispatch(); const dispatch = useDispatch<Dispatch<AppActions>>();
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@ -62,7 +62,9 @@ function ActionItem({
} }
dispatch({ dispatch({
type: SET_SEARCH_QUERY_STRING, type: SET_SEARCH_QUERY_STRING,
payload: updatedQueryString, payload: {
searchQueryString: updatedQueryString,
},
}); });
if (liveTail === 'STOPPED') { if (liveTail === 'STOPPED') {

View File

@ -64,7 +64,6 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) { if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
return ( return (
<AddToQueryHOC fieldKey={fieldKey[0]} fieldValue={flattenLogData[field]}> <AddToQueryHOC fieldKey={fieldKey[0]} fieldValue={flattenLogData[field]}>
{' '}
{renderedField} {renderedField}
</AddToQueryHOC> </AddToQueryHOC>
); );

View File

@ -1,20 +1,17 @@
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined } from '@ant-design/icons';
import { Button, Popover, Spin } from 'antd'; import { Button, Popover, Spin, Typography } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import React, { useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import {
IField,
IInterestingFields,
ISelectedFields,
} from 'types/api/logs/fields';
import { ICON_STYLE } from './config';
import { Field } from './styles'; import { Field } from './styles';
interface FieldItemProps { function FieldItem({
name: string;
buttonIcon: React.ReactNode;
buttonOnClick: (arg0: Record<string, unknown>) => void;
fieldData: Record<string, never>;
fieldIndex: number;
isLoading: boolean;
iconHoverText: string;
}
export function FieldItem({
name, name,
buttonIcon, buttonIcon,
buttonOnClick, buttonOnClick,
@ -23,33 +20,65 @@ export function FieldItem({
isLoading, isLoading,
iconHoverText, iconHoverText,
}: FieldItemProps): JSX.Element { }: FieldItemProps): JSX.Element {
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState<boolean>(false);
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
const onClickHandler = useCallback(() => {
if (!isLoading && buttonOnClick) buttonOnClick({ fieldData, fieldIndex });
}, [buttonOnClick, fieldData, fieldIndex, isLoading]);
const renderContent = useMemo(() => {
if (isLoading) {
return <Spin spinning size="small" indicator={<LoadingOutlined spin />} />;
}
if (isHovered) {
return (
<Popover content={<Typography>{iconHoverText}</Typography>}>
<Button
size="small"
type="text"
icon={buttonIcon}
onClick={onClickHandler}
/>
</Popover>
);
}
return null;
}, [buttonIcon, iconHoverText, isHovered, isLoading, onClickHandler]);
const onMouseHoverHandler = useCallback(
(value: boolean) => (): void => {
setIsHovered(value);
},
[],
);
return ( return (
<Field <Field
onMouseEnter={(): void => { onMouseEnter={onMouseHoverHandler(true)}
setIsHovered(true); onMouseLeave={onMouseHoverHandler(false)}
}}
onMouseLeave={(): void => setIsHovered(false)}
isDarkMode={isDarkMode} isDarkMode={isDarkMode}
> >
<span>{name}</span> <Typography style={ICON_STYLE.PLUS}>{name}</Typography>
{isLoading ? (
<Spin spinning size="small" indicator={<LoadingOutlined spin />} /> {renderContent}
) : (
isHovered &&
buttonOnClick && (
<Popover content={<span>{iconHoverText}</span>}>
<Button
type="text"
size="small"
icon={buttonIcon}
onClick={(): void => buttonOnClick({ fieldData, fieldIndex })}
style={{ color: 'inherit', padding: 0, height: '1rem', width: '1rem' }}
/>
</Popover>
)
)}
</Field> </Field>
); );
} }
interface FieldItemProps {
name: string;
buttonIcon: React.ReactNode;
buttonOnClick: (props: {
fieldData: IInterestingFields | ISelectedFields;
fieldIndex: number;
}) => void;
fieldData: IField;
fieldIndex: number;
isLoading: boolean;
iconHoverText: string;
}
export default FieldItem;

View File

@ -0,0 +1,8 @@
import { blue, red } from '@ant-design/colors';
export const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id'];
export const ICON_STYLE = {
PLUS: { color: blue[5] },
CLOSE: { color: red[5] },
};

View File

@ -1,27 +1,19 @@
/* eslint-disable react/no-array-index-key */
import { red } from '@ant-design/colors';
import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons'; import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons';
import { Input } from 'antd'; import { Input } from 'antd';
import AddToSelectedFields from 'api/logs/AddToSelectedField';
import RemoveSelectedField from 'api/logs/RemoveFromSelectedField';
import CategoryHeading from 'components/Logs/CategoryHeading'; import CategoryHeading from 'components/Logs/CategoryHeading';
import { fieldSearchFilter } from 'lib/logs/fieldSearch'; import { fieldSearchFilter } from 'lib/logs/fieldSearch';
import React, { memo, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { connect, useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import { GetLogsFields } from 'store/actions/logs/getFields';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { IInterestingFields, ISelectedFields } from 'types/api/logs/fields';
import { ILogsReducer } from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import { FieldItem } from './FieldItem'; import { ICON_STYLE } from './config';
import FieldItem from './FieldItem';
import { CategoryContainer, Container, FieldContainer } from './styles'; import { CategoryContainer, Container, FieldContainer } from './styles';
import { IHandleInterestProps, IHandleRemoveInterestProps } from './types';
import { onHandleAddInterest, onHandleRemoveInterest } from './utils';
const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id']; function LogsFilters(): JSX.Element {
function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
const { const {
fields: { interesting, selected }, fields: { interesting, selected },
} = useSelector<AppState, ILogsReducer>((state) => state.logs); } = useSelector<AppState, ILogsReducer>((state) => state.logs);
@ -36,57 +28,40 @@ function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
setFilterValuesInput((e.target as HTMLInputElement).value); setFilterValuesInput((e.target as HTMLInputElement).value);
}; };
const handleAddInterestingToSelected = async ({ const onHandleAddSelectedToInteresting = useCallback(
fieldData, ({ fieldData, fieldIndex }: IHandleInterestProps) => (): Promise<void> =>
fieldIndex, onHandleAddInterest({
}: { fieldData,
fieldData: IInterestingFields; fieldIndex,
fieldIndex: number; interesting,
}): Promise<void> => { interestingFieldLoading,
setInterestingFieldLoading((prevState: number[]) => { setInterestingFieldLoading,
prevState.push(fieldIndex); selected,
return [...prevState]; }),
}); [interesting, interestingFieldLoading, selected],
);
await AddToSelectedFields({ const onHandleRemoveSelected = useCallback(
...fieldData, ({
selected: true, fieldData,
}); fieldIndex,
getLogsFields(); }: IHandleRemoveInterestProps) => (): Promise<void> =>
onHandleRemoveInterest({
fieldData,
fieldIndex,
interesting,
interestingFieldLoading,
selected,
setSelectedFieldLoading,
}),
[interesting, interestingFieldLoading, selected, setSelectedFieldLoading],
);
setInterestingFieldLoading(
interestingFieldLoading.filter((e) => e !== fieldIndex),
);
};
const handleRemoveSelectedField = async ({
fieldData,
fieldIndex,
}: {
fieldData: ISelectedFields;
fieldIndex: number;
}): Promise<void> => {
setSelectedFieldLoading((prevState) => {
prevState.push(fieldIndex);
return [...prevState];
});
await RemoveSelectedField({
...fieldData,
selected: false,
});
getLogsFields();
setSelectedFieldLoading(
interestingFieldLoading.filter((e) => e !== fieldIndex),
);
};
return ( return (
<Container flex="450px"> <Container flex="450px">
<Input <Input
placeholder="Filter Values" placeholder="Filter Values"
onInput={handleSearch} onInput={handleSearch}
style={{ width: '100%' }}
value={filterValuesInput} value={filterValuesInput}
onChange={handleSearch} onChange={handleSearch}
/> />
@ -98,15 +73,15 @@ function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
.filter((field) => fieldSearchFilter(field.name, filterValuesInput)) .filter((field) => fieldSearchFilter(field.name, filterValuesInput))
.map((field, idx) => ( .map((field, idx) => (
<FieldItem <FieldItem
key={`${JSON.stringify(field)}-${idx}`} key={`${JSON.stringify(field)}`}
name={field.name} name={field.name}
fieldData={field as never} fieldData={field}
fieldIndex={idx} fieldIndex={idx}
buttonIcon={<CloseOutlined style={{ color: red[5] }} />} buttonIcon={<CloseOutlined style={ICON_STYLE.CLOSE} />}
buttonOnClick={ buttonOnClick={onHandleRemoveSelected({
(!RESTRICTED_SELECTED_FIELDS.includes(field.name) && fieldData: field,
handleRemoveSelectedField) as never fieldIndex: idx,
} })}
isLoading={selectedFieldLoading.includes(idx)} isLoading={selectedFieldLoading.includes(idx)}
iconHoverText="Remove from Selected Fields" iconHoverText="Remove from Selected Fields"
/> />
@ -120,33 +95,23 @@ function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
.filter((field) => fieldSearchFilter(field.name, filterValuesInput)) .filter((field) => fieldSearchFilter(field.name, filterValuesInput))
.map((field, idx) => ( .map((field, idx) => (
<FieldItem <FieldItem
key={`${JSON.stringify(field)}-${idx}`} key={`${JSON.stringify(field)}`}
name={field.name} name={field.name}
fieldData={field as never} fieldData={field}
fieldIndex={idx} fieldIndex={idx}
buttonIcon={<PlusCircleFilled />} buttonIcon={<PlusCircleFilled style={ICON_STYLE.PLUS} />}
buttonOnClick={handleAddInterestingToSelected as never} buttonOnClick={onHandleAddSelectedToInteresting({
fieldData: field,
fieldIndex: idx,
})}
isLoading={interestingFieldLoading.includes(idx)} isLoading={interestingFieldLoading.includes(idx)}
iconHoverText="Add to Selected Fields" iconHoverText="Add to Selected Fields"
/> />
))} ))}
</FieldContainer> </FieldContainer>
</CategoryContainer> </CategoryContainer>
{/* <ExtractField>Extract Fields</ExtractField> */}
</Container> </Container>
); );
} }
interface DispatchProps { export default LogsFilters;
getLogsFields: () => (dispatch: Dispatch<AppActions>) => void;
}
const mapDispatchToProps = (
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
): DispatchProps => ({
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
});
type LogsFiltersProps = DispatchProps;
export default connect(null, mapDispatchToProps)(memo(LogsFilters));

View File

@ -4,8 +4,8 @@ import styled from 'styled-components';
export const Container = styled(Col)` export const Container = styled(Col)`
padding-top: 0.3rem; padding-top: 0.3rem;
min-width: 250px; min-width: 15.625rem;
max-width: 350px; max-width: 21.875rem;
`; `;
export const CategoryContainer = styled.div` export const CategoryContainer = styled.div`
@ -21,6 +21,7 @@ export const FieldContainer = styled(Typography.Text)`
export const Field = styled.div<{ isDarkMode: boolean }>` export const Field = styled.div<{ isDarkMode: boolean }>`
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 0.3rem 0.5rem; padding: 0.3rem 0.5rem;
height: 2rem;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;

View File

@ -0,0 +1,35 @@
import {
IField,
IInterestingFields,
ISelectedFields,
} from 'types/api/logs/fields';
type SetLoading = (value: React.SetStateAction<number[]>) => void;
export type IHandleInterestProps = {
fieldData: IInterestingFields;
fieldIndex: number;
};
export type IHandleRemoveInterestProps = {
fieldData: ISelectedFields;
fieldIndex: number;
};
export interface OnHandleAddInterestProps {
setInterestingFieldLoading: SetLoading;
fieldIndex: number;
fieldData: ISelectedFields;
interesting: IField[];
interestingFieldLoading: number[];
selected: IField[];
}
export interface OnHandleRemoveInterestProps {
setSelectedFieldLoading: SetLoading;
selected: IField[];
interesting: IField[];
interestingFieldLoading: number[];
fieldData: IInterestingFields;
fieldIndex: number;
}

View File

@ -0,0 +1,94 @@
import AddToSelectedFields from 'api/logs/AddToSelectedField';
import RemoveSelectedField from 'api/logs/RemoveFromSelectedField';
import store from 'store';
import {
UPDATE_INTERESTING_FIELDS,
UPDATE_SELECTED_FIELDS,
} from 'types/actions/logs';
import { RESTRICTED_SELECTED_FIELDS } from './config';
import { OnHandleAddInterestProps, OnHandleRemoveInterestProps } from './types';
export const onHandleAddInterest = async ({
setInterestingFieldLoading,
fieldIndex,
fieldData,
interesting,
interestingFieldLoading,
selected,
}: OnHandleAddInterestProps): Promise<void> => {
const { dispatch } = store;
setInterestingFieldLoading((prevState: number[]) => {
prevState.push(fieldIndex);
return [...prevState];
});
await AddToSelectedFields({
...fieldData,
selected: true,
});
dispatch({
type: UPDATE_INTERESTING_FIELDS,
payload: {
field: interesting.filter((e) => e.name !== fieldData.name),
type: 'selected',
},
});
dispatch({
type: UPDATE_SELECTED_FIELDS,
payload: {
field: [...selected, fieldData],
type: 'selected',
},
});
setInterestingFieldLoading(
interestingFieldLoading.filter((e) => e !== fieldIndex),
);
};
export const onHandleRemoveInterest = async ({
setSelectedFieldLoading,
selected,
interesting,
interestingFieldLoading,
fieldData,
fieldIndex,
}: OnHandleRemoveInterestProps): Promise<void> => {
if (RESTRICTED_SELECTED_FIELDS.includes(fieldData.name)) return;
const { dispatch } = store;
setSelectedFieldLoading((prevState) => {
prevState.push(fieldIndex);
return [...prevState];
});
await RemoveSelectedField({
...fieldData,
selected: false,
});
dispatch({
type: UPDATE_SELECTED_FIELDS,
payload: {
field: selected.filter((e) => e.name !== fieldData.name),
type: 'selected',
},
});
dispatch({
type: UPDATE_INTERESTING_FIELDS,
payload: {
field: [...interesting, fieldData],
type: 'interesting',
},
});
setSelectedFieldLoading(
interestingFieldLoading.filter((e) => e !== fieldIndex),
);
};

View File

@ -1,8 +1,9 @@
import { Input, InputRef, Popover } from 'antd'; import { Input, InputRef, Popover } from 'antd';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import getStep from 'lib/getStep'; import getStep from 'lib/getStep';
import { debounce } from 'lodash-es'; import debounce from 'lodash-es/debounce';
import React, { import React, {
memo,
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
@ -12,6 +13,7 @@ import React, {
import { connect, useDispatch, useSelector } from 'react-redux'; import { connect, useDispatch, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux'; import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { GetLogsFields } from 'store/actions/logs/getFields';
import { getLogs } from 'store/actions/logs/getLogs'; import { getLogs } from 'store/actions/logs/getLogs';
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate'; import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
@ -32,6 +34,7 @@ import { useSearchParser } from './useSearchParser';
function SearchFilter({ function SearchFilter({
getLogs, getLogs,
getLogsAggregate, getLogsAggregate,
getLogsFields,
}: SearchFilterProps): JSX.Element { }: SearchFilterProps): JSX.Element {
const { const {
updateParsedQuery, updateParsedQuery,
@ -45,7 +48,7 @@ function SearchFilter({
AppState, AppState,
ILogsReducer ILogsReducer
>((state) => state.logs); >((state) => state.logs);
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const globalTime = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
); );
const dispatch = useDispatch<Dispatch<AppActions>>(); const dispatch = useDispatch<Dispatch<AppActions>>();
@ -69,6 +72,8 @@ function SearchFilter({
const handleSearch = useCallback( const handleSearch = useCallback(
(customQuery: string) => { (customQuery: string) => {
getLogsFields();
if (liveTail === 'PLAYING') { if (liveTail === 'PLAYING') {
dispatch({ dispatch({
type: TOGGLE_LIVE_TAIL, type: TOGGLE_LIVE_TAIL,
@ -77,15 +82,13 @@ function SearchFilter({
dispatch({ dispatch({
type: FLUSH_LOGS, type: FLUSH_LOGS,
}); });
setTimeout( dispatch({
() => type: TOGGLE_LIVE_TAIL,
dispatch({ payload: liveTail,
type: TOGGLE_LIVE_TAIL, });
payload: liveTail,
}),
0,
);
} else { } else {
const { maxTime, minTime } = globalTime;
getLogs({ getLogs({
q: customQuery, q: customQuery,
limit: logLinesPerPage, limit: logLinesPerPage,
@ -117,8 +120,8 @@ function SearchFilter({
idStart, idStart,
liveTail, liveTail,
logLinesPerPage, logLinesPerPage,
maxTime, globalTime,
minTime, getLogsFields,
], ],
); );
@ -145,12 +148,12 @@ function SearchFilter({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
urlQueryString, urlQueryString,
maxTime,
minTime,
idEnd, idEnd,
idStart, idStart,
logLinesPerPage, logLinesPerPage,
dispatch, dispatch,
globalTime.maxTime,
globalTime.minTime,
]); ]);
return ( return (
@ -194,6 +197,7 @@ function SearchFilter({
interface DispatchProps { interface DispatchProps {
getLogs: typeof getLogs; getLogs: typeof getLogs;
getLogsAggregate: typeof getLogsAggregate; getLogsAggregate: typeof getLogsAggregate;
getLogsFields: typeof GetLogsFields;
} }
type SearchFilterProps = DispatchProps; type SearchFilterProps = DispatchProps;
@ -203,6 +207,7 @@ const mapDispatchToProps = (
): DispatchProps => ({ ): DispatchProps => ({
getLogs: bindActionCreators(getLogs, dispatch), getLogs: bindActionCreators(getLogs, dispatch),
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch), getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
}); });
export default connect(null, mapDispatchToProps)(SearchFilter); export default connect(null, mapDispatchToProps)(memo(SearchFilter));

View File

@ -1,38 +1,54 @@
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
import history from 'lib/history'; import history from 'lib/history';
import { parseQuery, reverseParser } from 'lib/logql'; import { parseQuery, reverseParser } from 'lib/logql';
import { ILogQLParsedQueryItem } from 'lib/logql/types'; import { ILogQLParsedQueryItem } from 'lib/logql/types';
import isEqual from 'lodash-es/isEqual'; import isEqual from 'lodash-es/isEqual';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { import {
SET_SEARCH_QUERY_PARSED_PAYLOAD, SET_SEARCH_QUERY_PARSED_PAYLOAD,
SET_SEARCH_QUERY_STRING, SET_SEARCH_QUERY_STRING,
} from 'types/actions/logs'; } from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime';
import { ILogsReducer } from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import { getGlobalTime } from './utils';
export function useSearchParser(): { export function useSearchParser(): {
queryString: string; queryString: string;
parsedQuery: unknown; parsedQuery: unknown;
updateParsedQuery: (arg0: ILogQLParsedQueryItem[]) => void; updateParsedQuery: (arg0: ILogQLParsedQueryItem[]) => void;
updateQueryString: (arg0: string) => void; updateQueryString: (arg0: string) => void;
} { } {
const dispatch = useDispatch(); const dispatch = useDispatch<Dispatch<AppActions>>();
const { const {
searchFilter: { parsedQuery, queryString }, searchFilter: { parsedQuery, queryString },
} = useSelector<AppState, ILogsReducer>((store) => store.logs); } = useSelector<AppState, ILogsReducer>((store) => store.logs);
const { minTime, maxTime, selectedTime } = useSelector<
AppState,
GlobalReducer
>((store) => store.globalTime);
const updateQueryString = useCallback( const updateQueryString = useCallback(
(updatedQueryString: string) => { (updatedQueryString: string) => {
history.replace({ history.replace({
pathname: history.location.pathname, pathname: history.location.pathname,
search: updatedQueryString ? `?q=${updatedQueryString}` : '', search: `?q=${updatedQueryString}`,
}); });
const globalTime = getMinMax(selectedTime, minTime, maxTime);
dispatch({ dispatch({
type: SET_SEARCH_QUERY_STRING, type: SET_SEARCH_QUERY_STRING,
payload: updatedQueryString, payload: {
searchQueryString: updatedQueryString,
globalTime: getGlobalTime(selectedTime, globalTime),
},
}); });
const parsedQueryFromString = parseQuery(updatedQueryString); const parsedQueryFromString = parseQuery(updatedQueryString);
if (!isEqual(parsedQuery, parsedQueryFromString)) { if (!isEqual(parsedQuery, parsedQueryFromString)) {
dispatch({ dispatch({
@ -41,11 +57,13 @@ export function useSearchParser(): {
}); });
} }
}, },
// need to hide this warning as we don't want to update the query string on every change
// eslint-disable-next-line react-hooks/exhaustive-deps
[dispatch, parsedQuery], [dispatch, parsedQuery],
); );
useEffect(() => { useEffect(() => {
if (queryString !== null) updateQueryString(queryString); updateQueryString(queryString);
}, [queryString, updateQueryString]); }, [queryString, updateQueryString]);
const updateParsedQuery = useCallback( const updateParsedQuery = useCallback(
@ -61,7 +79,9 @@ export function useSearchParser(): {
) { ) {
dispatch({ dispatch({
type: SET_SEARCH_QUERY_STRING, type: SET_SEARCH_QUERY_STRING,
payload: reversedParsedQuery, payload: {
searchQueryString: reversedParsedQuery,
},
}); });
} }
}, },

View File

@ -0,0 +1,12 @@
import { Time } from 'container/TopNav/DateTimeSelection/config';
import { GetMinMaxPayload } from 'lib/getMinMax';
export const getGlobalTime = (
selectedTime: Time,
globalTime: GetMinMaxPayload,
): GetMinMaxPayload | undefined => {
if (selectedTime === 'custom') {
return undefined;
}
return globalTime;
};

View File

@ -1,7 +1,7 @@
import { ILog } from 'types/api/logs/log'; import { ILog } from 'types/api/logs/log';
export function FlatLogData(log: ILog): Record<string, unknown> { export function FlatLogData(log: ILog): Record<string, string> {
const flattenLogObject: Record<string, unknown> = {}; const flattenLogObject: Record<string, string> = {};
Object.keys(log).forEach((key: string): void => { Object.keys(log).forEach((key: string): void => {
if (typeof log[key as never] !== 'object') { if (typeof log[key as never] !== 'object') {

View File

@ -6,39 +6,11 @@ import LogsAggregate from 'container/LogsAggregate';
import LogsFilters from 'container/LogsFilters'; import LogsFilters from 'container/LogsFilters';
import LogsSearchFilter from 'container/LogsSearchFilter'; import LogsSearchFilter from 'container/LogsSearchFilter';
import LogsTable from 'container/LogsTable'; import LogsTable from 'container/LogsTable';
import useMountedState from 'hooks/useMountedState'; import React from 'react';
import useUrlQuery from 'hooks/useUrlQuery';
import React, { memo, useEffect } from 'react';
import { connect, useDispatch } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import { GetLogsFields } from 'store/actions/logs/getFields';
import AppActions from 'types/actions';
import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs';
import SpaceContainer from './styles'; import SpaceContainer from './styles';
function Logs({ getLogsFields }: LogsProps): JSX.Element { function Logs(): JSX.Element {
const getMountedState = useMountedState();
const urlQuery = useUrlQuery();
const dispatch = useDispatch();
useEffect(() => {
const hasMounted = getMountedState();
if (!hasMounted) {
dispatch({
type: SET_SEARCH_QUERY_STRING,
payload: urlQuery.get('q'),
});
}
}, [dispatch, getMountedState, urlQuery]);
useEffect(() => {
getLogsFields();
}, [getLogsFields]);
return ( return (
<> <>
<SpaceContainer <SpaceContainer
@ -63,16 +35,4 @@ function Logs({ getLogsFields }: LogsProps): JSX.Element {
); );
} }
type LogsProps = DispatchProps; export default Logs;
interface DispatchProps {
getLogsFields: () => (dispatch: Dispatch<AppActions>) => void;
}
const mapDispatchToProps = (
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
): DispatchProps => ({
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
});
export default connect(null, mapDispatchToProps)(memo(Logs));

View File

@ -6,6 +6,10 @@ import {
UPDATE_AUTO_REFRESH_INTERVAL, UPDATE_AUTO_REFRESH_INTERVAL,
UPDATE_TIME_INTERVAL, UPDATE_TIME_INTERVAL,
} from 'types/actions/globalTime'; } from 'types/actions/globalTime';
import {
RESET_ID_START_AND_END,
SET_SEARCH_QUERY_STRING,
} from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
const intitalState: GlobalReducer = { const intitalState: GlobalReducer = {
@ -53,6 +57,29 @@ const globalTimeReducer = (
}; };
} }
case RESET_ID_START_AND_END: {
return {
...state,
maxTime: action.payload.maxTime,
minTime: action.payload.minTime,
};
}
case SET_SEARCH_QUERY_STRING: {
const { globalTime } = action.payload;
if (globalTime) {
return {
...state,
maxTime: globalTime.maxTime,
minTime: globalTime.minTime,
};
}
return {
...state,
};
}
default: default:
return state; return state;
} }

View File

@ -20,6 +20,8 @@ import {
SET_SEARCH_QUERY_STRING, SET_SEARCH_QUERY_STRING,
STOP_LIVE_TAIL, STOP_LIVE_TAIL,
TOGGLE_LIVE_TAIL, TOGGLE_LIVE_TAIL,
UPDATE_INTERESTING_FIELDS,
UPDATE_SELECTED_FIELDS,
} from 'types/actions/logs'; } from 'types/actions/logs';
import { ILogsReducer } from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
@ -83,7 +85,7 @@ export const LogsReducer = (
...state, ...state,
searchFilter: { searchFilter: {
...state.searchFilter, ...state.searchFilter,
queryString: action.payload, queryString: action.payload.searchQueryString,
}, },
}; };
} }
@ -126,7 +128,7 @@ export const LogsReducer = (
case SET_LOG_LINES_PER_PAGE: { case SET_LOG_LINES_PER_PAGE: {
return { return {
...state, ...state,
logLinesPerPage: action.payload, logLinesPerPage: action.payload.logsLinesPerPage,
}; };
} }
@ -203,6 +205,26 @@ export const LogsReducer = (
}; };
} }
case UPDATE_INTERESTING_FIELDS: {
return {
...state,
fields: {
...state.fields,
interesting: action.payload.field,
},
};
}
case UPDATE_SELECTED_FIELDS: {
return {
...state,
fields: {
...state.fields,
selected: action.payload.field,
},
};
}
default: default:
return state; return state;
} }

View File

@ -1,5 +1,7 @@
import { Time } from 'container/TopNav/DateTimeSelection/config'; import { Time } from 'container/TopNav/DateTimeSelection/config';
import { ResetIdStartAndEnd, SetSearchQueryString } from './logs';
export const UPDATE_TIME_INTERVAL = 'UPDATE_TIME_INTERVAL'; export const UPDATE_TIME_INTERVAL = 'UPDATE_TIME_INTERVAL';
export const GLOBAL_TIME_LOADING_START = 'GLOBAL_TIME_LOADING_START'; export const GLOBAL_TIME_LOADING_START = 'GLOBAL_TIME_LOADING_START';
export const UPDATE_AUTO_REFRESH_DISABLED = 'UPDATE_AUTO_REFRESH_DISABLED'; export const UPDATE_AUTO_REFRESH_DISABLED = 'UPDATE_AUTO_REFRESH_DISABLED';
@ -37,4 +39,6 @@ export type GlobalTimeAction =
| UpdateTimeInterval | UpdateTimeInterval
| GlobalTimeLoading | GlobalTimeLoading
| UpdateAutoRefreshDisabled | UpdateAutoRefreshDisabled
| UpdateAutoRefreshInterval; | UpdateAutoRefreshInterval
| ResetIdStartAndEnd
| SetSearchQueryString;

View File

@ -1,26 +1,11 @@
// import { DBOverView } from 'types/api/metrics/getDBOverview';
// import { ExternalAverageDuration } from 'types/api/metrics/getExternalAverageDuration';
// import { ExternalError } from 'types/api/metrics/getExternalError';
// import { ExternalService } from 'types/api/metrics/getExternalService';
// import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types';
// import { ServicesList } from 'types/api/metrics/getService';
// import { ServiceOverview } from 'types/api/metrics/getServiceOverview';
// import { TopEndPoints } from 'types/api/metrics/getTopEndPoints';
import { ILogQLParsedQueryItem } from 'lib/logql/types'; import { ILogQLParsedQueryItem } from 'lib/logql/types';
import { IFieldMoveToSelected, IFields } from 'types/api/logs/fields'; import { IField, IFieldMoveToSelected, IFields } from 'types/api/logs/fields';
import { TLogsLiveTailState } from 'types/api/logs/liveTail'; import { TLogsLiveTailState } from 'types/api/logs/liveTail';
import { ILog } from 'types/api/logs/log'; import { ILog } from 'types/api/logs/log';
import { ILogsAggregate } from 'types/api/logs/logAggregate'; import { ILogsAggregate } from 'types/api/logs/logAggregate';
// export const GET_SERVICE_LIST_SUCCESS = 'GET_SERVICE_LIST_SUCCESS'; import { GlobalTime } from './globalTime';
// export const GET_SERVICE_LIST_LOADING_START = 'GET_SERVICE_LIST_LOADING_START';
// export const GET_SERVICE_LIST_ERROR = 'GET_SERVICE_LIST_ERROR';
// export const GET_INITIAL_APPLICATION_LOADING =
// 'GET_INITIAL_APPLICATION_LOADING';
// export const GET_INITIAL_APPLICATION_ERROR = 'GET_INITIAL_APPLICATION_ERROR';
// export const GET_INTIAL_APPLICATION_DATA = 'GET_INTIAL_APPLICATION_DATA';
// export const RESET_INITIAL_APPLICATION_DATA = 'RESET_INITIAL_APPLICATION_DATA';
export const GET_FIELDS = 'LOGS_GET_FIELDS'; export const GET_FIELDS = 'LOGS_GET_FIELDS';
export const SET_FIELDS = 'LOGS_SET_FIELDS'; export const SET_FIELDS = 'LOGS_SET_FIELDS';
export const SET_SEARCH_QUERY_STRING = 'LOGS_SET_SEARCH_QUERY_STRING'; export const SET_SEARCH_QUERY_STRING = 'LOGS_SET_SEARCH_QUERY_STRING';
@ -43,6 +28,9 @@ export const PUSH_LIVE_TAIL_EVENT = 'LOGS_PUSH_LIVE_TAIL_EVENT';
export const STOP_LIVE_TAIL = 'LOGS_STOP_LIVE_TAIL'; export const STOP_LIVE_TAIL = 'LOGS_STOP_LIVE_TAIL';
export const FLUSH_LOGS = 'LOGS_FLUSH_LOGS'; export const FLUSH_LOGS = 'LOGS_FLUSH_LOGS';
export const SET_LIVE_TAIL_START_TIME = 'LOGS_SET_LIVE_TAIL_START_TIME'; export const SET_LIVE_TAIL_START_TIME = 'LOGS_SET_LIVE_TAIL_START_TIME';
export const UPDATE_SELECTED_FIELDS = 'LOGS_UPDATE_SELECTED_FIELDS';
export const UPDATE_INTERESTING_FIELDS = 'LOGS_UPDATE_INTERESTING_FIELDS';
export interface GetFields { export interface GetFields {
type: typeof GET_FIELDS; type: typeof GET_FIELDS;
} }
@ -53,7 +41,10 @@ export interface SetFields {
} }
export interface SetSearchQueryString { export interface SetSearchQueryString {
type: typeof SET_SEARCH_QUERY_STRING; type: typeof SET_SEARCH_QUERY_STRING;
payload: string; payload: {
searchQueryString: string;
globalTime?: GlobalTime;
};
} }
export interface SetSearchQueryParsedPayload { export interface SetSearchQueryParsedPayload {
@ -75,7 +66,9 @@ export interface UpdateLogs {
} }
export interface SetLogsLinesPerPage { export interface SetLogsLinesPerPage {
type: typeof SET_LOG_LINES_PER_PAGE; type: typeof SET_LOG_LINES_PER_PAGE;
payload: number; payload: {
logsLinesPerPage: number;
};
} }
export interface PreviousLogsLines { export interface PreviousLogsLines {
@ -86,6 +79,7 @@ export interface NextLogsLines {
} }
export interface ResetIdStartAndEnd { export interface ResetIdStartAndEnd {
type: typeof RESET_ID_START_AND_END; type: typeof RESET_ID_START_AND_END;
payload: GlobalTime;
} }
export interface SetLoading { export interface SetLoading {
type: typeof SET_LOADING; type: typeof SET_LOADING;
@ -124,6 +118,16 @@ export interface SetLiveTailStartTime {
payload: number; payload: number;
} }
type IFieldType = 'interesting' | 'selected';
export interface UpdateSelectedInterestFields {
type: typeof UPDATE_INTERESTING_FIELDS | typeof UPDATE_SELECTED_FIELDS;
payload: {
field: IField[];
type: IFieldType;
};
}
export type LogsActions = export type LogsActions =
| GetFields | GetFields
| SetFields | SetFields
@ -144,4 +148,5 @@ export type LogsActions =
| PushLiveTailEvent | PushLiveTailEvent
| StopLiveTail | StopLiveTail
| FlushLogs | FlushLogs
| SetLiveTailStartTime; | SetLiveTailStartTime
| UpdateSelectedInterestFields;