mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-16 07:25:51 +08:00
Merge branch 'main' into SIG-7395-1
This commit is contained in:
commit
850b145c8f
@ -40,7 +40,7 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
schema-migrator-sync:
|
schema-migrator-sync:
|
||||||
image: signoz/signoz-schema-migrator:v0.111.41
|
image: signoz/signoz-schema-migrator:v0.111.42
|
||||||
container_name: schema-migrator-sync
|
container_name: schema-migrator-sync
|
||||||
command:
|
command:
|
||||||
- sync
|
- sync
|
||||||
@ -53,7 +53,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
schema-migrator-async:
|
schema-migrator-async:
|
||||||
image: signoz/signoz-schema-migrator:v0.111.41
|
image: signoz/signoz-schema-migrator:v0.111.42
|
||||||
container_name: schema-migrator-async
|
container_name: schema-migrator-async
|
||||||
command:
|
command:
|
||||||
- async
|
- async
|
||||||
|
@ -174,7 +174,7 @@ services:
|
|||||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||||
signoz:
|
signoz:
|
||||||
!!merge <<: *db-depend
|
!!merge <<: *db-depend
|
||||||
image: signoz/signoz:v0.85.0
|
image: signoz/signoz:v0.85.2
|
||||||
command:
|
command:
|
||||||
- --config=/root/config/prometheus.yml
|
- --config=/root/config/prometheus.yml
|
||||||
ports:
|
ports:
|
||||||
@ -206,7 +206,7 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
otel-collector:
|
otel-collector:
|
||||||
!!merge <<: *db-depend
|
!!merge <<: *db-depend
|
||||||
image: signoz/signoz-otel-collector:v0.111.41
|
image: signoz/signoz-otel-collector:v0.111.42
|
||||||
command:
|
command:
|
||||||
- --config=/etc/otel-collector-config.yaml
|
- --config=/etc/otel-collector-config.yaml
|
||||||
- --manager-config=/etc/manager-config.yaml
|
- --manager-config=/etc/manager-config.yaml
|
||||||
@ -230,7 +230,7 @@ services:
|
|||||||
- signoz
|
- signoz
|
||||||
schema-migrator:
|
schema-migrator:
|
||||||
!!merge <<: *common
|
!!merge <<: *common
|
||||||
image: signoz/signoz-schema-migrator:v0.111.41
|
image: signoz/signoz-schema-migrator:v0.111.42
|
||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
|
@ -110,7 +110,7 @@ services:
|
|||||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||||
signoz:
|
signoz:
|
||||||
!!merge <<: *db-depend
|
!!merge <<: *db-depend
|
||||||
image: signoz/signoz:v0.85.0
|
image: signoz/signoz:v0.85.2
|
||||||
command:
|
command:
|
||||||
- --config=/root/config/prometheus.yml
|
- --config=/root/config/prometheus.yml
|
||||||
ports:
|
ports:
|
||||||
@ -141,7 +141,7 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
otel-collector:
|
otel-collector:
|
||||||
!!merge <<: *db-depend
|
!!merge <<: *db-depend
|
||||||
image: signoz/signoz-otel-collector:v0.111.41
|
image: signoz/signoz-otel-collector:v0.111.42
|
||||||
command:
|
command:
|
||||||
- --config=/etc/otel-collector-config.yaml
|
- --config=/etc/otel-collector-config.yaml
|
||||||
- --manager-config=/etc/manager-config.yaml
|
- --manager-config=/etc/manager-config.yaml
|
||||||
@ -165,7 +165,7 @@ services:
|
|||||||
- signoz
|
- signoz
|
||||||
schema-migrator:
|
schema-migrator:
|
||||||
!!merge <<: *common
|
!!merge <<: *common
|
||||||
image: signoz/signoz-schema-migrator:v0.111.41
|
image: signoz/signoz-schema-migrator:v0.111.42
|
||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
|
@ -177,7 +177,7 @@ services:
|
|||||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||||
signoz:
|
signoz:
|
||||||
!!merge <<: *db-depend
|
!!merge <<: *db-depend
|
||||||
image: signoz/signoz:${VERSION:-v0.85.0}
|
image: signoz/signoz:${VERSION:-v0.85.2}
|
||||||
container_name: signoz
|
container_name: signoz
|
||||||
command:
|
command:
|
||||||
- --config=/root/config/prometheus.yml
|
- --config=/root/config/prometheus.yml
|
||||||
@ -210,7 +210,7 @@ services:
|
|||||||
# TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing?
|
# TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing?
|
||||||
otel-collector:
|
otel-collector:
|
||||||
!!merge <<: *db-depend
|
!!merge <<: *db-depend
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.41}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.42}
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
command:
|
command:
|
||||||
- --config=/etc/otel-collector-config.yaml
|
- --config=/etc/otel-collector-config.yaml
|
||||||
@ -236,7 +236,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
schema-migrator-sync:
|
schema-migrator-sync:
|
||||||
!!merge <<: *common
|
!!merge <<: *common
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.41}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.42}
|
||||||
container_name: schema-migrator-sync
|
container_name: schema-migrator-sync
|
||||||
command:
|
command:
|
||||||
- sync
|
- sync
|
||||||
@ -247,7 +247,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
schema-migrator-async:
|
schema-migrator-async:
|
||||||
!!merge <<: *db-depend
|
!!merge <<: *db-depend
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.41}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.42}
|
||||||
container_name: schema-migrator-async
|
container_name: schema-migrator-async
|
||||||
command:
|
command:
|
||||||
- async
|
- async
|
||||||
|
@ -110,7 +110,7 @@ services:
|
|||||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||||
signoz:
|
signoz:
|
||||||
!!merge <<: *db-depend
|
!!merge <<: *db-depend
|
||||||
image: signoz/signoz:${VERSION:-v0.85.0}
|
image: signoz/signoz:${VERSION:-v0.85.2}
|
||||||
container_name: signoz
|
container_name: signoz
|
||||||
command:
|
command:
|
||||||
- --config=/root/config/prometheus.yml
|
- --config=/root/config/prometheus.yml
|
||||||
@ -142,7 +142,7 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
otel-collector:
|
otel-collector:
|
||||||
!!merge <<: *db-depend
|
!!merge <<: *db-depend
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.41}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.42}
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
command:
|
command:
|
||||||
- --config=/etc/otel-collector-config.yaml
|
- --config=/etc/otel-collector-config.yaml
|
||||||
@ -164,7 +164,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
schema-migrator-sync:
|
schema-migrator-sync:
|
||||||
!!merge <<: *common
|
!!merge <<: *common
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.41}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.42}
|
||||||
container_name: schema-migrator-sync
|
container_name: schema-migrator-sync
|
||||||
command:
|
command:
|
||||||
- sync
|
- sync
|
||||||
@ -176,7 +176,7 @@ services:
|
|||||||
restart: on-failure
|
restart: on-failure
|
||||||
schema-migrator-async:
|
schema-migrator-async:
|
||||||
!!merge <<: *db-depend
|
!!merge <<: *db-depend
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.41}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.42}
|
||||||
container_name: schema-migrator-async
|
container_name: schema-migrator-async
|
||||||
command:
|
command:
|
||||||
- async
|
- async
|
||||||
|
@ -7,9 +7,11 @@ import logEvent from 'api/common/logEvent';
|
|||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||||
import {
|
import {
|
||||||
|
CustomTimeType,
|
||||||
FixedDurationSuggestionOptions,
|
FixedDurationSuggestionOptions,
|
||||||
Options,
|
Options,
|
||||||
RelativeDurationSuggestionOptions,
|
RelativeDurationSuggestionOptions,
|
||||||
|
Time,
|
||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { isValidTimeFormat } from 'lib/getMinMax';
|
import { isValidTimeFormat } from 'lib/getMinMax';
|
||||||
@ -56,6 +58,10 @@ interface CustomTimePickerProps {
|
|||||||
setCustomDTPickerVisible?: Dispatch<SetStateAction<boolean>>;
|
setCustomDTPickerVisible?: Dispatch<SetStateAction<boolean>>;
|
||||||
onCustomDateHandler?: (dateTimeRange: DateTimeRangeType) => void;
|
onCustomDateHandler?: (dateTimeRange: DateTimeRangeType) => void;
|
||||||
handleGoLive?: () => void;
|
handleGoLive?: () => void;
|
||||||
|
onTimeChange?: (
|
||||||
|
interval: Time | CustomTimeType,
|
||||||
|
dateTimeRange?: [number, number],
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CustomTimePicker({
|
function CustomTimePicker({
|
||||||
@ -73,6 +79,7 @@ function CustomTimePicker({
|
|||||||
setCustomDTPickerVisible,
|
setCustomDTPickerVisible,
|
||||||
onCustomDateHandler,
|
onCustomDateHandler,
|
||||||
handleGoLive,
|
handleGoLive,
|
||||||
|
onTimeChange,
|
||||||
}: CustomTimePickerProps): JSX.Element {
|
}: CustomTimePickerProps): JSX.Element {
|
||||||
const [
|
const [
|
||||||
selectedTimePlaceholderValue,
|
selectedTimePlaceholderValue,
|
||||||
@ -336,6 +343,7 @@ function CustomTimePicker({
|
|||||||
setActiveView={setActiveView}
|
setActiveView={setActiveView}
|
||||||
setIsOpenedFromFooter={setIsOpenedFromFooter}
|
setIsOpenedFromFooter={setIsOpenedFromFooter}
|
||||||
isOpenedFromFooter={isOpenedFromFooter}
|
isOpenedFromFooter={isOpenedFromFooter}
|
||||||
|
onTimeChange={onTimeChange}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
content
|
content
|
||||||
@ -405,4 +413,5 @@ CustomTimePicker.defaultProps = {
|
|||||||
onCustomDateHandler: noop,
|
onCustomDateHandler: noop,
|
||||||
handleGoLive: noop,
|
handleGoLive: noop,
|
||||||
onCustomTimeStatusUpdate: noop,
|
onCustomTimeStatusUpdate: noop,
|
||||||
|
onTimeChange: undefined,
|
||||||
};
|
};
|
||||||
|
@ -7,9 +7,11 @@ import cx from 'classnames';
|
|||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||||
import {
|
import {
|
||||||
|
CustomTimeType,
|
||||||
LexicalContext,
|
LexicalContext,
|
||||||
Option,
|
Option,
|
||||||
RelativeDurationSuggestionOptions,
|
RelativeDurationSuggestionOptions,
|
||||||
|
Time,
|
||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||||
import { Clock, PenLine } from 'lucide-react';
|
import { Clock, PenLine } from 'lucide-react';
|
||||||
import { useTimezone } from 'providers/Timezone';
|
import { useTimezone } from 'providers/Timezone';
|
||||||
@ -35,6 +37,10 @@ interface CustomTimePickerPopoverContentProps {
|
|||||||
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
|
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
|
||||||
isOpenedFromFooter: boolean;
|
isOpenedFromFooter: boolean;
|
||||||
setIsOpenedFromFooter: Dispatch<SetStateAction<boolean>>;
|
setIsOpenedFromFooter: Dispatch<SetStateAction<boolean>>;
|
||||||
|
onTimeChange?: (
|
||||||
|
interval: Time | CustomTimeType,
|
||||||
|
dateTimeRange?: [number, number],
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
@ -51,6 +57,7 @@ function CustomTimePickerPopoverContent({
|
|||||||
setActiveView,
|
setActiveView,
|
||||||
isOpenedFromFooter,
|
isOpenedFromFooter,
|
||||||
setIsOpenedFromFooter,
|
setIsOpenedFromFooter,
|
||||||
|
onTimeChange,
|
||||||
}: CustomTimePickerPopoverContentProps): JSX.Element {
|
}: CustomTimePickerPopoverContentProps): JSX.Element {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
@ -143,6 +150,7 @@ function CustomTimePickerPopoverContent({
|
|||||||
setIsOpen={setIsOpen}
|
setIsOpen={setIsOpen}
|
||||||
onCustomDateHandler={onCustomDateHandler}
|
onCustomDateHandler={onCustomDateHandler}
|
||||||
selectedTime={selectedTime}
|
selectedTime={selectedTime}
|
||||||
|
onTimeChange={onTimeChange}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="relative-times-container">
|
<div className="relative-times-container">
|
||||||
@ -181,4 +189,8 @@ function CustomTimePickerPopoverContent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CustomTimePickerPopoverContent.defaultProps = {
|
||||||
|
onTimeChange: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
export default CustomTimePickerPopoverContent;
|
export default CustomTimePickerPopoverContent;
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
/* eslint-disable react/jsx-props-no-spreading */
|
||||||
import './RangePickerModal.styles.scss';
|
import './RangePickerModal.styles.scss';
|
||||||
|
|
||||||
import { DatePicker } from 'antd';
|
import { DatePicker } from 'antd';
|
||||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||||
import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config';
|
import {
|
||||||
|
CustomTimeType,
|
||||||
|
LexicalContext,
|
||||||
|
Time,
|
||||||
|
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { useTimezone } from 'providers/Timezone';
|
import { useTimezone } from 'providers/Timezone';
|
||||||
import { Dispatch, SetStateAction, useMemo } from 'react';
|
import { Dispatch, SetStateAction, useMemo } from 'react';
|
||||||
@ -19,6 +24,10 @@ interface RangePickerModalProps {
|
|||||||
lexicalContext?: LexicalContext | undefined,
|
lexicalContext?: LexicalContext | undefined,
|
||||||
) => void;
|
) => void;
|
||||||
selectedTime: string;
|
selectedTime: string;
|
||||||
|
onTimeChange?: (
|
||||||
|
interval: Time | CustomTimeType,
|
||||||
|
dateTimeRange?: [number, number],
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function RangePickerModal(props: RangePickerModalProps): JSX.Element {
|
function RangePickerModal(props: RangePickerModalProps): JSX.Element {
|
||||||
@ -27,6 +36,7 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
|
|||||||
setIsOpen,
|
setIsOpen,
|
||||||
onCustomDateHandler,
|
onCustomDateHandler,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
|
onTimeChange,
|
||||||
} = props;
|
} = props;
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
@ -74,13 +84,22 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
|
|||||||
date.tz(timezone.value).format(DATE_TIME_FORMATS.ISO_DATETIME)
|
date.tz(timezone.value).format(DATE_TIME_FORMATS.ISO_DATETIME)
|
||||||
}
|
}
|
||||||
onOk={onModalOkHandler}
|
onOk={onModalOkHandler}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
{...(selectedTime === 'custom' &&
|
||||||
{...(selectedTime === 'custom' && {
|
!onTimeChange && {
|
||||||
value: rangeValue,
|
value: rangeValue,
|
||||||
})}
|
})}
|
||||||
|
// use default value if onTimeChange is provided
|
||||||
|
{...(selectedTime === 'custom' &&
|
||||||
|
onTimeChange && {
|
||||||
|
defaultValue: rangeValue,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RangePickerModal.defaultProps = {
|
||||||
|
onTimeChange: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
export default RangePickerModal;
|
export default RangePickerModal;
|
||||||
|
@ -26,6 +26,7 @@ import { useQuery } from 'react-query';
|
|||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { VIEWS } from '../constants';
|
||||||
import { getHostTracesQueryPayload, selectedColumns } from './constants';
|
import { getHostTracesQueryPayload, selectedColumns } from './constants';
|
||||||
import { getListColumns } from './utils';
|
import { getListColumns } from './utils';
|
||||||
|
|
||||||
@ -39,7 +40,10 @@ interface Props {
|
|||||||
interval: Time | CustomTimeType,
|
interval: Time | CustomTimeType,
|
||||||
dateTimeRange?: [number, number],
|
dateTimeRange?: [number, number],
|
||||||
) => void;
|
) => void;
|
||||||
handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void;
|
handleChangeTracesFilters: (
|
||||||
|
value: IBuilderQuery['filters'],
|
||||||
|
view: VIEWS,
|
||||||
|
) => void;
|
||||||
tracesFilters: IBuilderQuery['filters'];
|
tracesFilters: IBuilderQuery['filters'];
|
||||||
selectedInterval: Time;
|
selectedInterval: Time;
|
||||||
}
|
}
|
||||||
@ -70,14 +74,16 @@ function HostMetricTraces({
|
|||||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
...currentQuery.builder.queryData[0].aggregateAttribute,
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
items: [],
|
items: tracesFilters.items.filter(
|
||||||
|
(item) => item.key?.key !== 'host.name',
|
||||||
|
),
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[currentQuery],
|
[currentQuery, tracesFilters.items],
|
||||||
);
|
);
|
||||||
|
|
||||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||||
@ -153,14 +159,16 @@ function HostMetricTraces({
|
|||||||
{query && (
|
{query && (
|
||||||
<QueryBuilderSearch
|
<QueryBuilderSearch
|
||||||
query={query}
|
query={query}
|
||||||
onChange={handleChangeTracesFilters}
|
onChange={(value): void =>
|
||||||
|
handleChangeTracesFilters(value, VIEWS.TRACES)
|
||||||
|
}
|
||||||
disableNavigationShortcuts
|
disableNavigationShortcuts
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="datetime-section">
|
<div className="datetime-section">
|
||||||
<DateTimeSelectionV2
|
<DateTimeSelectionV2
|
||||||
showAutoRefresh={false}
|
showAutoRefresh
|
||||||
showRefreshText={false}
|
showRefreshText={false}
|
||||||
hideShareModal
|
hideShareModal
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
@ -19,6 +19,8 @@ import {
|
|||||||
initialQueryState,
|
initialQueryState,
|
||||||
} from 'constants/queryBuilder';
|
} from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
|
import { getFiltersFromParams } from 'container/InfraMonitoringK8s/commonUtils';
|
||||||
|
import { INFRA_MONITORING_K8S_PARAMS_KEYS } from 'container/InfraMonitoringK8s/constants';
|
||||||
import {
|
import {
|
||||||
CustomTimeType,
|
CustomTimeType,
|
||||||
Time,
|
Time,
|
||||||
@ -93,8 +95,18 @@ function HostMetricsDetails({
|
|||||||
);
|
);
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
const initialFilters = useMemo(
|
const initialFilters = useMemo(() => {
|
||||||
() => ({
|
const urlView = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||||
|
const queryKey =
|
||||||
|
urlView === VIEW_TYPES.LOGS
|
||||||
|
? INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS
|
||||||
|
: INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS;
|
||||||
|
const filters = getFiltersFromParams(searchParams, queryKey);
|
||||||
|
if (filters) {
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@ -111,9 +123,8 @@ function HostMetricsDetails({
|
|||||||
value: host?.hostName || '',
|
value: host?.hostName || '',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
};
|
||||||
[host?.hostName],
|
}, [host?.hostName, searchParams]);
|
||||||
);
|
|
||||||
|
|
||||||
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||||
initialFilters,
|
initialFilters,
|
||||||
@ -154,7 +165,13 @@ function HostMetricsDetails({
|
|||||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||||
setSelectedView(e.target.value);
|
setSelectedView(e.target.value);
|
||||||
if (host?.hostName) {
|
if (host?.hostName) {
|
||||||
setSearchParams({ hostName: host?.hostName, view: e.target.value });
|
setSelectedView(e.target.value);
|
||||||
|
setSearchParams({
|
||||||
|
...Object.fromEntries(searchParams.entries()),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: e.target.value,
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(null),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(null),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||||
entity: InfraMonitoringEvents.HostEntity,
|
entity: InfraMonitoringEvents.HostEntity,
|
||||||
@ -191,7 +208,7 @@ function HostMetricsDetails({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleChangeLogFilters = useCallback(
|
const handleChangeLogFilters = useCallback(
|
||||||
(value: IBuilderQuery['filters']) => {
|
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||||
setLogFilters((prevFilters) => {
|
setLogFilters((prevFilters) => {
|
||||||
const hostNameFilter = prevFilters.items.find(
|
const hostNameFilter = prevFilters.items.find(
|
||||||
(item) => item.key?.key === 'host.name',
|
(item) => item.key?.key === 'host.name',
|
||||||
@ -209,7 +226,7 @@ function HostMetricsDetails({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const updatedFilters = {
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
items: [
|
items: [
|
||||||
hostNameFilter,
|
hostNameFilter,
|
||||||
@ -217,6 +234,15 @@ function HostMetricsDetails({
|
|||||||
...(paginationFilter ? [paginationFilter] : []),
|
...(paginationFilter ? [paginationFilter] : []),
|
||||||
].filter((item): item is TagFilterItem => item !== undefined),
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setSearchParams({
|
||||||
|
...Object.fromEntries(searchParams.entries()),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||||
|
updatedFilters,
|
||||||
|
),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||||
|
});
|
||||||
|
return updatedFilters;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@ -224,7 +250,7 @@ function HostMetricsDetails({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleChangeTracesFilters = useCallback(
|
const handleChangeTracesFilters = useCallback(
|
||||||
(value: IBuilderQuery['filters']) => {
|
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||||
setTracesFilters((prevFilters) => {
|
setTracesFilters((prevFilters) => {
|
||||||
const hostNameFilter = prevFilters.items.find(
|
const hostNameFilter = prevFilters.items.find(
|
||||||
(item) => item.key?.key === 'host.name',
|
(item) => item.key?.key === 'host.name',
|
||||||
@ -238,13 +264,23 @@ function HostMetricsDetails({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const updatedFilters = {
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
items: [
|
items: [
|
||||||
hostNameFilter,
|
hostNameFilter,
|
||||||
...value.items.filter((item) => item.key?.key !== 'host.name'),
|
...value.items.filter((item) => item.key?.key !== 'host.name'),
|
||||||
].filter((item): item is TagFilterItem => item !== undefined),
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setSearchParams({
|
||||||
|
...Object.fromEntries(searchParams.entries()),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||||
|
updatedFilters,
|
||||||
|
),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedFilters;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
@ -11,6 +11,7 @@ import { useMemo } from 'react';
|
|||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { VIEWS } from '../constants';
|
||||||
import HostMetricsLogs from './HostMetricsLogs';
|
import HostMetricsLogs from './HostMetricsLogs';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -23,7 +24,7 @@ interface Props {
|
|||||||
interval: Time | CustomTimeType,
|
interval: Time | CustomTimeType,
|
||||||
dateTimeRange?: [number, number],
|
dateTimeRange?: [number, number],
|
||||||
) => void;
|
) => void;
|
||||||
handleChangeLogFilters: (value: IBuilderQuery['filters']) => void;
|
handleChangeLogFilters: (value: IBuilderQuery['filters'], view: VIEWS) => void;
|
||||||
logFilters: IBuilderQuery['filters'];
|
logFilters: IBuilderQuery['filters'];
|
||||||
selectedInterval: Time;
|
selectedInterval: Time;
|
||||||
}
|
}
|
||||||
@ -51,14 +52,14 @@ function HostMetricLogsDetailedView({
|
|||||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
...currentQuery.builder.queryData[0].aggregateAttribute,
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
items: [],
|
items: logFilters.items.filter((item) => item.key?.key !== 'host.name'),
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[currentQuery],
|
[currentQuery, logFilters.items],
|
||||||
);
|
);
|
||||||
|
|
||||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||||
@ -70,14 +71,14 @@ function HostMetricLogsDetailedView({
|
|||||||
{query && (
|
{query && (
|
||||||
<QueryBuilderSearch
|
<QueryBuilderSearch
|
||||||
query={query}
|
query={query}
|
||||||
onChange={handleChangeLogFilters}
|
onChange={(value): void => handleChangeLogFilters(value, VIEWS.LOGS)}
|
||||||
disableNavigationShortcuts
|
disableNavigationShortcuts
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="datetime-section">
|
<div className="datetime-section">
|
||||||
<DateTimeSelectionV2
|
<DateTimeSelectionV2
|
||||||
showAutoRefresh={false}
|
showAutoRefresh
|
||||||
showRefreshText={false}
|
showRefreshText={false}
|
||||||
hideShareModal
|
hideShareModal
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
@ -18,7 +18,7 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
|||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useQueries, UseQueryResult } from 'react-query';
|
import { useQueries, UseQueryResult } from 'react-query';
|
||||||
import { SuccessResponse } from 'types/api';
|
import { SuccessResponse } from 'types/api';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
@ -68,6 +68,45 @@ function Metrics({
|
|||||||
[queries],
|
[queries],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [graphTimeIntervals, setGraphTimeIntervals] = useState<
|
||||||
|
{
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}[]
|
||||||
|
>(
|
||||||
|
new Array(queries.length).fill({
|
||||||
|
start: timeRange.startTime,
|
||||||
|
end: timeRange.endTime,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setGraphTimeIntervals(
|
||||||
|
new Array(queries.length).fill({
|
||||||
|
start: timeRange.startTime,
|
||||||
|
end: timeRange.endTime,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [timeRange]);
|
||||||
|
|
||||||
|
const onDragSelect = useCallback(
|
||||||
|
(start: number, end: number, graphIndex: number) => {
|
||||||
|
const startTimestamp = Math.trunc(start);
|
||||||
|
const endTimestamp = Math.trunc(end);
|
||||||
|
|
||||||
|
setGraphTimeIntervals((prev) => {
|
||||||
|
const newIntervals = [...prev];
|
||||||
|
newIntervals[graphIndex] = {
|
||||||
|
start: Math.floor(startTimestamp / 1000),
|
||||||
|
end: Math.floor(endTimestamp / 1000),
|
||||||
|
};
|
||||||
|
return newIntervals;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
() =>
|
() =>
|
||||||
queries.map(({ data }, idx) =>
|
queries.map(({ data }, idx) =>
|
||||||
@ -78,12 +117,12 @@ function Metrics({
|
|||||||
yAxisUnit: hostWidgetInfo[idx].yAxisUnit,
|
yAxisUnit: hostWidgetInfo[idx].yAxisUnit,
|
||||||
softMax: null,
|
softMax: null,
|
||||||
softMin: null,
|
softMin: null,
|
||||||
minTimeScale: timeRange.startTime,
|
minTimeScale: graphTimeIntervals[idx].start,
|
||||||
maxTimeScale: timeRange.endTime,
|
maxTimeScale: graphTimeIntervals[idx].end,
|
||||||
enableZoom: true,
|
onDragSelect: (start, end) => onDragSelect(start, end, idx),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
[queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime],
|
[queries, isDarkMode, dimensions, graphTimeIntervals, onDragSelect],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderCardContent = (
|
const renderCardContent = (
|
||||||
|
@ -191,8 +191,9 @@ function GridCardGraph({
|
|||||||
|
|
||||||
const isLogsQuery = useMemo(
|
const isLogsQuery = useMemo(
|
||||||
() =>
|
() =>
|
||||||
requestData.query.builder.queryData.every(
|
requestData?.query?.builder?.queryData?.length > 0 &&
|
||||||
(query) => query.dataSource === DataSource.LOGS,
|
requestData?.query?.builder?.queryData?.every(
|
||||||
|
(query) => query?.dataSource === DataSource.LOGS,
|
||||||
),
|
),
|
||||||
[requestData.query],
|
[requestData.query],
|
||||||
);
|
);
|
||||||
@ -203,7 +204,7 @@ function GridCardGraph({
|
|||||||
variables: getDashboardVariables(variables),
|
variables: getDashboardVariables(variables),
|
||||||
selectedTime: widget.timePreferance || 'GLOBAL_TIME',
|
selectedTime: widget.timePreferance || 'GLOBAL_TIME',
|
||||||
globalSelectedInterval:
|
globalSelectedInterval:
|
||||||
widget.panelTypes === PANEL_TYPES.LIST && isLogsQuery
|
widget?.panelTypes === PANEL_TYPES.LIST && isLogsQuery
|
||||||
? 'custom'
|
? 'custom'
|
||||||
: globalSelectedInterval,
|
: globalSelectedInterval,
|
||||||
start: customTimeRange?.startTime || start,
|
start: customTimeRange?.startTime || start,
|
||||||
|
@ -58,7 +58,7 @@ function HostsListControls({
|
|||||||
|
|
||||||
<div className="time-selector">
|
<div className="time-selector">
|
||||||
<DateTimeSelectionV2
|
<DateTimeSelectionV2
|
||||||
showAutoRefresh={false}
|
showAutoRefresh
|
||||||
showRefreshText={false}
|
showRefreshText={false}
|
||||||
hideShareModal
|
hideShareModal
|
||||||
/>
|
/>
|
||||||
|
@ -59,11 +59,26 @@ function K8sClustersList({
|
|||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
|
|
||||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const [currentPage, setCurrentPage] = useState(() => {
|
||||||
|
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||||
|
if (page) {
|
||||||
|
return parseInt(page, 10);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchParams({
|
||||||
|
...Object.fromEntries(searchParams.entries()),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
const [orderBy, setOrderBy] = useState<{
|
const [orderBy, setOrderBy] = useState<{
|
||||||
columnName: string;
|
columnName: string;
|
||||||
order: 'asc' | 'desc';
|
order: 'asc' | 'desc';
|
||||||
@ -115,7 +130,9 @@ function K8sClustersList({
|
|||||||
|
|
||||||
// Reset pagination every time quick filters are changed
|
// Reset pagination every time quick filters are changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage(1);
|
if (quickFiltersLastUpdated !== -1) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
}, [quickFiltersLastUpdated]);
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
const createFiltersForSelectedRowData = (
|
const createFiltersForSelectedRowData = (
|
||||||
@ -309,7 +326,11 @@ function K8sClustersList({
|
|||||||
const handleFiltersChange = useCallback(
|
const handleFiltersChange = useCallback(
|
||||||
(value: IBuilderQuery['filters']): void => {
|
(value: IBuilderQuery['filters']): void => {
|
||||||
handleChangeQueryData('filters', value);
|
handleChangeQueryData('filters', value);
|
||||||
setCurrentPage(1);
|
if (filtersInitialised) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
} else {
|
||||||
|
setFiltersInitialised(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.items.length > 0) {
|
if (value.items.length > 0) {
|
||||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||||
@ -319,7 +340,8 @@ function K8sClustersList({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleChangeQueryData],
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -61,11 +61,26 @@ function K8sDaemonSetsList({
|
|||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const [currentPage, setCurrentPage] = useState(() => {
|
||||||
|
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||||
|
if (page) {
|
||||||
|
return parseInt(page, 10);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchParams({
|
||||||
|
...Object.fromEntries(searchParams.entries()),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
const [orderBy, setOrderBy] = useState<{
|
const [orderBy, setOrderBy] = useState<{
|
||||||
columnName: string;
|
columnName: string;
|
||||||
order: 'asc' | 'desc';
|
order: 'asc' | 'desc';
|
||||||
@ -117,7 +132,9 @@ function K8sDaemonSetsList({
|
|||||||
|
|
||||||
// Reset pagination every time quick filters are changed
|
// Reset pagination every time quick filters are changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage(1);
|
if (quickFiltersLastUpdated !== -1) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
}, [quickFiltersLastUpdated]);
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
const createFiltersForSelectedRowData = (
|
const createFiltersForSelectedRowData = (
|
||||||
@ -313,7 +330,11 @@ function K8sDaemonSetsList({
|
|||||||
const handleFiltersChange = useCallback(
|
const handleFiltersChange = useCallback(
|
||||||
(value: IBuilderQuery['filters']): void => {
|
(value: IBuilderQuery['filters']): void => {
|
||||||
handleChangeQueryData('filters', value);
|
handleChangeQueryData('filters', value);
|
||||||
setCurrentPage(1);
|
if (filtersInitialised) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
} else {
|
||||||
|
setFiltersInitialised(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.items.length > 0) {
|
if (value.items.length > 0) {
|
||||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||||
@ -323,7 +344,8 @@ function K8sDaemonSetsList({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleChangeQueryData],
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -61,10 +61,26 @@ function K8sDeploymentsList({
|
|||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const [currentPage, setCurrentPage] = useState(() => {
|
||||||
|
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||||
|
if (page) {
|
||||||
|
return parseInt(page, 10);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchParams({
|
||||||
|
...Object.fromEntries(searchParams.entries()),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
|
|
||||||
const [orderBy, setOrderBy] = useState<{
|
const [orderBy, setOrderBy] = useState<{
|
||||||
columnName: string;
|
columnName: string;
|
||||||
@ -117,7 +133,9 @@ function K8sDeploymentsList({
|
|||||||
|
|
||||||
// Reset pagination every time quick filters are changed
|
// Reset pagination every time quick filters are changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage(1);
|
if (quickFiltersLastUpdated !== -1) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
}, [quickFiltersLastUpdated]);
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
const createFiltersForSelectedRowData = (
|
const createFiltersForSelectedRowData = (
|
||||||
@ -315,7 +333,11 @@ function K8sDeploymentsList({
|
|||||||
const handleFiltersChange = useCallback(
|
const handleFiltersChange = useCallback(
|
||||||
(value: IBuilderQuery['filters']): void => {
|
(value: IBuilderQuery['filters']): void => {
|
||||||
handleChangeQueryData('filters', value);
|
handleChangeQueryData('filters', value);
|
||||||
setCurrentPage(1);
|
if (filtersInitialised) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
} else {
|
||||||
|
setFiltersInitialised(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.items.length > 0) {
|
if (value.items.length > 0) {
|
||||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||||
@ -325,7 +347,8 @@ function K8sDeploymentsList({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleChangeQueryData],
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -259,7 +259,7 @@ export default function Events({
|
|||||||
</div>
|
</div>
|
||||||
<div className="datetime-section">
|
<div className="datetime-section">
|
||||||
<DateTimeSelectionV2
|
<DateTimeSelectionV2
|
||||||
showAutoRefresh={false}
|
showAutoRefresh
|
||||||
showRefreshText={false}
|
showRefreshText={false}
|
||||||
hideShareModal
|
hideShareModal
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
@ -86,7 +86,7 @@ function EntityLogsDetailedView({
|
|||||||
</div>
|
</div>
|
||||||
<div className="datetime-section">
|
<div className="datetime-section">
|
||||||
<DateTimeSelectionV2
|
<DateTimeSelectionV2
|
||||||
showAutoRefresh={false}
|
showAutoRefresh
|
||||||
showRefreshText={false}
|
showRefreshText={false}
|
||||||
hideShareModal
|
hideShareModal
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
} from 'lib/dashboard/getQueryResults';
|
} from 'lib/dashboard/getQueryResults';
|
||||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useQueries, UseQueryResult } from 'react-query';
|
import { useQueries, UseQueryResult } from 'react-query';
|
||||||
import { SuccessResponse } from 'types/api';
|
import { SuccessResponse } from 'types/api';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
@ -94,6 +94,45 @@ function EntityMetrics<T>({
|
|||||||
[queries],
|
[queries],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [graphTimeIntervals, setGraphTimeIntervals] = useState<
|
||||||
|
{
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}[]
|
||||||
|
>(
|
||||||
|
new Array(queries.length).fill({
|
||||||
|
start: timeRange.startTime,
|
||||||
|
end: timeRange.endTime,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setGraphTimeIntervals(
|
||||||
|
new Array(queries.length).fill({
|
||||||
|
start: timeRange.startTime,
|
||||||
|
end: timeRange.endTime,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [timeRange]);
|
||||||
|
|
||||||
|
const onDragSelect = useCallback(
|
||||||
|
(start: number, end: number, graphIndex: number) => {
|
||||||
|
const startTimestamp = Math.trunc(start);
|
||||||
|
const endTimestamp = Math.trunc(end);
|
||||||
|
|
||||||
|
setGraphTimeIntervals((prev) => {
|
||||||
|
const newIntervals = [...prev];
|
||||||
|
newIntervals[graphIndex] = {
|
||||||
|
start: Math.floor(startTimestamp / 1000),
|
||||||
|
end: Math.floor(endTimestamp / 1000),
|
||||||
|
};
|
||||||
|
return newIntervals;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
() =>
|
() =>
|
||||||
queries.map(({ data }, idx) => {
|
queries.map(({ data }, idx) => {
|
||||||
@ -108,9 +147,9 @@ function EntityMetrics<T>({
|
|||||||
yAxisUnit: entityWidgetInfo[idx].yAxisUnit,
|
yAxisUnit: entityWidgetInfo[idx].yAxisUnit,
|
||||||
softMax: null,
|
softMax: null,
|
||||||
softMin: null,
|
softMin: null,
|
||||||
minTimeScale: timeRange.startTime,
|
minTimeScale: graphTimeIntervals[idx].start,
|
||||||
maxTimeScale: timeRange.endTime,
|
maxTimeScale: graphTimeIntervals[idx].end,
|
||||||
enableZoom: true,
|
onDragSelect: (start, end) => onDragSelect(start, end, idx),
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
@ -118,8 +157,8 @@ function EntityMetrics<T>({
|
|||||||
isDarkMode,
|
isDarkMode,
|
||||||
dimensions,
|
dimensions,
|
||||||
entityWidgetInfo,
|
entityWidgetInfo,
|
||||||
timeRange.startTime,
|
graphTimeIntervals,
|
||||||
timeRange.endTime,
|
onDragSelect,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ function EntityTraces({
|
|||||||
</div>
|
</div>
|
||||||
<div className="datetime-section">
|
<div className="datetime-section">
|
||||||
<DateTimeSelectionV2
|
<DateTimeSelectionV2
|
||||||
showAutoRefresh={false}
|
showAutoRefresh
|
||||||
showRefreshText={false}
|
showRefreshText={false}
|
||||||
hideShareModal
|
hideShareModal
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
@ -60,11 +60,26 @@ function K8sJobsList({
|
|||||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(() => {
|
||||||
|
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||||
|
if (page) {
|
||||||
|
return parseInt(page, 10);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchParams({
|
||||||
|
...Object.fromEntries(searchParams.entries()),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
|
|
||||||
const [orderBy, setOrderBy] = useState<{
|
const [orderBy, setOrderBy] = useState<{
|
||||||
columnName: string;
|
columnName: string;
|
||||||
@ -112,7 +127,9 @@ function K8sJobsList({
|
|||||||
|
|
||||||
// Reset pagination every time quick filters are changed
|
// Reset pagination every time quick filters are changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage(1);
|
if (quickFiltersLastUpdated !== -1) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
}, [quickFiltersLastUpdated]);
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
const createFiltersForSelectedRowData = (
|
const createFiltersForSelectedRowData = (
|
||||||
@ -300,7 +317,11 @@ function K8sJobsList({
|
|||||||
const handleFiltersChange = useCallback(
|
const handleFiltersChange = useCallback(
|
||||||
(value: IBuilderQuery['filters']): void => {
|
(value: IBuilderQuery['filters']): void => {
|
||||||
handleChangeQueryData('filters', value);
|
handleChangeQueryData('filters', value);
|
||||||
setCurrentPage(1);
|
if (filtersInitialised) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
} else {
|
||||||
|
setFiltersInitialised(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.items.length > 0) {
|
if (value.items.length > 0) {
|
||||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||||
@ -310,7 +331,8 @@ function K8sJobsList({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleChangeQueryData],
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -136,7 +136,7 @@ function K8sHeader({
|
|||||||
|
|
||||||
<div className="k8s-list-controls-right">
|
<div className="k8s-list-controls-right">
|
||||||
<DateTimeSelectionV2
|
<DateTimeSelectionV2
|
||||||
showAutoRefresh={false}
|
showAutoRefresh
|
||||||
showRefreshText={false}
|
showRefreshText={false}
|
||||||
hideShareModal
|
hideShareModal
|
||||||
/>
|
/>
|
||||||
|
@ -60,11 +60,26 @@ function K8sNamespacesList({
|
|||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
|
|
||||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const [currentPage, setCurrentPage] = useState(() => {
|
||||||
|
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||||
|
if (page) {
|
||||||
|
return parseInt(page, 10);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchParams({
|
||||||
|
...Object.fromEntries(searchParams.entries()),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
const [orderBy, setOrderBy] = useState<{
|
const [orderBy, setOrderBy] = useState<{
|
||||||
columnName: string;
|
columnName: string;
|
||||||
order: 'asc' | 'desc';
|
order: 'asc' | 'desc';
|
||||||
@ -116,7 +131,9 @@ function K8sNamespacesList({
|
|||||||
|
|
||||||
// Reset pagination every time quick filters are changed
|
// Reset pagination every time quick filters are changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage(1);
|
if (quickFiltersLastUpdated !== -1) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
}, [quickFiltersLastUpdated]);
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
const createFiltersForSelectedRowData = (
|
const createFiltersForSelectedRowData = (
|
||||||
@ -312,7 +329,11 @@ function K8sNamespacesList({
|
|||||||
const handleFiltersChange = useCallback(
|
const handleFiltersChange = useCallback(
|
||||||
(value: IBuilderQuery['filters']): void => {
|
(value: IBuilderQuery['filters']): void => {
|
||||||
handleChangeQueryData('filters', value);
|
handleChangeQueryData('filters', value);
|
||||||
setCurrentPage(1);
|
if (filtersInitialised) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
} else {
|
||||||
|
setFiltersInitialised(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.items.length > 0) {
|
if (value.items.length > 0) {
|
||||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||||
@ -322,7 +343,8 @@ function K8sNamespacesList({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleChangeQueryData],
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -59,12 +59,26 @@ function K8sNodesList({
|
|||||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
|
|
||||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const [currentPage, setCurrentPage] = useState(() => {
|
||||||
|
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||||
|
if (page) {
|
||||||
|
return parseInt(page, 10);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchParams({
|
||||||
|
...Object.fromEntries(searchParams.entries()),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||||
const [orderBy, setOrderBy] = useState<{
|
const [orderBy, setOrderBy] = useState<{
|
||||||
columnName: string;
|
columnName: string;
|
||||||
order: 'asc' | 'desc';
|
order: 'asc' | 'desc';
|
||||||
@ -111,7 +125,9 @@ function K8sNodesList({
|
|||||||
|
|
||||||
// Reset pagination every time quick filters are changed
|
// Reset pagination every time quick filters are changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage(1);
|
if (quickFiltersLastUpdated !== -1) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
}, [quickFiltersLastUpdated]);
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
const createFiltersForSelectedRowData = (
|
const createFiltersForSelectedRowData = (
|
||||||
@ -299,7 +315,11 @@ function K8sNodesList({
|
|||||||
const handleFiltersChange = useCallback(
|
const handleFiltersChange = useCallback(
|
||||||
(value: IBuilderQuery['filters']): void => {
|
(value: IBuilderQuery['filters']): void => {
|
||||||
handleChangeQueryData('filters', value);
|
handleChangeQueryData('filters', value);
|
||||||
setCurrentPage(1);
|
if (filtersInitialised) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
} else {
|
||||||
|
setFiltersInitialised(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.items.length > 0) {
|
if (value.items.length > 0) {
|
||||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||||
@ -309,7 +329,8 @@ function K8sNodesList({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleChangeQueryData],
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -64,7 +64,22 @@ function K8sPodsList({
|
|||||||
);
|
);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(() => {
|
||||||
|
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||||
|
if (page) {
|
||||||
|
return parseInt(page, 10);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchParams({
|
||||||
|
...Object.fromEntries(searchParams.entries()),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
const [addedColumns, setAddedColumns] = useState<IEntityColumn[]>([]);
|
const [addedColumns, setAddedColumns] = useState<IEntityColumn[]>([]);
|
||||||
|
|
||||||
@ -123,7 +138,9 @@ function K8sPodsList({
|
|||||||
|
|
||||||
// Reset pagination every time quick filters are changed
|
// Reset pagination every time quick filters are changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage(1);
|
if (quickFiltersLastUpdated !== -1) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
}, [quickFiltersLastUpdated]);
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -314,7 +331,11 @@ function K8sPodsList({
|
|||||||
const handleFiltersChange = useCallback(
|
const handleFiltersChange = useCallback(
|
||||||
(value: IBuilderQuery['filters']): void => {
|
(value: IBuilderQuery['filters']): void => {
|
||||||
handleChangeQueryData('filters', value);
|
handleChangeQueryData('filters', value);
|
||||||
setCurrentPage(1);
|
if (filtersInitialised) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
} else {
|
||||||
|
setFiltersInitialised(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.items.length > 0) {
|
if (value.items.length > 0) {
|
||||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||||
@ -324,7 +345,8 @@ function K8sPodsList({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleChangeQueryData],
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleGroupByChange = useCallback(
|
const handleGroupByChange = useCallback(
|
||||||
|
@ -60,10 +60,26 @@ function K8sStatefulSetsList({
|
|||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const [currentPage, setCurrentPage] = useState(() => {
|
||||||
|
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||||
|
if (page) {
|
||||||
|
return parseInt(page, 10);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchParams({
|
||||||
|
...Object.fromEntries(searchParams.entries()),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
|
|
||||||
const [orderBy, setOrderBy] = useState<{
|
const [orderBy, setOrderBy] = useState<{
|
||||||
columnName: string;
|
columnName: string;
|
||||||
@ -116,7 +132,9 @@ function K8sStatefulSetsList({
|
|||||||
|
|
||||||
// Reset pagination every time quick filters are changed
|
// Reset pagination every time quick filters are changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage(1);
|
if (quickFiltersLastUpdated !== -1) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
}, [quickFiltersLastUpdated]);
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
const createFiltersForSelectedRowData = (
|
const createFiltersForSelectedRowData = (
|
||||||
@ -314,7 +332,11 @@ function K8sStatefulSetsList({
|
|||||||
const handleFiltersChange = useCallback(
|
const handleFiltersChange = useCallback(
|
||||||
(value: IBuilderQuery['filters']): void => {
|
(value: IBuilderQuery['filters']): void => {
|
||||||
handleChangeQueryData('filters', value);
|
handleChangeQueryData('filters', value);
|
||||||
setCurrentPage(1);
|
if (filtersInitialised) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
} else {
|
||||||
|
setFiltersInitialised(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.items.length > 0) {
|
if (value.items.length > 0) {
|
||||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||||
@ -324,7 +346,8 @@ function K8sStatefulSetsList({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleChangeQueryData],
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -60,10 +60,26 @@ function K8sVolumesList({
|
|||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const [currentPage, setCurrentPage] = useState(() => {
|
||||||
|
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||||
|
if (page) {
|
||||||
|
return parseInt(page, 10);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchParams({
|
||||||
|
...Object.fromEntries(searchParams.entries()),
|
||||||
|
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
|
|
||||||
const [orderBy, setOrderBy] = useState<{
|
const [orderBy, setOrderBy] = useState<{
|
||||||
columnName: string;
|
columnName: string;
|
||||||
@ -116,7 +132,9 @@ function K8sVolumesList({
|
|||||||
|
|
||||||
// Reset pagination every time quick filters are changed
|
// Reset pagination every time quick filters are changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage(1);
|
if (quickFiltersLastUpdated !== -1) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
}, [quickFiltersLastUpdated]);
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
const createFiltersForSelectedRowData = (
|
const createFiltersForSelectedRowData = (
|
||||||
@ -304,7 +322,11 @@ function K8sVolumesList({
|
|||||||
const handleFiltersChange = useCallback(
|
const handleFiltersChange = useCallback(
|
||||||
(value: IBuilderQuery['filters']): void => {
|
(value: IBuilderQuery['filters']): void => {
|
||||||
handleChangeQueryData('filters', value);
|
handleChangeQueryData('filters', value);
|
||||||
setCurrentPage(1);
|
if (filtersInitialised) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
} else {
|
||||||
|
setFiltersInitialised(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.items.length > 0) {
|
if (value.items.length > 0) {
|
||||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||||
@ -314,7 +336,8 @@ function K8sVolumesList({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleChangeQueryData],
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -538,4 +538,5 @@ export const INFRA_MONITORING_K8S_PARAMS_KEYS = {
|
|||||||
TRACES_FILTERS: 'tracesFilters',
|
TRACES_FILTERS: 'tracesFilters',
|
||||||
EVENTS_FILTERS: 'eventsFilters',
|
EVENTS_FILTERS: 'eventsFilters',
|
||||||
HOSTS_FILTERS: 'hostsFilters',
|
HOSTS_FILTERS: 'hostsFilters',
|
||||||
|
CURRENT_PAGE: 'currentPage',
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import { InfoCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Flex, Input, Tooltip, Typography } from 'antd';
|
import { Flex, Input, Typography } from 'antd';
|
||||||
import type { ColumnsType } from 'antd/es/table/interface';
|
import type { ColumnsType } from 'antd/es/table/interface';
|
||||||
import saveAlertApi from 'api/alerts/save';
|
import saveAlertApi from 'api/alerts/save';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
@ -175,41 +175,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
setData(filteredData);
|
setData(filteredData);
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchTooltipContent = (
|
|
||||||
<div style={{ maxWidth: 400 }}>
|
|
||||||
<div style={{ marginBottom: 8, fontWeight: 'bold' }}>Search Options:</div>
|
|
||||||
<div style={{ marginBottom: 4 }}>
|
|
||||||
• <strong>Plain text:</strong> Search across all fields
|
|
||||||
</div>
|
|
||||||
<div style={{ marginBottom: 4 }}>
|
|
||||||
• <strong>Key:value pairs:</strong> Specific field matching
|
|
||||||
</div>
|
|
||||||
<div style={{ marginBottom: 4 }}>
|
|
||||||
• <strong>Multiple terms:</strong> All terms must match (AND logic)
|
|
||||||
</div>
|
|
||||||
<div style={{ marginBottom: 8 }}>
|
|
||||||
• <strong>Status mapping:</strong> Use "ok" for inactive alerts
|
|
||||||
</div>
|
|
||||||
<div style={{ marginBottom: 8, fontWeight: 'bold' }}>Examples:</div>
|
|
||||||
<div style={{ marginBottom: 4 }}>
|
|
||||||
• <code>cpu warning</code> - Find alerts with both "cpu" and
|
|
||||||
"warning"
|
|
||||||
</div>
|
|
||||||
<div style={{ marginBottom: 4 }}>
|
|
||||||
• <code>status:ok</code> - Find alerts with OK status
|
|
||||||
</div>
|
|
||||||
<div style={{ marginBottom: 4 }}>
|
|
||||||
• <code>severity:critical</code> - Find critical alerts
|
|
||||||
</div>
|
|
||||||
<div style={{ marginBottom: 4 }}>
|
|
||||||
• <code>cluster:prod</code> - Find alerts with cluster=prod label
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
• <code>status:ok cpu</code> - Find OK alerts containing "cpu"
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const dynamicColumns: ColumnsType<GettableAlert> = [
|
const dynamicColumns: ColumnsType<GettableAlert> = [
|
||||||
{
|
{
|
||||||
title: 'Created At',
|
title: 'Created At',
|
||||||
@ -379,22 +344,11 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchContainer>
|
<SearchContainer>
|
||||||
<div className="search-container">
|
<Search
|
||||||
<Search
|
placeholder="Search by Alert Name, Severity and Labels"
|
||||||
placeholder="Search by name, status, severity, labels or key:value or chaining (e.g. 'status:ok cpu warning')"
|
onChange={handleSearch}
|
||||||
onChange={handleSearch}
|
defaultValue={searchString}
|
||||||
defaultValue={searchString}
|
/>
|
||||||
prefix={
|
|
||||||
<Flex align="center" gap={8}>
|
|
||||||
<Tooltip title={searchTooltipContent} placement="bottomRight">
|
|
||||||
<InfoCircleOutlined className="search-tooltip" />
|
|
||||||
</Tooltip>
|
|
||||||
<div className="search-divider" />
|
|
||||||
</Flex>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Flex gap={12}>
|
<Flex gap={12}>
|
||||||
{addNewAlert && (
|
{addNewAlert && (
|
||||||
<Button
|
<Button
|
||||||
|
131
frontend/src/container/ListAlertRules/__test__/utils.test.ts
Normal file
131
frontend/src/container/ListAlertRules/__test__/utils.test.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
/* eslint-disable sonarjs/no-duplicate-string */
|
||||||
|
import { GettableAlert } from 'types/api/alerts/get';
|
||||||
|
|
||||||
|
import { filterAlerts } from '../utils';
|
||||||
|
|
||||||
|
describe('filterAlerts', () => {
|
||||||
|
const mockAlertBase: Partial<GettableAlert> = {
|
||||||
|
state: 'active',
|
||||||
|
disabled: false,
|
||||||
|
createAt: '2024-01-01T00:00:00Z',
|
||||||
|
createBy: 'test-user',
|
||||||
|
updateAt: '2024-01-01T00:00:00Z',
|
||||||
|
updateBy: 'test-user',
|
||||||
|
version: '1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockAlerts: GettableAlert[] = [
|
||||||
|
{
|
||||||
|
...mockAlertBase,
|
||||||
|
id: '1',
|
||||||
|
alert: 'High CPU Usage',
|
||||||
|
alertType: 'metrics',
|
||||||
|
labels: {
|
||||||
|
severity: 'warning',
|
||||||
|
status: 'ok',
|
||||||
|
environment: 'production',
|
||||||
|
},
|
||||||
|
} as GettableAlert,
|
||||||
|
{
|
||||||
|
...mockAlertBase,
|
||||||
|
id: '2',
|
||||||
|
alert: 'Memory Leak Detected',
|
||||||
|
alertType: 'metrics',
|
||||||
|
labels: {
|
||||||
|
severity: 'critical',
|
||||||
|
status: 'firing',
|
||||||
|
environment: 'staging',
|
||||||
|
},
|
||||||
|
} as GettableAlert,
|
||||||
|
{
|
||||||
|
...mockAlertBase,
|
||||||
|
id: '3',
|
||||||
|
alert: 'Database Connection Error',
|
||||||
|
alertType: 'metrics',
|
||||||
|
labels: {
|
||||||
|
severity: 'error',
|
||||||
|
status: 'pending',
|
||||||
|
environment: 'production',
|
||||||
|
},
|
||||||
|
} as GettableAlert,
|
||||||
|
];
|
||||||
|
|
||||||
|
it('should return all alerts when filter is empty', () => {
|
||||||
|
const result = filterAlerts(mockAlerts, '');
|
||||||
|
expect(result).toEqual(mockAlerts);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all alerts when filter is only whitespace', () => {
|
||||||
|
const result = filterAlerts(mockAlerts, ' ');
|
||||||
|
expect(result).toEqual(mockAlerts);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter alerts by alert name', () => {
|
||||||
|
const result = filterAlerts(mockAlerts, 'CPU');
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].alert).toBe('High CPU Usage');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter alerts by severity', () => {
|
||||||
|
const result = filterAlerts(mockAlerts, 'warning');
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].labels?.severity).toBe('warning');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter alerts by label key', () => {
|
||||||
|
const result = filterAlerts(mockAlerts, 'environment');
|
||||||
|
expect(result).toHaveLength(3); // All alerts have environment label
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter alerts by label value', () => {
|
||||||
|
const result = filterAlerts(mockAlerts, 'production');
|
||||||
|
expect(result).toHaveLength(2);
|
||||||
|
expect(
|
||||||
|
result.every((alert) => alert.labels?.environment === 'production'),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be case insensitive', () => {
|
||||||
|
const result = filterAlerts(mockAlerts, 'cpu');
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].alert).toBe('High CPU Usage');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle partial matches', () => {
|
||||||
|
const result = filterAlerts(mockAlerts, 'mem');
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].alert).toBe('Memory Leak Detected');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle alerts with missing labels', () => {
|
||||||
|
const alertsWithMissingLabels: GettableAlert[] = [
|
||||||
|
{
|
||||||
|
...mockAlertBase,
|
||||||
|
id: '4',
|
||||||
|
alert: 'Test Alert',
|
||||||
|
alertType: 'metrics',
|
||||||
|
labels: undefined,
|
||||||
|
} as GettableAlert,
|
||||||
|
];
|
||||||
|
const result = filterAlerts(alertsWithMissingLabels, 'test');
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].alert).toBe('Test Alert');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle alerts with missing alert name', () => {
|
||||||
|
const alertsWithMissingName: GettableAlert[] = [
|
||||||
|
{
|
||||||
|
...mockAlertBase,
|
||||||
|
id: '5',
|
||||||
|
alert: '',
|
||||||
|
alertType: 'metrics',
|
||||||
|
labels: {
|
||||||
|
severity: 'warning',
|
||||||
|
},
|
||||||
|
} as GettableAlert,
|
||||||
|
];
|
||||||
|
const result = filterAlerts(alertsWithMissingName, 'warning');
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].labels?.severity).toBe('warning');
|
||||||
|
});
|
||||||
|
});
|
@ -8,24 +8,6 @@ export const SearchContainer = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
.search-container {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
.search-tooltip {
|
|
||||||
color: var(--bg-robin-500);
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-divider {
|
|
||||||
height: 16px;
|
|
||||||
margin: 0;
|
|
||||||
border-left: 1px solid var(--bg-slate-100);
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Button = styled(ButtonComponent)`
|
export const Button = styled(ButtonComponent)`
|
||||||
|
@ -1,162 +0,0 @@
|
|||||||
/* eslint-disable sonarjs/no-duplicate-string */
|
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import { GettableAlert } from 'types/api/alerts/get';
|
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
|
||||||
|
|
||||||
import { filterAlerts } from './utils';
|
|
||||||
|
|
||||||
const testLabels = { severity: 'warning', cluster: 'prod', test: 'value' };
|
|
||||||
|
|
||||||
const baseAlert: GettableAlert = {
|
|
||||||
id: '1',
|
|
||||||
alert: 'CPU Usage High',
|
|
||||||
state: 'inactive',
|
|
||||||
disabled: false,
|
|
||||||
createAt: '',
|
|
||||||
createBy: '',
|
|
||||||
updateAt: '',
|
|
||||||
updateBy: '',
|
|
||||||
alertType: 'type',
|
|
||||||
ruleType: 'rule',
|
|
||||||
frequency: '1m',
|
|
||||||
condition: {
|
|
||||||
compositeQuery: {
|
|
||||||
builderQueries: {},
|
|
||||||
promQueries: {},
|
|
||||||
chQueries: {},
|
|
||||||
queryType: EQueryType.QUERY_BUILDER,
|
|
||||||
panelType: PANEL_TYPES.TABLE,
|
|
||||||
unit: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
labels: testLabels,
|
|
||||||
annotations: {},
|
|
||||||
evalWindow: '',
|
|
||||||
source: '',
|
|
||||||
preferredChannels: [],
|
|
||||||
broadcastToAll: false,
|
|
||||||
version: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const alerts: GettableAlert[] = [
|
|
||||||
{
|
|
||||||
...baseAlert,
|
|
||||||
id: '1',
|
|
||||||
alert: 'CPU Usage High',
|
|
||||||
state: 'inactive',
|
|
||||||
labels: testLabels,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...baseAlert,
|
|
||||||
id: '2',
|
|
||||||
alert: 'Memory Usage',
|
|
||||||
state: 'firing',
|
|
||||||
labels: { severity: 'critical', cluster: 'dev', test: 'other' },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...baseAlert,
|
|
||||||
id: '3',
|
|
||||||
alert: 'Disk IO',
|
|
||||||
state: 'pending',
|
|
||||||
labels: testLabels,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...baseAlert,
|
|
||||||
id: '4',
|
|
||||||
alert: 'Network Latency',
|
|
||||||
state: 'disabled',
|
|
||||||
labels: { severity: 'info', cluster: 'qa', test: 'value' },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
describe('filterAlerts', () => {
|
|
||||||
it('returns all alerts if filter is empty', () => {
|
|
||||||
expect(filterAlerts(alerts, '')).toHaveLength(alerts.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by alert name (case-insensitive)', () => {
|
|
||||||
const result = filterAlerts(alerts, 'cpu usage');
|
|
||||||
expect(result).toHaveLength(1);
|
|
||||||
expect(result[0].alert).toBe('CPU Usage High');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by severity', () => {
|
|
||||||
const result = filterAlerts(alerts, 'warning');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['1', '3']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by label key or value', () => {
|
|
||||||
const result = filterAlerts(alerts, 'prod');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['1', '3']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by multi-word AND search', () => {
|
|
||||||
const result = filterAlerts(alerts, 'cpu prod');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['1']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by key:value (label)', () => {
|
|
||||||
const result = filterAlerts(alerts, 'test:value');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['1', '3', '4']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by key: value (label, with space)', () => {
|
|
||||||
const result = filterAlerts(alerts, 'test: value');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['1', '3', '4']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by key:value (severity)', () => {
|
|
||||||
const result = filterAlerts(alerts, 'severity:warning');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['1', '3']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by key:value (status:ok)', () => {
|
|
||||||
const result = filterAlerts(alerts, 'status:ok');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['1']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by key:value (status:inactive)', () => {
|
|
||||||
const result = filterAlerts(alerts, 'status:inactive');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['1']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by key:value (status:firing)', () => {
|
|
||||||
const result = filterAlerts(alerts, 'status:firing');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['2']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by key:value (status:pending)', () => {
|
|
||||||
const result = filterAlerts(alerts, 'status:pending');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['3']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by key:value (status:disabled)', () => {
|
|
||||||
const result = filterAlerts(alerts, 'status:disabled');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['4']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by key:value (cluster:prod)', () => {
|
|
||||||
const result = filterAlerts(alerts, 'cluster:prod');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['1', '3']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by key:value (cluster:dev)', () => {
|
|
||||||
const result = filterAlerts(alerts, 'cluster:dev');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['2']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by key:value (case-insensitive)', () => {
|
|
||||||
const result = filterAlerts(alerts, 'CLUSTER:PROD');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['1', '3']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('matches by combination of word and key:value', () => {
|
|
||||||
const result = filterAlerts(alerts, 'cpu status:ok');
|
|
||||||
expect(result.map((a) => a.id)).toEqual(['1']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns empty if no match', () => {
|
|
||||||
const result = filterAlerts(alerts, 'notfound');
|
|
||||||
expect(result).toHaveLength(0);
|
|
||||||
});
|
|
||||||
});
|
|
@ -3,108 +3,29 @@ import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
|||||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
import { GettableAlert } from 'types/api/alerts/get';
|
import { GettableAlert } from 'types/api/alerts/get';
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses key:value pairs from the filter string, allowing optional whitespace after the colon.
|
|
||||||
*/
|
|
||||||
function parseKeyValuePairs(
|
|
||||||
filter: string,
|
|
||||||
): { keyValuePairs: Record<string, string>; filterCopy: string } {
|
|
||||||
// Allow optional whitespace after colon, and support more flexible values
|
|
||||||
const keyValueRegex = /([\w-]+):\s*([^\s]+)/g;
|
|
||||||
const keyValuePairs: Record<string, string> = {};
|
|
||||||
let filterCopy = filter.toLowerCase();
|
|
||||||
const matches = Array.from(filterCopy.matchAll(keyValueRegex));
|
|
||||||
matches.forEach((match) => {
|
|
||||||
const [, key, value] = match;
|
|
||||||
keyValuePairs[key] = value.trim();
|
|
||||||
filterCopy = filterCopy.replace(match[0], '');
|
|
||||||
});
|
|
||||||
return { keyValuePairs, filterCopy };
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusMap: Record<string, string> = {
|
|
||||||
ok: 'inactive',
|
|
||||||
inactive: 'inactive',
|
|
||||||
pending: 'pending',
|
|
||||||
firing: 'firing',
|
|
||||||
disabled: 'disabled',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the alert matches the search words and key-value pairs.
|
|
||||||
*/
|
|
||||||
function alertMatches(alert: GettableAlert, searchWords: string[]): boolean {
|
|
||||||
const alertName = alert.alert?.toLowerCase() || '';
|
|
||||||
const severity = alert.labels?.severity?.toLowerCase() || '';
|
|
||||||
const status = alert.state?.toLowerCase() || '';
|
|
||||||
const labelKeys = Object.keys(alert.labels || {})
|
|
||||||
.filter((e) => e !== 'severity')
|
|
||||||
.map((k) => k.toLowerCase());
|
|
||||||
const labelValues = Object.values(alert.labels || {}).map((v) =>
|
|
||||||
typeof v === 'string' ? v.toLowerCase() : '',
|
|
||||||
);
|
|
||||||
|
|
||||||
const searchable = [
|
|
||||||
alertName,
|
|
||||||
severity,
|
|
||||||
status,
|
|
||||||
...labelKeys,
|
|
||||||
...labelValues,
|
|
||||||
].join(' ');
|
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
||||||
return searchWords.every((word) => {
|
|
||||||
const plainTextMatch = searchable.includes(word);
|
|
||||||
|
|
||||||
// Check if this word is a key:value pair
|
|
||||||
const isKeyValue = word.includes(':');
|
|
||||||
if (isKeyValue) {
|
|
||||||
// For key:value pairs, check if the key:value logic matches
|
|
||||||
const [key, value] = word.split(':');
|
|
||||||
const keyValueMatch = ((): boolean => {
|
|
||||||
if (key === 'severity') {
|
|
||||||
return severity === value;
|
|
||||||
}
|
|
||||||
if (key === 'status') {
|
|
||||||
const mappedStatus = statusMap[value] || value;
|
|
||||||
const labelVal =
|
|
||||||
alert.labels && key in alert.labels ? alert.labels[key] : undefined;
|
|
||||||
return (
|
|
||||||
status === mappedStatus ||
|
|
||||||
(typeof labelVal === 'string' && labelVal.toLowerCase() === value)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (alert.labels && key in alert.labels) {
|
|
||||||
const labelVal = alert.labels[key];
|
|
||||||
return typeof labelVal === 'string' && labelVal.toLowerCase() === value;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
})();
|
|
||||||
|
|
||||||
// For key:value pairs, match if EITHER plain text OR key:value logic matches
|
|
||||||
return plainTextMatch || keyValueMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For regular words, only plain text matching is required
|
|
||||||
return plainTextMatch;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const filterAlerts = (
|
export const filterAlerts = (
|
||||||
allAlertRules: GettableAlert[],
|
allAlertRules: GettableAlert[],
|
||||||
filter: string,
|
filter: string,
|
||||||
): GettableAlert[] => {
|
): GettableAlert[] => {
|
||||||
if (!filter.trim()) return allAlertRules;
|
if (!filter.trim()) return allAlertRules;
|
||||||
|
|
||||||
const { keyValuePairs, filterCopy } = parseKeyValuePairs(filter);
|
const value = filter.trim().toLowerCase();
|
||||||
// Include both the remaining words AND the original key:value strings as search words
|
return allAlertRules.filter((alert) => {
|
||||||
const remainingWords = filterCopy.split(/\s+/).filter(Boolean);
|
const alertName = alert.alert?.toLowerCase();
|
||||||
const keyValueStrings = Object.entries(keyValuePairs).map(
|
const severity = alert.labels?.severity?.toLowerCase();
|
||||||
([key, value]) => `${key}:${value}`,
|
|
||||||
);
|
|
||||||
const searchWords = [...remainingWords, ...keyValueStrings];
|
|
||||||
|
|
||||||
return allAlertRules.filter((alert) => alertMatches(alert, searchWords));
|
// Create a string of all label keys and values for searching
|
||||||
|
const labelSearchString = Object.entries(alert.labels || {})
|
||||||
|
.map(([key, val]) => `${key} ${val}`)
|
||||||
|
.join(' ')
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
alertName?.includes(value) ||
|
||||||
|
severity?.includes(value) ||
|
||||||
|
labelSearchString.includes(value)
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const alertActionLogEvent = (
|
export const alertActionLogEvent = (
|
||||||
|
@ -108,7 +108,7 @@ function LogsPanelComponent({
|
|||||||
...prev,
|
...prev,
|
||||||
tableParams: {
|
tableParams: {
|
||||||
pagination: {
|
pagination: {
|
||||||
limit: widget.query.builder.queryData[0].limit || 0,
|
limit: widget.query?.builder?.queryData[0]?.limit || 0,
|
||||||
offset: newOffset < 0 ? 0 : newOffset,
|
offset: newOffset < 0 ? 0 : newOffset,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -105,9 +105,11 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
GlobalReducer
|
GlobalReducer
|
||||||
>((state) => state.globalTime);
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
const isLogsQuery = currentQuery.builder.queryData.every(
|
const isLogsQuery =
|
||||||
(query) => query.dataSource === DataSource.LOGS,
|
currentQuery?.builder?.queryData?.length > 0 &&
|
||||||
);
|
currentQuery?.builder?.queryData?.every(
|
||||||
|
(query) => query?.dataSource === DataSource.LOGS,
|
||||||
|
);
|
||||||
|
|
||||||
const customGlobalSelectedInterval = useMemo(
|
const customGlobalSelectedInterval = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -358,7 +360,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
// this has been moved here from the left container
|
// this has been moved here from the left container
|
||||||
const [requestData, setRequestData] = useState<GetQueryResultsProps>(() => {
|
const [requestData, setRequestData] = useState<GetQueryResultsProps>(() => {
|
||||||
const updatedQuery = cloneDeep(stagedQuery || initialQueriesMap.metrics);
|
const updatedQuery = cloneDeep(stagedQuery || initialQueriesMap.metrics);
|
||||||
updatedQuery.builder.queryData[0].pageSize = 10;
|
if (updatedQuery?.builder?.queryData?.[0]) {
|
||||||
|
updatedQuery.builder.queryData[0].pageSize = 10;
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedWidget) {
|
if (selectedWidget) {
|
||||||
if (selectedGraph === PANEL_TYPES.LIST) {
|
if (selectedGraph === PANEL_TYPES.LIST) {
|
||||||
@ -406,7 +410,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
if (stagedQuery) {
|
if (stagedQuery) {
|
||||||
setIsLoadingPanelData(false);
|
setIsLoadingPanelData(false);
|
||||||
const updatedStagedQuery = cloneDeep(stagedQuery);
|
const updatedStagedQuery = cloneDeep(stagedQuery);
|
||||||
updatedStagedQuery.builder.queryData[0].pageSize = 10;
|
if (updatedStagedQuery?.builder?.queryData?.[0]) {
|
||||||
|
updatedStagedQuery.builder.queryData[0].pageSize = 10;
|
||||||
|
}
|
||||||
setRequestData((prev) => ({
|
setRequestData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
selectedTime: selectedTime.enum || prev.selectedTime,
|
selectedTime: selectedTime.enum || prev.selectedTime,
|
||||||
|
@ -835,6 +835,7 @@ function DateTimeSelection({
|
|||||||
onCustomDateHandler={onCustomDateHandler}
|
onCustomDateHandler={onCustomDateHandler}
|
||||||
customDateTimeVisible={customDateTimeVisible}
|
customDateTimeVisible={customDateTimeVisible}
|
||||||
setCustomDTPickerVisible={setCustomDTPickerVisible}
|
setCustomDTPickerVisible={setCustomDTPickerVisible}
|
||||||
|
onTimeChange={onTimeChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showAutoRefresh && selectedTime !== 'custom' && (
|
{showAutoRefresh && selectedTime !== 'custom' && (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user