mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-18 19:15:52 +08:00
feat: add the url pagination & update options menu (#2943)
* feat: add dynamic table based on query * fix: group by repeating * fix: change view when groupBy exist in the list * fix: table scroll * feat: add the pagination and update options menu * feat: trace explorer is updated --------- Co-authored-by: Yevhen Shevchenko <y.shevchenko@seedium.io> Co-authored-by: Nazarenko19 <danil.nazarenko2000@gmail.com> Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
parent
bd18eee662
commit
56402b0d40
@ -233,6 +233,7 @@ export const PANEL_TYPES: Record<PanelTypeKeys, GRAPH_TYPES> = {
|
|||||||
TABLE: 'table',
|
TABLE: 'table',
|
||||||
LIST: 'list',
|
LIST: 'list',
|
||||||
EMPTY_WIDGET: 'EMPTY_WIDGET',
|
EMPTY_WIDGET: 'EMPTY_WIDGET',
|
||||||
|
TRACE: 'trace',
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IQueryBuilderState = 'search';
|
export type IQueryBuilderState = 'search';
|
||||||
|
@ -1,33 +1,29 @@
|
|||||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||||
import { Button, Select } from 'antd';
|
import { Button, Select } from 'antd';
|
||||||
|
import { Pagination } from 'hooks/queryPagination';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
|
|
||||||
import { defaultSelectStyle, ITEMS_PER_PAGE_OPTIONS } from './config';
|
import { defaultSelectStyle, ITEMS_PER_PAGE_OPTIONS } from './config';
|
||||||
import { Container } from './styles';
|
import { Container } from './styles';
|
||||||
|
|
||||||
interface ControlsProps {
|
function Controls({
|
||||||
count: number;
|
offset = 0,
|
||||||
countPerPage: number;
|
isLoading,
|
||||||
isLoading: boolean;
|
totalCount,
|
||||||
handleNavigatePrevious: () => void;
|
countPerPage,
|
||||||
handleNavigateNext: () => void;
|
handleNavigatePrevious,
|
||||||
handleCountItemsPerPageChange: (e: number) => void;
|
handleNavigateNext,
|
||||||
}
|
handleCountItemsPerPageChange,
|
||||||
|
}: ControlsProps): JSX.Element | null {
|
||||||
function Controls(props: ControlsProps): JSX.Element | null {
|
|
||||||
const {
|
|
||||||
count,
|
|
||||||
isLoading,
|
|
||||||
countPerPage,
|
|
||||||
handleNavigatePrevious,
|
|
||||||
handleNavigateNext,
|
|
||||||
handleCountItemsPerPageChange,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const isNextAndPreviousDisabled = useMemo(
|
const isNextAndPreviousDisabled = useMemo(
|
||||||
() => isLoading || countPerPage === 0 || count === 0 || count < countPerPage,
|
() => isLoading || countPerPage < 0 || totalCount === 0,
|
||||||
[isLoading, countPerPage, count],
|
[isLoading, countPerPage, totalCount],
|
||||||
);
|
);
|
||||||
|
const isPreviousDisabled = useMemo(() => offset <= 0, [offset]);
|
||||||
|
const isNextDisabled = useMemo(() => totalCount < countPerPage, [
|
||||||
|
countPerPage,
|
||||||
|
totalCount,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
@ -35,7 +31,7 @@ function Controls(props: ControlsProps): JSX.Element | null {
|
|||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
size="small"
|
size="small"
|
||||||
type="link"
|
type="link"
|
||||||
disabled={isNextAndPreviousDisabled}
|
disabled={isPreviousDisabled || isNextAndPreviousDisabled}
|
||||||
onClick={handleNavigatePrevious}
|
onClick={handleNavigatePrevious}
|
||||||
>
|
>
|
||||||
<LeftOutlined /> Previous
|
<LeftOutlined /> Previous
|
||||||
@ -44,12 +40,12 @@ function Controls(props: ControlsProps): JSX.Element | null {
|
|||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
size="small"
|
size="small"
|
||||||
type="link"
|
type="link"
|
||||||
disabled={isNextAndPreviousDisabled}
|
disabled={isNextDisabled || isNextAndPreviousDisabled}
|
||||||
onClick={handleNavigateNext}
|
onClick={handleNavigateNext}
|
||||||
>
|
>
|
||||||
Next <RightOutlined />
|
Next <RightOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
<Select
|
<Select<Pagination['limit']>
|
||||||
style={defaultSelectStyle}
|
style={defaultSelectStyle}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
value={countPerPage}
|
value={countPerPage}
|
||||||
@ -66,4 +62,18 @@ function Controls(props: ControlsProps): JSX.Element | null {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Controls.defaultProps = {
|
||||||
|
offset: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ControlsProps {
|
||||||
|
offset?: Pagination['offset'];
|
||||||
|
totalCount: number;
|
||||||
|
countPerPage: Pagination['limit'];
|
||||||
|
isLoading: boolean;
|
||||||
|
handleNavigatePrevious: () => void;
|
||||||
|
handleNavigateNext: () => void;
|
||||||
|
handleCountItemsPerPageChange: (value: Pagination['limit']) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export default memo(Controls);
|
export default memo(Controls);
|
||||||
|
@ -5,6 +5,7 @@ import Controls from 'container/Controls';
|
|||||||
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
||||||
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { Pagination } from 'hooks/queryPagination';
|
||||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||||
import * as Papa from 'papaparse';
|
import * as Papa from 'papaparse';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
@ -37,7 +38,7 @@ function LogControls(): JSX.Element | null {
|
|||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
|
||||||
const handleLogLinesPerPageChange = (e: number): void => {
|
const handleLogLinesPerPageChange = (e: Pagination['limit']): void => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SET_LOG_LINES_PER_PAGE,
|
type: SET_LOG_LINES_PER_PAGE,
|
||||||
payload: {
|
payload: {
|
||||||
@ -166,7 +167,7 @@ function LogControls(): JSX.Element | null {
|
|||||||
<Divider type="vertical" />
|
<Divider type="vertical" />
|
||||||
<Controls
|
<Controls
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
count={logs.length}
|
totalCount={logs.length}
|
||||||
countPerPage={logLinesPerPage}
|
countPerPage={logLinesPerPage}
|
||||||
handleNavigatePrevious={handleNavigatePrevious}
|
handleNavigatePrevious={handleNavigatePrevious}
|
||||||
handleNavigateNext={handleNavigateNext}
|
handleNavigateNext={handleNavigateNext}
|
||||||
|
@ -16,7 +16,13 @@ const Items: ItemsProps[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export type ITEMS = 'graph' | 'value' | 'list' | 'table' | 'EMPTY_WIDGET';
|
export type ITEMS =
|
||||||
|
| 'graph'
|
||||||
|
| 'value'
|
||||||
|
| 'list'
|
||||||
|
| 'table'
|
||||||
|
| 'EMPTY_WIDGET'
|
||||||
|
| 'trace';
|
||||||
|
|
||||||
interface ItemsProps {
|
interface ItemsProps {
|
||||||
name: ITEMS;
|
name: ITEMS;
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import { Input } from 'antd';
|
import { Input } from 'antd';
|
||||||
|
import Typography from 'antd/es/typography/Typography';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { OptionsMenuConfig } from '..';
|
|
||||||
import { FieldTitle } from '../styles';
|
import { FieldTitle } from '../styles';
|
||||||
import { AddColumnSelect, AddColumnWrapper, SearchIconWrapper } from './styles';
|
import { OptionsMenuConfig } from '../types';
|
||||||
|
import {
|
||||||
|
AddColumnItem,
|
||||||
|
AddColumnSelect,
|
||||||
|
AddColumnWrapper,
|
||||||
|
DeleteOutlinedIcon,
|
||||||
|
SearchIconWrapper,
|
||||||
|
} from './styles';
|
||||||
|
|
||||||
function AddColumnField({ config }: AddColumnFieldProps): JSX.Element | null {
|
function AddColumnField({ config }: AddColumnFieldProps): JSX.Element | null {
|
||||||
const { t } = useTranslation(['trace']);
|
const { t } = useTranslation(['trace']);
|
||||||
@ -19,19 +26,32 @@ function AddColumnField({ config }: AddColumnFieldProps): JSX.Element | null {
|
|||||||
|
|
||||||
<Input.Group compact>
|
<Input.Group compact>
|
||||||
<AddColumnSelect
|
<AddColumnSelect
|
||||||
allowClear
|
|
||||||
maxTagCount={0}
|
|
||||||
size="small"
|
size="small"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
options={config.options}
|
options={config.options}
|
||||||
value={config.value}
|
value={[]}
|
||||||
onChange={config.onChange}
|
onChange={config.onChange}
|
||||||
/>
|
/>
|
||||||
<SearchIconWrapper $isDarkMode={isDarkMode}>
|
<SearchIconWrapper $isDarkMode={isDarkMode}>
|
||||||
<SearchOutlined />
|
<SearchOutlined />
|
||||||
</SearchIconWrapper>
|
</SearchIconWrapper>
|
||||||
</Input.Group>
|
</Input.Group>
|
||||||
|
|
||||||
|
{config.value.map((selectedValue: string) => {
|
||||||
|
const option = config?.options?.find(
|
||||||
|
({ value }) => value === selectedValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AddColumnItem direction="horizontal" key={option?.value}>
|
||||||
|
<Typography>{option?.label}</Typography>
|
||||||
|
<DeleteOutlinedIcon
|
||||||
|
onClick={(): void => config.onRemove(selectedValue)}
|
||||||
|
/>
|
||||||
|
</AddColumnItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</AddColumnWrapper>
|
</AddColumnWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
import { Card, Select, SelectProps, Space } from 'antd';
|
import { Card, Select, SelectProps, Space } from 'antd';
|
||||||
import { themeColors } from 'constants/theme';
|
import { themeColors } from 'constants/theme';
|
||||||
import { FunctionComponent } from 'react';
|
import { FunctionComponent } from 'react';
|
||||||
@ -26,3 +27,13 @@ export const AddColumnSelect: FunctionComponent<SelectProps> = styled(
|
|||||||
export const AddColumnWrapper = styled(Space)`
|
export const AddColumnWrapper = styled(Space)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const AddColumnItem = styled(Space)`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DeleteOutlinedIcon = styled(DeleteOutlined)`
|
||||||
|
color: red;
|
||||||
|
`;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { OptionsMenuConfig } from '..';
|
|
||||||
import { FieldTitle } from '../styles';
|
import { FieldTitle } from '../styles';
|
||||||
|
import { OptionsMenuConfig } from '../types';
|
||||||
import { FormatFieldWrapper, RadioButton, RadioGroup } from './styles';
|
import { FormatFieldWrapper, RadioButton, RadioGroup } from './styles';
|
||||||
|
|
||||||
function FormatField({ config }: FormatFieldProps): JSX.Element | null {
|
function FormatField({ config }: FormatFieldProps): JSX.Element | null {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { OptionsMenuConfig } from '..';
|
|
||||||
import { FieldTitle } from '../styles';
|
import { FieldTitle } from '../styles';
|
||||||
|
import { OptionsMenuConfig } from '../types';
|
||||||
import { MaxLinesFieldWrapper, MaxLinesInput } from './styles';
|
import { MaxLinesFieldWrapper, MaxLinesInput } from './styles';
|
||||||
|
|
||||||
function MaxLinesField({ config }: MaxLinesFieldProps): JSX.Element | null {
|
function MaxLinesField({ config }: MaxLinesFieldProps): JSX.Element | null {
|
||||||
|
9
frontend/src/container/OptionsMenu/constants.ts
Normal file
9
frontend/src/container/OptionsMenu/constants.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { OptionsQuery } from './types';
|
||||||
|
|
||||||
|
export const URL_OPTIONS = 'options';
|
||||||
|
|
||||||
|
export const defaultOptionsQuery: OptionsQuery = {
|
||||||
|
selectColumns: [],
|
||||||
|
maxLines: 0,
|
||||||
|
format: 'default',
|
||||||
|
};
|
@ -1,11 +1,5 @@
|
|||||||
import { SettingFilled, SettingOutlined } from '@ant-design/icons';
|
import { SettingFilled, SettingOutlined } from '@ant-design/icons';
|
||||||
import {
|
import { Popover, Space } from 'antd';
|
||||||
InputNumberProps,
|
|
||||||
Popover,
|
|
||||||
RadioProps,
|
|
||||||
SelectProps,
|
|
||||||
Space,
|
|
||||||
} from 'antd';
|
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -14,6 +8,12 @@ import AddColumnField from './AddColumnField';
|
|||||||
import FormatField from './FormatField';
|
import FormatField from './FormatField';
|
||||||
import MaxLinesField from './MaxLinesField';
|
import MaxLinesField from './MaxLinesField';
|
||||||
import { OptionsContainer, OptionsContentWrapper } from './styles';
|
import { OptionsContainer, OptionsContentWrapper } from './styles';
|
||||||
|
import { OptionsMenuConfig } from './types';
|
||||||
|
import useOptionsMenu from './useOptionsMenu';
|
||||||
|
|
||||||
|
interface OptionsMenuProps {
|
||||||
|
config: OptionsMenuConfig;
|
||||||
|
}
|
||||||
|
|
||||||
function OptionsMenu({ config }: OptionsMenuProps): JSX.Element {
|
function OptionsMenu({ config }: OptionsMenuProps): JSX.Element {
|
||||||
const { t } = useTranslation(['trace']);
|
const { t } = useTranslation(['trace']);
|
||||||
@ -44,14 +44,6 @@ function OptionsMenu({ config }: OptionsMenuProps): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OptionsMenuConfig = {
|
|
||||||
format?: Pick<RadioProps, 'value' | 'onChange'>;
|
|
||||||
maxLines?: Pick<InputNumberProps, 'value' | 'onChange'>;
|
|
||||||
addColumn?: Pick<SelectProps, 'options' | 'value' | 'onChange'>;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface OptionsMenuProps {
|
|
||||||
config: OptionsMenuConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default OptionsMenu;
|
export default OptionsMenu;
|
||||||
|
|
||||||
|
export { useOptionsMenu };
|
||||||
|
21
frontend/src/container/OptionsMenu/types.ts
Normal file
21
frontend/src/container/OptionsMenu/types.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { InputNumberProps, RadioProps, SelectProps } from 'antd';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
export interface OptionsQuery {
|
||||||
|
selectColumns: BaseAutocompleteData[];
|
||||||
|
maxLines: number;
|
||||||
|
format: 'default' | 'row' | 'column';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InitialOptions
|
||||||
|
extends Omit<Partial<OptionsQuery>, 'selectColumns'> {
|
||||||
|
selectColumns?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OptionsMenuConfig = {
|
||||||
|
format?: Pick<RadioProps, 'value' | 'onChange'>;
|
||||||
|
maxLines?: Pick<InputNumberProps, 'value' | 'onChange'>;
|
||||||
|
addColumn?: Pick<SelectProps, 'options' | 'value' | 'onChange'> & {
|
||||||
|
onRemove: (key: string) => void;
|
||||||
|
};
|
||||||
|
};
|
166
frontend/src/container/OptionsMenu/useOptionsMenu.ts
Normal file
166
frontend/src/container/OptionsMenu/useOptionsMenu.ts
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import { RadioChangeEvent } from 'antd';
|
||||||
|
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
||||||
|
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
||||||
|
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||||
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { defaultOptionsQuery, URL_OPTIONS } from './constants';
|
||||||
|
import { InitialOptions, OptionsMenuConfig, OptionsQuery } from './types';
|
||||||
|
import { getInitialColumns, getOptionsFromKeys } from './utils';
|
||||||
|
|
||||||
|
interface UseOptionsMenuProps {
|
||||||
|
dataSource: DataSource;
|
||||||
|
aggregateOperator: string;
|
||||||
|
initialOptions?: InitialOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseOptionsMenu {
|
||||||
|
isLoading: boolean;
|
||||||
|
options: OptionsQuery;
|
||||||
|
config: OptionsMenuConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useOptionsMenu = ({
|
||||||
|
dataSource,
|
||||||
|
aggregateOperator,
|
||||||
|
initialOptions = {},
|
||||||
|
}: UseOptionsMenuProps): UseOptionsMenu => {
|
||||||
|
const {
|
||||||
|
query: optionsQuery,
|
||||||
|
queryData: optionsQueryData,
|
||||||
|
redirectWithQuery: redirectWithOptionsData,
|
||||||
|
} = useUrlQueryData<OptionsQuery>(URL_OPTIONS);
|
||||||
|
|
||||||
|
const { data, isFetched, isLoading } = useQuery(
|
||||||
|
[QueryBuilderKeys.GET_ATTRIBUTE_KEY],
|
||||||
|
async () =>
|
||||||
|
getAggregateKeys({
|
||||||
|
searchText: '',
|
||||||
|
dataSource,
|
||||||
|
aggregateOperator,
|
||||||
|
aggregateAttribute: '',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const attributeKeys = useMemo(() => data?.payload?.attributeKeys || [], [
|
||||||
|
data?.payload?.attributeKeys,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const initialOptionsQuery: OptionsQuery = useMemo(
|
||||||
|
() => ({
|
||||||
|
...defaultOptionsQuery,
|
||||||
|
...initialOptions,
|
||||||
|
selectColumns: initialOptions?.selectColumns
|
||||||
|
? getInitialColumns(initialOptions?.selectColumns || [], attributeKeys)
|
||||||
|
: defaultOptionsQuery.selectColumns,
|
||||||
|
}),
|
||||||
|
[initialOptions, attributeKeys],
|
||||||
|
);
|
||||||
|
|
||||||
|
const options = useMemo(() => getOptionsFromKeys(attributeKeys), [
|
||||||
|
attributeKeys,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const selectedColumnKeys = useMemo(
|
||||||
|
() => optionsQueryData?.selectColumns?.map(({ id }) => id) || [],
|
||||||
|
[optionsQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelectedColumnsChange = useCallback(
|
||||||
|
(value: string[]) => {
|
||||||
|
const newSelectedColumnKeys = [
|
||||||
|
...new Set([...selectedColumnKeys, ...value]),
|
||||||
|
];
|
||||||
|
const newSelectedColumns = newSelectedColumnKeys.reduce((acc, key) => {
|
||||||
|
const column = attributeKeys.find(({ id }) => id === key);
|
||||||
|
|
||||||
|
if (!column) return acc;
|
||||||
|
return [...acc, column];
|
||||||
|
}, [] as BaseAutocompleteData[]);
|
||||||
|
|
||||||
|
redirectWithOptionsData({
|
||||||
|
...defaultOptionsQuery,
|
||||||
|
selectColumns: newSelectedColumns,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[attributeKeys, selectedColumnKeys, redirectWithOptionsData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRemoveSelectedColumn = useCallback(
|
||||||
|
(columnKey: string) => {
|
||||||
|
redirectWithOptionsData({
|
||||||
|
...defaultOptionsQuery,
|
||||||
|
selectColumns: optionsQueryData?.selectColumns?.filter(
|
||||||
|
({ id }) => id !== columnKey,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[optionsQueryData, redirectWithOptionsData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFormatChange = useCallback(
|
||||||
|
(event: RadioChangeEvent) => {
|
||||||
|
redirectWithOptionsData({
|
||||||
|
...defaultOptionsQuery,
|
||||||
|
format: event.target.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[redirectWithOptionsData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMaxLinesChange = useCallback(
|
||||||
|
(value: string | number | null) => {
|
||||||
|
redirectWithOptionsData({
|
||||||
|
...defaultOptionsQuery,
|
||||||
|
maxLines: value as number,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[redirectWithOptionsData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const optionsMenuConfig: Required<OptionsMenuConfig> = useMemo(
|
||||||
|
() => ({
|
||||||
|
addColumn: {
|
||||||
|
value: selectedColumnKeys || defaultOptionsQuery.selectColumns,
|
||||||
|
options: options || [],
|
||||||
|
onChange: handleSelectedColumnsChange,
|
||||||
|
onRemove: handleRemoveSelectedColumn,
|
||||||
|
},
|
||||||
|
format: {
|
||||||
|
value: optionsQueryData?.format || defaultOptionsQuery.format,
|
||||||
|
onChange: handleFormatChange,
|
||||||
|
},
|
||||||
|
maxLines: {
|
||||||
|
value: optionsQueryData?.maxLines || defaultOptionsQuery.maxLines,
|
||||||
|
onChange: handleMaxLinesChange,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
options,
|
||||||
|
selectedColumnKeys,
|
||||||
|
optionsQueryData?.maxLines,
|
||||||
|
optionsQueryData?.format,
|
||||||
|
handleSelectedColumnsChange,
|
||||||
|
handleRemoveSelectedColumn,
|
||||||
|
handleFormatChange,
|
||||||
|
handleMaxLinesChange,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (optionsQuery || !isFetched) return;
|
||||||
|
|
||||||
|
redirectWithOptionsData(initialOptionsQuery);
|
||||||
|
}, [isFetched, optionsQuery, initialOptionsQuery, redirectWithOptionsData]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLoading,
|
||||||
|
options: optionsQueryData,
|
||||||
|
config: optionsMenuConfig,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useOptionsMenu;
|
22
frontend/src/container/OptionsMenu/utils.ts
Normal file
22
frontend/src/container/OptionsMenu/utils.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { SelectProps } from 'antd';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
export const getOptionsFromKeys = (
|
||||||
|
keys: BaseAutocompleteData[],
|
||||||
|
): SelectProps['options'] =>
|
||||||
|
keys.map(({ id, key }) => ({
|
||||||
|
label: key,
|
||||||
|
value: id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const getInitialColumns = (
|
||||||
|
initialColumnTitles: string[],
|
||||||
|
attributeKeys: BaseAutocompleteData[],
|
||||||
|
): BaseAutocompleteData[] =>
|
||||||
|
initialColumnTitles.reduce((acc, title) => {
|
||||||
|
const initialColumn = attributeKeys.find(({ key }) => title === key);
|
||||||
|
|
||||||
|
if (!initialColumn) return acc;
|
||||||
|
|
||||||
|
return [...acc, initialColumn];
|
||||||
|
}, [] as BaseAutocompleteData[]);
|
@ -12,8 +12,8 @@ function TraceExplorerControls(): JSX.Element | null {
|
|||||||
<Container>
|
<Container>
|
||||||
<Controls
|
<Controls
|
||||||
isLoading={false}
|
isLoading={false}
|
||||||
count={0}
|
totalCount={0}
|
||||||
countPerPage={0}
|
countPerPage={25}
|
||||||
handleNavigatePrevious={handleNavigatePrevious}
|
handleNavigatePrevious={handleNavigatePrevious}
|
||||||
handleNavigateNext={handleNavigateNext}
|
handleNavigateNext={handleNavigateNext}
|
||||||
handleCountItemsPerPageChange={handleCountItemsPerPageChange}
|
handleCountItemsPerPageChange={handleCountItemsPerPageChange}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { QueryBuilder } from 'container/QueryBuilder';
|
import { QueryBuilder } from 'container/QueryBuilder';
|
||||||
|
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
@ -9,10 +10,12 @@ import { ButtonWrapper, Container } from './styles';
|
|||||||
function QuerySection(): JSX.Element {
|
function QuerySection(): JSX.Element {
|
||||||
const { handleRunQuery } = useQueryBuilder();
|
const { handleRunQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.TIME_SERIES);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<QueryBuilder
|
<QueryBuilder
|
||||||
panelType={PANEL_TYPES.TIME_SERIES}
|
panelType={panelTypes}
|
||||||
config={{
|
config={{
|
||||||
queryVariant: 'static',
|
queryVariant: 'static',
|
||||||
initialDataSource: DataSource.TRACES,
|
initialDataSource: DataSource.TRACES,
|
||||||
|
8
frontend/src/hooks/queryPagination/config.ts
Normal file
8
frontend/src/hooks/queryPagination/config.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Pagination } from './types';
|
||||||
|
|
||||||
|
export const URL_PAGINATION = 'pagination';
|
||||||
|
|
||||||
|
export const defaultPaginationConfig: Pagination = {
|
||||||
|
offset: 0,
|
||||||
|
limit: 25,
|
||||||
|
};
|
2
frontend/src/hooks/queryPagination/index.ts
Normal file
2
frontend/src/hooks/queryPagination/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './config';
|
||||||
|
export * from './types';
|
4
frontend/src/hooks/queryPagination/types.ts
Normal file
4
frontend/src/hooks/queryPagination/types.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface Pagination {
|
||||||
|
offset: number;
|
||||||
|
limit: 25 | 50 | 100 | 200;
|
||||||
|
}
|
70
frontend/src/hooks/queryPagination/useQueryPagination.ts
Normal file
70
frontend/src/hooks/queryPagination/useQueryPagination.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { ControlsProps } from 'container/Controls';
|
||||||
|
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { defaultPaginationConfig, URL_PAGINATION } from './config';
|
||||||
|
import { Pagination } from './types';
|
||||||
|
import { checkIsValidPaginationData } from './utils';
|
||||||
|
|
||||||
|
const useQueryPagination = (totalCount: number): UseQueryPagination => {
|
||||||
|
const {
|
||||||
|
query: paginationQuery,
|
||||||
|
queryData: paginationQueryData,
|
||||||
|
redirectWithQuery: redirectWithCurrentPagination,
|
||||||
|
} = useUrlQueryData<Pagination>(URL_PAGINATION);
|
||||||
|
|
||||||
|
const handleCountItemsPerPageChange = useCallback(
|
||||||
|
(newLimit: Pagination['limit']) => {
|
||||||
|
redirectWithCurrentPagination({
|
||||||
|
...paginationQueryData,
|
||||||
|
limit: newLimit,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[paginationQueryData, redirectWithCurrentPagination],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleNavigatePrevious = useCallback(() => {
|
||||||
|
const previousOffset = paginationQueryData.offset - paginationQueryData.limit;
|
||||||
|
|
||||||
|
redirectWithCurrentPagination({
|
||||||
|
...paginationQueryData,
|
||||||
|
offset: previousOffset > 0 ? previousOffset : 0,
|
||||||
|
});
|
||||||
|
}, [paginationQueryData, redirectWithCurrentPagination]);
|
||||||
|
|
||||||
|
const handleNavigateNext = useCallback(() => {
|
||||||
|
redirectWithCurrentPagination({
|
||||||
|
...paginationQueryData,
|
||||||
|
offset:
|
||||||
|
paginationQueryData.limit === totalCount
|
||||||
|
? paginationQueryData.offset + paginationQueryData.limit
|
||||||
|
: paginationQueryData.offset,
|
||||||
|
});
|
||||||
|
}, [totalCount, paginationQueryData, redirectWithCurrentPagination]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isValidPaginationData = checkIsValidPaginationData(
|
||||||
|
paginationQueryData || defaultPaginationConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (paginationQuery && isValidPaginationData) return;
|
||||||
|
|
||||||
|
redirectWithCurrentPagination(defaultPaginationConfig);
|
||||||
|
}, [paginationQuery, paginationQueryData, redirectWithCurrentPagination]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pagination: paginationQueryData || defaultPaginationConfig,
|
||||||
|
handleCountItemsPerPageChange,
|
||||||
|
handleNavigatePrevious,
|
||||||
|
handleNavigateNext,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseQueryPagination = Pick<
|
||||||
|
ControlsProps,
|
||||||
|
| 'handleCountItemsPerPageChange'
|
||||||
|
| 'handleNavigateNext'
|
||||||
|
| 'handleNavigatePrevious'
|
||||||
|
> & { pagination: Pagination };
|
||||||
|
|
||||||
|
export default useQueryPagination;
|
12
frontend/src/hooks/queryPagination/utils.ts
Normal file
12
frontend/src/hooks/queryPagination/utils.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Pagination } from './types';
|
||||||
|
|
||||||
|
export const checkIsValidPaginationData = ({
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
}: Pagination): boolean =>
|
||||||
|
Boolean(
|
||||||
|
limit &&
|
||||||
|
(limit === 25 || limit === 50 || limit === 100 || limit === 200) &&
|
||||||
|
offset &&
|
||||||
|
offset > 0,
|
||||||
|
);
|
44
frontend/src/hooks/useUrlQueryData.ts
Normal file
44
frontend/src/hooks/useUrlQueryData.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
const useUrlQueryData = <T>(
|
||||||
|
queryKey: string,
|
||||||
|
defaultData?: T,
|
||||||
|
): UseUrlQueryData<T> => {
|
||||||
|
const history = useHistory();
|
||||||
|
const location = useLocation();
|
||||||
|
const urlQuery = useUrlQuery();
|
||||||
|
|
||||||
|
const query = urlQuery.get(queryKey);
|
||||||
|
|
||||||
|
const queryData: T = useMemo(() => (query ? JSON.parse(query) : defaultData), [
|
||||||
|
query,
|
||||||
|
defaultData,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const redirectWithQuery = useCallback(
|
||||||
|
(newQueryData: T): void => {
|
||||||
|
const newQuery = JSON.stringify(newQueryData);
|
||||||
|
|
||||||
|
urlQuery.set(queryKey, newQuery);
|
||||||
|
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||||
|
history.push(generatedUrl);
|
||||||
|
},
|
||||||
|
[history, location, urlQuery, queryKey],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
query,
|
||||||
|
queryData,
|
||||||
|
redirectWithQuery,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
interface UseUrlQueryData<T> {
|
||||||
|
query: string | null;
|
||||||
|
queryData: T;
|
||||||
|
redirectWithQuery: (newQueryData: T) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useUrlQueryData;
|
@ -12,7 +12,7 @@ import { DataSource } from 'types/common/queryBuilder';
|
|||||||
// ** Styles
|
// ** Styles
|
||||||
import { ButtonWrapperStyled, WrapperStyled } from './styles';
|
import { ButtonWrapperStyled, WrapperStyled } from './styles';
|
||||||
|
|
||||||
function LogsExporer(): JSX.Element {
|
function LogsExplorer(): JSX.Element {
|
||||||
const { handleRunQuery, updateAllQueriesOperators } = useQueryBuilder();
|
const { handleRunQuery, updateAllQueriesOperators } = useQueryBuilder();
|
||||||
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
|
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
|
||||||
|
|
||||||
@ -53,4 +53,4 @@ function LogsExporer(): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LogsExporer;
|
export default LogsExplorer;
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
export const CURRENT_TRACES_EXPLORER_TAB = 'currentTab';
|
|
||||||
|
|
||||||
export enum TracesExplorerTabs {
|
|
||||||
TIME_SERIES = 'times-series',
|
|
||||||
TRACES = 'traces',
|
|
||||||
}
|
|
@ -1,56 +1,71 @@
|
|||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames';
|
||||||
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
import QuerySection from 'container/TracesExplorer/QuerySection';
|
import QuerySection from 'container/TracesExplorer/QuerySection';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { CURRENT_TRACES_EXPLORER_TAB, TracesExplorerTabs } from './constants';
|
|
||||||
import { Container } from './styles';
|
import { Container } from './styles';
|
||||||
import { getTabsItems } from './utils';
|
import { getTabsItems } from './utils';
|
||||||
|
|
||||||
function TracesExplorer(): JSX.Element {
|
function TracesExplorer(): JSX.Element {
|
||||||
const urlQuery = useUrlQuery();
|
const {
|
||||||
const history = useHistory();
|
updateAllQueriesOperators,
|
||||||
const location = useLocation();
|
redirectWithQueryBuilderData,
|
||||||
|
currentQuery,
|
||||||
|
panelType,
|
||||||
|
} = useQueryBuilder();
|
||||||
|
|
||||||
const currentUrlTab = urlQuery.get(
|
|
||||||
CURRENT_TRACES_EXPLORER_TAB,
|
|
||||||
) as TracesExplorerTabs;
|
|
||||||
const currentTab = currentUrlTab || TracesExplorerTabs.TIME_SERIES;
|
|
||||||
const tabsItems = getTabsItems();
|
const tabsItems = getTabsItems();
|
||||||
|
|
||||||
const redirectWithCurrentTab = useCallback(
|
const currentTab = panelType || PANEL_TYPES.TIME_SERIES;
|
||||||
(tabKey: string): void => {
|
|
||||||
urlQuery.set(CURRENT_TRACES_EXPLORER_TAB, tabKey);
|
|
||||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
|
||||||
history.push(generatedUrl);
|
|
||||||
},
|
|
||||||
[history, location, urlQuery],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleTabChange = useCallback(
|
const handleTabChange = useCallback(
|
||||||
(tabKey: string): void => {
|
(newPanelType: string): void => {
|
||||||
redirectWithCurrentTab(tabKey);
|
if (panelType === newPanelType) return;
|
||||||
|
|
||||||
|
const query = updateAllQueriesOperators(
|
||||||
|
currentQuery,
|
||||||
|
newPanelType as GRAPH_TYPES,
|
||||||
|
DataSource.TRACES,
|
||||||
|
);
|
||||||
|
|
||||||
|
redirectWithQueryBuilderData(query, { [PANEL_TYPES_QUERY]: newPanelType });
|
||||||
},
|
},
|
||||||
[redirectWithCurrentTab],
|
[
|
||||||
|
currentQuery,
|
||||||
|
panelType,
|
||||||
|
redirectWithQueryBuilderData,
|
||||||
|
updateAllQueriesOperators,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
useShareBuilderUrl(initialQueriesMap.traces);
|
const defaultValue = useMemo(
|
||||||
|
() =>
|
||||||
|
updateAllQueriesOperators(
|
||||||
|
initialQueriesMap.traces,
|
||||||
|
PANEL_TYPES.TIME_SERIES,
|
||||||
|
DataSource.TRACES,
|
||||||
|
),
|
||||||
|
[updateAllQueriesOperators],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useShareBuilderUrl(defaultValue);
|
||||||
if (currentUrlTab) return;
|
|
||||||
|
|
||||||
redirectWithCurrentTab(TracesExplorerTabs.TIME_SERIES);
|
|
||||||
}, [currentUrlTab, redirectWithCurrentTab]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<QuerySection />
|
<QuerySection />
|
||||||
|
|
||||||
<Container>
|
<Container>
|
||||||
<Tabs activeKey={currentTab} items={tabsItems} onChange={handleTabChange} />
|
<Tabs
|
||||||
|
defaultActiveKey={currentTab}
|
||||||
|
activeKey={currentTab}
|
||||||
|
items={tabsItems}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
import { TabsProps } from 'antd';
|
import { TabsProps } from 'antd';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import TimeSeriesView from 'container/TracesExplorer/TimeSeriesView';
|
import TimeSeriesView from 'container/TracesExplorer/TimeSeriesView';
|
||||||
|
|
||||||
import { TracesExplorerTabs } from './constants';
|
|
||||||
|
|
||||||
export const getTabsItems = (): TabsProps['items'] => [
|
export const getTabsItems = (): TabsProps['items'] => [
|
||||||
{
|
{
|
||||||
label: 'Time Series',
|
label: 'Time Series',
|
||||||
key: TracesExplorerTabs.TIME_SERIES,
|
key: PANEL_TYPES.TIME_SERIES,
|
||||||
children: <TimeSeriesView />,
|
children: <TimeSeriesView />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Traces',
|
label: 'Traces',
|
||||||
key: TracesExplorerTabs.TRACES,
|
key: PANEL_TYPES.TRACE,
|
||||||
children: <div>Traces tab</div>,
|
children: <div>Traces tab</div>,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { LogViewMode } from 'container/LogsTable';
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
|
import { Pagination } from 'hooks/queryPagination';
|
||||||
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
||||||
import { IField, 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';
|
||||||
@ -70,7 +71,7 @@ export interface UpdateLogs {
|
|||||||
export interface SetLogsLinesPerPage {
|
export interface SetLogsLinesPerPage {
|
||||||
type: typeof SET_LOG_LINES_PER_PAGE;
|
type: typeof SET_LOG_LINES_PER_PAGE;
|
||||||
payload: {
|
payload: {
|
||||||
logsLinesPerPage: number;
|
logsLinesPerPage: Pagination['limit'];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +143,8 @@ export type PanelTypeKeys =
|
|||||||
| 'VALUE'
|
| 'VALUE'
|
||||||
| 'TABLE'
|
| 'TABLE'
|
||||||
| 'LIST'
|
| 'LIST'
|
||||||
| 'EMPTY_WIDGET';
|
| 'EMPTY_WIDGET'
|
||||||
|
| 'TRACE';
|
||||||
|
|
||||||
export type ReduceOperators = 'last' | 'sum' | 'avg' | 'max' | 'min';
|
export type ReduceOperators = 'last' | 'sum' | 'avg' | 'max' | 'min';
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { LogViewMode } from 'container/LogsTable';
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
|
import { Pagination } from 'hooks/queryPagination';
|
||||||
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
||||||
import { IFields } from 'types/api/logs/fields';
|
import { IFields } from 'types/api/logs/fields';
|
||||||
import { TLogsLiveTailState } from 'types/api/logs/liveTail';
|
import { TLogsLiveTailState } from 'types/api/logs/liveTail';
|
||||||
@ -12,7 +13,7 @@ export interface ILogsReducer {
|
|||||||
parsedQuery: ILogQLParsedQueryItem[];
|
parsedQuery: ILogQLParsedQueryItem[];
|
||||||
};
|
};
|
||||||
logs: ILog[];
|
logs: ILog[];
|
||||||
logLinesPerPage: number;
|
logLinesPerPage: Pagination['limit'];
|
||||||
linesPerRow: number;
|
linesPerRow: number;
|
||||||
viewMode: LogViewMode;
|
viewMode: LogViewMode;
|
||||||
idEnd: string;
|
idEnd: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user