mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 05:25:57 +08:00
feat: created a component for apdex (#3283)
* feat: created a component for apdex * refactor: added get and set apDexSetting API support * refactor: done with ApDexSetting * feat: done with the traces graph for ApDex * refactor: separated traces using feature flag * refactor: restucture the component level * feat: done with metrics ApDex application * refactor: removed unwanted logs * fix: some css part * refactor: handle error state * refactor: made use of constants and handleGraphClick for apDex * refactor: shifted type to type.ts * chore: css fixes * refactor: handle error and loading state * refactor: one on one mapping * refactor: according to review comments * refactor: removed unwanted color from local theme colors * refactor: one on one mapping for queryKey * refactor: updated css for view traces button issue * chore: commented out the ExcludeStatusCode feature * refactor: updated some css part of ApDexSetting popover * test: added test case for ApDexApplication and ApDexSettings * refactor: test cases * refactor: review comments * refactor: remove the checked for threshold size upto 1 * refactor: changed some text part of ApDex * refactor: only ApDexMetrics inuse * refactor: changes due to merge conflicts * fix: build pipeline * chore: change the type of the threshold * feat: widget header as ReactNode * chore: error for the title is updated * refactor: widget header as Reactnode * refactor: show tooltip when hover over the question icon * refactor: review changes * refactor: convert threadhold to ReactNode * refactor: updated test cases * refactor: move allow threshold a level up * fix: build pipeline * fix: input number issue for value 0 --------- Co-authored-by: Palash Gupta <palashgdev@gmail.com> Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
This commit is contained in:
parent
f8ec850670
commit
8844144c01
16
frontend/src/api/metrics/ApDex/apDexSettings.ts
Normal file
16
frontend/src/api/metrics/ApDex/apDexSettings.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import {
|
||||||
|
ApDexPayloadAndSettingsProps,
|
||||||
|
SetApDexPayloadProps,
|
||||||
|
} from 'types/api/metrics/getApDex';
|
||||||
|
|
||||||
|
export const setApDexSettings = async ({
|
||||||
|
servicename,
|
||||||
|
threshold,
|
||||||
|
excludeStatusCode,
|
||||||
|
}: ApDexPayloadAndSettingsProps): Promise<SetApDexPayloadProps> =>
|
||||||
|
axios.post('/settings/apdex', {
|
||||||
|
servicename,
|
||||||
|
threshold,
|
||||||
|
excludeStatusCode,
|
||||||
|
});
|
8
frontend/src/api/metrics/ApDex/getApDexSettings.ts
Normal file
8
frontend/src/api/metrics/ApDex/getApDexSettings.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { ApDexPayloadAndSettingsProps } from 'types/api/metrics/getApDex';
|
||||||
|
|
||||||
|
export const getApDexSettings = (
|
||||||
|
servicename: string,
|
||||||
|
): Promise<AxiosResponse<ApDexPayloadAndSettingsProps[]>> =>
|
||||||
|
axios.get(`/settings/apdex?services=${servicename}`);
|
8
frontend/src/api/metrics/ApDex/getMetricMeta.ts
Normal file
8
frontend/src/api/metrics/ApDex/getMetricMeta.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { MetricMetaProps } from 'types/api/metrics/getApDex';
|
||||||
|
|
||||||
|
export const getMetricMeta = (
|
||||||
|
metricName: string,
|
||||||
|
): Promise<AxiosResponse<MetricMetaProps>> =>
|
||||||
|
axios.get(`/metric_meta?metricName=${metricName}`);
|
@ -3,7 +3,6 @@ import {
|
|||||||
BarElement,
|
BarElement,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
Chart,
|
Chart,
|
||||||
ChartType,
|
|
||||||
Decimation,
|
Decimation,
|
||||||
Filler,
|
Filler,
|
||||||
Legend,
|
Legend,
|
||||||
@ -18,6 +17,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import annotationPlugin from 'chartjs-plugin-annotation';
|
import annotationPlugin from 'chartjs-plugin-annotation';
|
||||||
|
import { generateGridTitle } from 'container/GridPanelSwitch/utils';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import isEqual from 'lodash-es/isEqual';
|
import isEqual from 'lodash-es/isEqual';
|
||||||
import {
|
import {
|
||||||
@ -26,6 +26,7 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
@ -83,6 +84,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
|||||||
const nearestDatasetIndex = useRef<null | number>(null);
|
const nearestDatasetIndex = useRef<null | number>(null);
|
||||||
const chartRef = useRef<HTMLCanvasElement>(null);
|
const chartRef = useRef<HTMLCanvasElement>(null);
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
const gridTitle = useMemo(() => generateGridTitle(title), [title]);
|
||||||
|
|
||||||
const currentTheme = isDarkMode ? 'dark' : 'light';
|
const currentTheme = isDarkMode ? 'dark' : 'light';
|
||||||
const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data
|
const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data
|
||||||
@ -119,7 +121,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
|||||||
const options: CustomChartOptions = getGraphOptions(
|
const options: CustomChartOptions = getGraphOptions(
|
||||||
animate,
|
animate,
|
||||||
staticLine,
|
staticLine,
|
||||||
title,
|
gridTitle,
|
||||||
nearestDatasetIndex,
|
nearestDatasetIndex,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
onDragSelect,
|
onDragSelect,
|
||||||
@ -154,7 +156,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
|||||||
}, [
|
}, [
|
||||||
animate,
|
animate,
|
||||||
staticLine,
|
staticLine,
|
||||||
title,
|
gridTitle,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
onDragSelect,
|
onDragSelect,
|
||||||
dragSelectColor,
|
dragSelectColor,
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
ChartType,
|
ChartType,
|
||||||
TimeUnit,
|
TimeUnit,
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import { ForwardedRef } from 'react';
|
import { ForwardedRef, ReactNode } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
dragSelectPluginId,
|
dragSelectPluginId,
|
||||||
@ -49,7 +49,7 @@ export interface GraphProps {
|
|||||||
animate?: boolean;
|
animate?: boolean;
|
||||||
type: ChartType;
|
type: ChartType;
|
||||||
data: Chart['data'];
|
data: Chart['data'];
|
||||||
title?: string;
|
title?: ReactNode;
|
||||||
isStacked?: boolean;
|
isStacked?: boolean;
|
||||||
onClickHandler?: GraphOnClickHandler;
|
onClickHandler?: GraphOnClickHandler;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { grey } from '@ant-design/colors';
|
import { blue, grey } from '@ant-design/colors';
|
||||||
import { QuestionCircleFilled } from '@ant-design/icons';
|
import {
|
||||||
|
QuestionCircleFilled,
|
||||||
|
QuestionCircleOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
import { Tooltip } from 'antd';
|
import { Tooltip } from 'antd';
|
||||||
import { themeColors } from 'constants/theme';
|
import { themeColors } from 'constants/theme';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
@ -7,7 +10,12 @@ import { useMemo } from 'react';
|
|||||||
|
|
||||||
import { style } from './styles';
|
import { style } from './styles';
|
||||||
|
|
||||||
function TextToolTip({ text, url }: TextToolTipProps): JSX.Element {
|
function TextToolTip({
|
||||||
|
text,
|
||||||
|
url,
|
||||||
|
useFilledIcon = true,
|
||||||
|
urlText,
|
||||||
|
}: TextToolTipProps): JSX.Element {
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
const overlay = useMemo(
|
const overlay = useMemo(
|
||||||
@ -16,12 +24,12 @@ function TextToolTip({ text, url }: TextToolTipProps): JSX.Element {
|
|||||||
{`${text} `}
|
{`${text} `}
|
||||||
{url && (
|
{url && (
|
||||||
<a href={url} rel="noopener noreferrer" target="_blank">
|
<a href={url} rel="noopener noreferrer" target="_blank">
|
||||||
here
|
{urlText || 'here'}
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
[text, url],
|
[text, url, urlText],
|
||||||
);
|
);
|
||||||
|
|
||||||
const iconStyle = useMemo(
|
const iconStyle = useMemo(
|
||||||
@ -32,19 +40,35 @@ function TextToolTip({ text, url }: TextToolTipProps): JSX.Element {
|
|||||||
[isDarkMode],
|
[isDarkMode],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const iconOutlinedStyle = useMemo(
|
||||||
|
() => ({
|
||||||
|
...style,
|
||||||
|
color: isDarkMode ? themeColors.navyBlue : blue[0],
|
||||||
|
}),
|
||||||
|
[isDarkMode],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip overlay={overlay}>
|
<Tooltip overlay={overlay}>
|
||||||
<QuestionCircleFilled style={iconStyle} />
|
{useFilledIcon ? (
|
||||||
|
<QuestionCircleFilled style={iconStyle} />
|
||||||
|
) : (
|
||||||
|
<QuestionCircleOutlined style={iconOutlinedStyle} />
|
||||||
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextToolTip.defaultProps = {
|
TextToolTip.defaultProps = {
|
||||||
url: '',
|
url: '',
|
||||||
|
urlText: '',
|
||||||
|
useFilledIcon: true,
|
||||||
};
|
};
|
||||||
interface TextToolTipProps {
|
interface TextToolTipProps {
|
||||||
url?: string;
|
url?: string;
|
||||||
text: string;
|
text: string;
|
||||||
|
useFilledIcon?: boolean;
|
||||||
|
urlText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TextToolTip;
|
export default TextToolTip;
|
||||||
|
5
frontend/src/constants/apDex.ts
Normal file
5
frontend/src/constants/apDex.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const apDexToolTipText =
|
||||||
|
"Apdex is a way to measure your users' satisfaction with the response time of your web service. It's represented as a score from 0-1.";
|
||||||
|
export const apDexToolTipUrl =
|
||||||
|
'https://signoz.io/docs/userguide/metrics/#apdex?utm_source=product&utm_medium=frontend&utm_campaign=apdex';
|
||||||
|
export const apDexToolTipUrlText = 'Learn more about Apdex.';
|
@ -53,6 +53,7 @@ function WidgetGraphComponent({
|
|||||||
setLayout,
|
setLayout,
|
||||||
onDragSelect,
|
onDragSelect,
|
||||||
onClickHandler,
|
onClickHandler,
|
||||||
|
threshold,
|
||||||
headerMenuList,
|
headerMenuList,
|
||||||
}: WidgetGraphComponentProps): JSX.Element {
|
}: WidgetGraphComponentProps): JSX.Element {
|
||||||
const [deleteModal, setDeleteModal] = useState(false);
|
const [deleteModal, setDeleteModal] = useState(false);
|
||||||
@ -279,6 +280,7 @@ function WidgetGraphComponent({
|
|||||||
onClone={onCloneHandler}
|
onClone={onCloneHandler}
|
||||||
queryResponse={queryResponse}
|
queryResponse={queryResponse}
|
||||||
errorMessage={errorMessage}
|
errorMessage={errorMessage}
|
||||||
|
threshold={threshold}
|
||||||
headerMenuList={headerMenuList}
|
headerMenuList={headerMenuList}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,6 +29,7 @@ function GridCardGraph({
|
|||||||
onClickHandler,
|
onClickHandler,
|
||||||
headerMenuList = [MenuItemKeys.View],
|
headerMenuList = [MenuItemKeys.View],
|
||||||
isQueryEnabled,
|
isQueryEnabled,
|
||||||
|
threshold,
|
||||||
}: GridCardGraphProps): JSX.Element {
|
}: GridCardGraphProps): JSX.Element {
|
||||||
const { isAddWidget } = useSelector<AppState, DashboardReducer>(
|
const { isAddWidget } = useSelector<AppState, DashboardReducer>(
|
||||||
(state) => state.dashboards,
|
(state) => state.dashboards,
|
||||||
@ -70,11 +71,12 @@ function GridCardGraph({
|
|||||||
{
|
{
|
||||||
queryKey: [
|
queryKey: [
|
||||||
`GetMetricsQueryRange-${widget?.timePreferance}-${globalSelectedInterval}-${widget?.id}`,
|
`GetMetricsQueryRange-${widget?.timePreferance}-${globalSelectedInterval}-${widget?.id}`,
|
||||||
widget,
|
|
||||||
maxTime,
|
maxTime,
|
||||||
minTime,
|
minTime,
|
||||||
globalSelectedInterval,
|
globalSelectedInterval,
|
||||||
variables,
|
variables,
|
||||||
|
widget?.query,
|
||||||
|
widget?.panelTypes,
|
||||||
],
|
],
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
enabled: isGraphVisible && !isEmptyWidget && isQueryEnabled && !isAddWidget,
|
enabled: isGraphVisible && !isEmptyWidget && isQueryEnabled && !isAddWidget,
|
||||||
@ -105,7 +107,7 @@ function GridCardGraph({
|
|||||||
return <Spinner height="20vh" tip="Loading..." />;
|
return <Spinner height="20vh" tip="Loading..." />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queryResponse.isError && !isEmptyLayout) {
|
if ((queryResponse.isError && !isEmptyLayout) || !isQueryEnabled) {
|
||||||
return (
|
return (
|
||||||
<span ref={graphRef}>
|
<span ref={graphRef}>
|
||||||
{!isEmpty(widget) && prevChartDataSetRef && (
|
{!isEmpty(widget) && prevChartDataSetRef && (
|
||||||
@ -120,6 +122,7 @@ function GridCardGraph({
|
|||||||
yAxisUnit={yAxisUnit}
|
yAxisUnit={yAxisUnit}
|
||||||
layout={layout}
|
layout={layout}
|
||||||
setLayout={setLayout}
|
setLayout={setLayout}
|
||||||
|
threshold={threshold}
|
||||||
headerMenuList={headerMenuList}
|
headerMenuList={headerMenuList}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -141,6 +144,7 @@ function GridCardGraph({
|
|||||||
yAxisUnit={yAxisUnit}
|
yAxisUnit={yAxisUnit}
|
||||||
layout={layout}
|
layout={layout}
|
||||||
setLayout={setLayout}
|
setLayout={setLayout}
|
||||||
|
threshold={threshold}
|
||||||
headerMenuList={headerMenuList}
|
headerMenuList={headerMenuList}
|
||||||
onClickHandler={onClickHandler}
|
onClickHandler={onClickHandler}
|
||||||
/>
|
/>
|
||||||
@ -161,6 +165,7 @@ function GridCardGraph({
|
|||||||
name={name}
|
name={name}
|
||||||
yAxisUnit={yAxisUnit}
|
yAxisUnit={yAxisUnit}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
|
threshold={threshold}
|
||||||
headerMenuList={headerMenuList}
|
headerMenuList={headerMenuList}
|
||||||
onClickHandler={onClickHandler}
|
onClickHandler={onClickHandler}
|
||||||
/>
|
/>
|
||||||
@ -175,6 +180,7 @@ GridCardGraph.defaultProps = {
|
|||||||
onDragSelect: undefined,
|
onDragSelect: undefined,
|
||||||
onClickHandler: undefined,
|
onClickHandler: undefined,
|
||||||
isQueryEnabled: true,
|
isQueryEnabled: true,
|
||||||
|
threshold: undefined,
|
||||||
headerMenuList: [MenuItemKeys.View],
|
headerMenuList: [MenuItemKeys.View],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ChartData } from 'chart.js';
|
import { ChartData } from 'chart.js';
|
||||||
import { GraphOnClickHandler, ToggleGraphProps } from 'components/Graph/types';
|
import { GraphOnClickHandler, ToggleGraphProps } from 'components/Graph/types';
|
||||||
import { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
import { Dispatch, MutableRefObject, ReactNode, SetStateAction } from 'react';
|
||||||
import { Layout } from 'react-grid-layout';
|
import { Layout } from 'react-grid-layout';
|
||||||
import { UseQueryResult } from 'react-query';
|
import { UseQueryResult } from 'react-query';
|
||||||
import { DeleteWidgetProps } from 'store/actions/dashboard/deleteWidget';
|
import { DeleteWidgetProps } from 'store/actions/dashboard/deleteWidget';
|
||||||
@ -39,6 +39,7 @@ export interface WidgetGraphComponentProps extends DispatchProps {
|
|||||||
setLayout?: Dispatch<SetStateAction<LayoutProps[]>>;
|
setLayout?: Dispatch<SetStateAction<LayoutProps[]>>;
|
||||||
onDragSelect?: (start: number, end: number) => void;
|
onDragSelect?: (start: number, end: number) => void;
|
||||||
onClickHandler?: GraphOnClickHandler;
|
onClickHandler?: GraphOnClickHandler;
|
||||||
|
threshold?: ReactNode;
|
||||||
headerMenuList: MenuItemKeys[];
|
headerMenuList: MenuItemKeys[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +51,7 @@ export interface GridCardGraphProps {
|
|||||||
setLayout?: Dispatch<SetStateAction<LayoutProps[]>>;
|
setLayout?: Dispatch<SetStateAction<LayoutProps[]>>;
|
||||||
onDragSelect?: (start: number, end: number) => void;
|
onDragSelect?: (start: number, end: number) => void;
|
||||||
onClickHandler?: GraphOnClickHandler;
|
onClickHandler?: GraphOnClickHandler;
|
||||||
|
threshold?: ReactNode;
|
||||||
headerMenuList?: WidgetGraphComponentProps['headerMenuList'];
|
headerMenuList?: WidgetGraphComponentProps['headerMenuList'];
|
||||||
isQueryEnabled: boolean;
|
isQueryEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DisplayThresholdContainer,
|
||||||
|
TypographHeading,
|
||||||
|
Typography,
|
||||||
|
} from './styles';
|
||||||
|
import { DisplayThresholdProps } from './types';
|
||||||
|
|
||||||
|
function DisplayThreshold({ threshold }: DisplayThresholdProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<DisplayThresholdContainer>
|
||||||
|
<TypographHeading>Threshold </TypographHeading>
|
||||||
|
<Typography>{threshold || <InfoCircleOutlined />}</Typography>
|
||||||
|
</DisplayThresholdContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DisplayThreshold;
|
@ -12,7 +12,7 @@ import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
|||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { ReactNode, useCallback, useMemo, useState } from 'react';
|
||||||
import { UseQueryResult } from 'react-query';
|
import { UseQueryResult } from 'react-query';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -32,12 +32,14 @@ import {
|
|||||||
ArrowContainer,
|
ArrowContainer,
|
||||||
HeaderContainer,
|
HeaderContainer,
|
||||||
HeaderContentContainer,
|
HeaderContentContainer,
|
||||||
|
ThesholdContainer,
|
||||||
|
WidgetHeaderContainer,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
import { MenuItem } from './types';
|
import { MenuItem } from './types';
|
||||||
import { generateMenuList, isTWidgetOptions } from './utils';
|
import { generateMenuList, isTWidgetOptions } from './utils';
|
||||||
|
|
||||||
interface IWidgetHeaderProps {
|
interface IWidgetHeaderProps {
|
||||||
title: string;
|
title: ReactNode;
|
||||||
widget: Widgets;
|
widget: Widgets;
|
||||||
onView: VoidFunction;
|
onView: VoidFunction;
|
||||||
onDelete?: VoidFunction;
|
onDelete?: VoidFunction;
|
||||||
@ -47,6 +49,7 @@ interface IWidgetHeaderProps {
|
|||||||
SuccessResponse<MetricRangePayloadProps> | ErrorResponse
|
SuccessResponse<MetricRangePayloadProps> | ErrorResponse
|
||||||
>;
|
>;
|
||||||
errorMessage: string | undefined;
|
errorMessage: string | undefined;
|
||||||
|
threshold?: ReactNode;
|
||||||
headerMenuList?: MenuItemKeys[];
|
headerMenuList?: MenuItemKeys[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +62,7 @@ function WidgetHeader({
|
|||||||
parentHover,
|
parentHover,
|
||||||
queryResponse,
|
queryResponse,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
|
threshold,
|
||||||
headerMenuList,
|
headerMenuList,
|
||||||
}: IWidgetHeaderProps): JSX.Element {
|
}: IWidgetHeaderProps): JSX.Element {
|
||||||
const [localHover, setLocalHover] = useState(false);
|
const [localHover, setLocalHover] = useState(false);
|
||||||
@ -171,7 +175,7 @@ function WidgetHeader({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<WidgetHeaderContainer>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
destroyPopupOnHide
|
destroyPopupOnHide
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
@ -196,6 +200,7 @@ function WidgetHeader({
|
|||||||
</HeaderContentContainer>
|
</HeaderContentContainer>
|
||||||
</HeaderContainer>
|
</HeaderContainer>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
<ThesholdContainer>{threshold}</ThesholdContainer>
|
||||||
{queryResponse.isFetching && !queryResponse.isError && (
|
{queryResponse.isFetching && !queryResponse.isError && (
|
||||||
<Spinner height="5vh" style={spinnerStyles} />
|
<Spinner height="5vh" style={spinnerStyles} />
|
||||||
)}
|
)}
|
||||||
@ -204,13 +209,14 @@ function WidgetHeader({
|
|||||||
<ExclamationCircleOutlined style={tooltipStyles} />
|
<ExclamationCircleOutlined style={tooltipStyles} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</WidgetHeaderContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
WidgetHeader.defaultProps = {
|
WidgetHeader.defaultProps = {
|
||||||
onDelete: undefined,
|
onDelete: undefined,
|
||||||
onClone: undefined,
|
onClone: undefined,
|
||||||
|
threshold: undefined,
|
||||||
headerMenuList: [MenuItemKeys.View],
|
headerMenuList: [MenuItemKeys.View],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { grey } from '@ant-design/colors';
|
import { grey } from '@ant-design/colors';
|
||||||
|
import { Typography as TypographyComponent } from 'antd';
|
||||||
|
import { themeColors } from 'constants/theme';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const HeaderContainer = styled.div<{ hover: boolean }>`
|
export const HeaderContainer = styled.div<{ hover: boolean }>`
|
||||||
@ -24,3 +26,34 @@ export const ArrowContainer = styled.span<{ hover: boolean }>`
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: -1rem;
|
right: -1rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const ThesholdContainer = styled.span`
|
||||||
|
margin-top: -0.3rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DisplayThresholdContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: auto;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const WidgetHeaderContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Typography = styled(TypographyComponent)`
|
||||||
|
&&& {
|
||||||
|
color: ${themeColors.white};
|
||||||
|
width: auto;
|
||||||
|
margin-left: 0.2rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TypographHeading = styled(TypographyComponent)`
|
||||||
|
&&& {
|
||||||
|
color: ${grey[2]};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -10,3 +10,7 @@ export interface MenuItem {
|
|||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
danger?: boolean;
|
danger?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DisplayThresholdProps {
|
||||||
|
threshold: ReactNode;
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
} from 'components/Graph/types';
|
} from 'components/Graph/types';
|
||||||
import { GridTableComponentProps } from 'container/GridTableComponent/types';
|
import { GridTableComponentProps } from 'container/GridTableComponent/types';
|
||||||
import { GridValueComponentProps } from 'container/GridValueComponent/types';
|
import { GridValueComponentProps } from 'container/GridValueComponent/types';
|
||||||
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { QueryDataV3 } from 'types/api/widgets/getQuery';
|
import { QueryDataV3 } from 'types/api/widgets/getQuery';
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ import { PANEL_TYPES } from '../../constants/queryBuilder';
|
|||||||
export type GridPanelSwitchProps = {
|
export type GridPanelSwitchProps = {
|
||||||
panelType: PANEL_TYPES;
|
panelType: PANEL_TYPES;
|
||||||
data: ChartData;
|
data: ChartData;
|
||||||
title?: string;
|
title?: Widgets['title'];
|
||||||
opacity?: string;
|
opacity?: string;
|
||||||
isStacked?: boolean;
|
isStacked?: boolean;
|
||||||
onClickHandler?: GraphOnClickHandler;
|
onClickHandler?: GraphOnClickHandler;
|
||||||
|
12
frontend/src/container/GridPanelSwitch/utils.ts
Normal file
12
frontend/src/container/GridPanelSwitch/utils.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export const generateGridTitle = (title: ReactNode): string => {
|
||||||
|
if (React.isValidElement(title)) {
|
||||||
|
return Array.isArray(title.props.children)
|
||||||
|
? title.props.children
|
||||||
|
.map((child: ReactNode) => (typeof child === 'string' ? child : ''))
|
||||||
|
.join(' ')
|
||||||
|
: title.props.children;
|
||||||
|
}
|
||||||
|
return title?.toString() || '';
|
||||||
|
};
|
@ -1,7 +1,8 @@
|
|||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||||
import ValueGraph from 'components/ValueGraph';
|
import ValueGraph from 'components/ValueGraph';
|
||||||
import { memo } from 'react';
|
import { generateGridTitle } from 'container/GridPanelSwitch/utils';
|
||||||
|
import { memo, useMemo } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { TitleContainer, ValueContainer } from './styles';
|
import { TitleContainer, ValueContainer } from './styles';
|
||||||
@ -15,6 +16,7 @@ function GridValueComponent({
|
|||||||
const value = (((data.datasets[0] || []).data || [])[0] || 0) as number;
|
const value = (((data.datasets[0] || []).data || [])[0] || 0) as number;
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const gridTitle = useMemo(() => generateGridTitle(title), [title]);
|
||||||
|
|
||||||
const isDashboardPage = location.pathname.split('/').length === 3;
|
const isDashboardPage = location.pathname.split('/').length === 3;
|
||||||
|
|
||||||
@ -29,7 +31,7 @@ function GridValueComponent({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TitleContainer isDashboardPage={isDashboardPage}>
|
<TitleContainer isDashboardPage={isDashboardPage}>
|
||||||
<Typography>{title}</Typography>
|
<Typography>{gridTitle}</Typography>
|
||||||
</TitleContainer>
|
</TitleContainer>
|
||||||
<ValueContainer isDashboardPage={isDashboardPage}>
|
<ValueContainer isDashboardPage={isDashboardPage}>
|
||||||
<ValueGraph
|
<ValueGraph
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { ChartData } from 'chart.js';
|
import { ChartData } from 'chart.js';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
export type GridValueComponentProps = {
|
export type GridValueComponentProps = {
|
||||||
data: ChartData;
|
data: ChartData;
|
||||||
title?: string;
|
title?: ReactNode;
|
||||||
yAxisUnit?: string;
|
yAxisUnit?: string;
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,13 @@ import {
|
|||||||
QUERYNAME_AND_EXPRESSION,
|
QUERYNAME_AND_EXPRESSION,
|
||||||
WidgetKeys,
|
WidgetKeys,
|
||||||
} from '../constant';
|
} from '../constant';
|
||||||
import { LatencyProps, OperationPerSecProps } from '../Tabs/types';
|
import {
|
||||||
|
ApDexMetricsQueryBuilderQueriesProps,
|
||||||
|
ApDexProps,
|
||||||
|
LatencyProps,
|
||||||
|
OperationPerSecProps,
|
||||||
|
} from '../Tabs/types';
|
||||||
|
import { convertMilSecToNanoSec, getNearestHighestBucketValue } from '../utils';
|
||||||
import {
|
import {
|
||||||
getQueryBuilderQueries,
|
getQueryBuilderQueries,
|
||||||
getQueryBuilderQuerieswithFormula,
|
getQueryBuilderQuerieswithFormula,
|
||||||
@ -85,6 +91,365 @@ export const latency = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const apDexTracesQueryBuilderQueries = ({
|
||||||
|
servicename,
|
||||||
|
tagFilterItems,
|
||||||
|
topLevelOperationsRoute,
|
||||||
|
threashold,
|
||||||
|
}: ApDexProps): QueryBuilderData => {
|
||||||
|
const autoCompleteDataA: BaseAutocompleteData = {
|
||||||
|
dataType: DataType.FLOAT64,
|
||||||
|
isColumn: true,
|
||||||
|
key: '',
|
||||||
|
type: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const autoCompleteDataB: BaseAutocompleteData = {
|
||||||
|
dataType: DataType.FLOAT64,
|
||||||
|
isColumn: true,
|
||||||
|
key: '',
|
||||||
|
type: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const autoCompleteDataC: BaseAutocompleteData = {
|
||||||
|
dataType: DataType.FLOAT64,
|
||||||
|
isColumn: true,
|
||||||
|
key: '',
|
||||||
|
type: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterItemA: TagFilterItem[] = [
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.ServiceName,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: true,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['='],
|
||||||
|
value: servicename,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.Name,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: true,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [...topLevelOperationsRoute],
|
||||||
|
},
|
||||||
|
...tagFilterItems,
|
||||||
|
];
|
||||||
|
|
||||||
|
const filterItemB: TagFilterItem[] = [
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.HasError,
|
||||||
|
dataType: DataType.BOOL,
|
||||||
|
isColumn: true,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['='],
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.DurationNano,
|
||||||
|
dataType: DataType.FLOAT64,
|
||||||
|
isColumn: true,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['<='],
|
||||||
|
value: convertMilSecToNanoSec(threashold),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.ServiceName,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: true,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['='],
|
||||||
|
value: servicename,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.Name,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: true,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [...topLevelOperationsRoute],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const filterItemC: TagFilterItem[] = [
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.DurationNano,
|
||||||
|
dataType: DataType.FLOAT64,
|
||||||
|
isColumn: true,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['<='],
|
||||||
|
value: convertMilSecToNanoSec(threashold * 4),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.HasError,
|
||||||
|
dataType: DataType.BOOL,
|
||||||
|
isColumn: true,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['='],
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.ServiceName,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: true,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['='],
|
||||||
|
value: servicename,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.Name,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: true,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [...topLevelOperationsRoute],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const autocompleteData = [
|
||||||
|
autoCompleteDataA,
|
||||||
|
autoCompleteDataB,
|
||||||
|
autoCompleteDataC,
|
||||||
|
];
|
||||||
|
const additionalItems = [filterItemA, filterItemB, filterItemC];
|
||||||
|
const legends = [GraphTitle.APDEX];
|
||||||
|
const disabled = Array(3).fill(true);
|
||||||
|
const expressions = [FORMULA.APDEX_TRACES];
|
||||||
|
const legendFormulas = [GraphTitle.APDEX];
|
||||||
|
const aggregateOperators = [
|
||||||
|
MetricAggregateOperator.COUNT,
|
||||||
|
MetricAggregateOperator.COUNT,
|
||||||
|
MetricAggregateOperator.COUNT,
|
||||||
|
];
|
||||||
|
const dataSource = DataSource.TRACES;
|
||||||
|
|
||||||
|
return getQueryBuilderQuerieswithFormula({
|
||||||
|
autocompleteData,
|
||||||
|
additionalItems,
|
||||||
|
legends,
|
||||||
|
disabled,
|
||||||
|
expressions,
|
||||||
|
legendFormulas,
|
||||||
|
aggregateOperators,
|
||||||
|
dataSource,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const apDexMetricsQueryBuilderQueries = ({
|
||||||
|
servicename,
|
||||||
|
tagFilterItems,
|
||||||
|
topLevelOperationsRoute,
|
||||||
|
threashold,
|
||||||
|
delta,
|
||||||
|
metricsBuckets,
|
||||||
|
}: ApDexMetricsQueryBuilderQueriesProps): QueryBuilderData => {
|
||||||
|
const autoCompleteDataA: BaseAutocompleteData = {
|
||||||
|
key: WidgetKeys.SignozLatencyCount,
|
||||||
|
dataType: DataType.FLOAT64,
|
||||||
|
isColumn: true,
|
||||||
|
type: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const autoCompleteDataB: BaseAutocompleteData = {
|
||||||
|
key: WidgetKeys.Signoz_latency_bucket,
|
||||||
|
dataType: DataType.FLOAT64,
|
||||||
|
isColumn: true,
|
||||||
|
type: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const autoCompleteDataC: BaseAutocompleteData = {
|
||||||
|
key: WidgetKeys.Signoz_latency_bucket,
|
||||||
|
dataType: DataType.FLOAT64,
|
||||||
|
isColumn: true,
|
||||||
|
type: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterItemA: TagFilterItem[] = [
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.Service_name,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['='],
|
||||||
|
value: servicename,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.Operation,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [...topLevelOperationsRoute],
|
||||||
|
},
|
||||||
|
...tagFilterItems,
|
||||||
|
];
|
||||||
|
|
||||||
|
const filterItemB: TagFilterItem[] = [
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.StatusCode,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['='],
|
||||||
|
value: 'STATUS_CODE_UNSET',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.Le,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['='],
|
||||||
|
value: getNearestHighestBucketValue(threashold * 1000, metricsBuckets),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.Service_name,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['='],
|
||||||
|
value: servicename,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.Operation,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [...topLevelOperationsRoute],
|
||||||
|
},
|
||||||
|
...tagFilterItems,
|
||||||
|
];
|
||||||
|
|
||||||
|
const filterItemC: TagFilterItem[] = [
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.Le,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['='],
|
||||||
|
value: getNearestHighestBucketValue(threashold * 1000 * 4, metricsBuckets),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.StatusCode,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['='],
|
||||||
|
value: 'STATUS_CODE_UNSET',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.Service_name,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS['='],
|
||||||
|
value: servicename,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
key: WidgetKeys.Operation,
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [...topLevelOperationsRoute],
|
||||||
|
},
|
||||||
|
...tagFilterItems,
|
||||||
|
];
|
||||||
|
|
||||||
|
const autocompleteData = [
|
||||||
|
autoCompleteDataA,
|
||||||
|
autoCompleteDataB,
|
||||||
|
autoCompleteDataC,
|
||||||
|
];
|
||||||
|
|
||||||
|
const additionalItems = [filterItemA, filterItemB, filterItemC];
|
||||||
|
const legends = [GraphTitle.APDEX];
|
||||||
|
const disabled = Array(3).fill(true);
|
||||||
|
const expressions = delta
|
||||||
|
? [FORMULA.APDEX_DELTA_SPAN_METRICS]
|
||||||
|
: [FORMULA.APDEX_CUMULATIVE_SPAN_METRICS];
|
||||||
|
const legendFormulas = [GraphTitle.APDEX];
|
||||||
|
const aggregateOperators = [
|
||||||
|
MetricAggregateOperator.SUM_RATE,
|
||||||
|
MetricAggregateOperator.SUM_RATE,
|
||||||
|
MetricAggregateOperator.SUM_RATE,
|
||||||
|
];
|
||||||
|
const dataSource = DataSource.METRICS;
|
||||||
|
|
||||||
|
return getQueryBuilderQuerieswithFormula({
|
||||||
|
autocompleteData,
|
||||||
|
additionalItems,
|
||||||
|
legends,
|
||||||
|
disabled,
|
||||||
|
expressions,
|
||||||
|
legendFormulas,
|
||||||
|
aggregateOperators,
|
||||||
|
dataSource,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const operationPerSec = ({
|
export const operationPerSec = ({
|
||||||
servicename,
|
servicename,
|
||||||
tagFilterItems,
|
tagFilterItems,
|
||||||
|
@ -32,7 +32,14 @@ import {
|
|||||||
errorPercentage,
|
errorPercentage,
|
||||||
operationPerSec,
|
operationPerSec,
|
||||||
} from '../MetricsPageQueries/OverviewQueries';
|
} from '../MetricsPageQueries/OverviewQueries';
|
||||||
import { Card, Col, Row } from '../styles';
|
import {
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
ColApDexContainer,
|
||||||
|
ColErrorContainer,
|
||||||
|
Row,
|
||||||
|
} from '../styles';
|
||||||
|
import ApDex from './Overview/ApDex';
|
||||||
import ServiceOverview from './Overview/ServiceOverview';
|
import ServiceOverview from './Overview/ServiceOverview';
|
||||||
import TopLevelOperation from './Overview/TopLevelOperations';
|
import TopLevelOperation from './Overview/TopLevelOperations';
|
||||||
import TopOperation from './Overview/TopOperation';
|
import TopOperation from './Overview/TopOperation';
|
||||||
@ -160,7 +167,7 @@ function Application(): JSX.Element {
|
|||||||
[dispatch],
|
[dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onErrorTrackHandler = (timestamp: number): void => {
|
const onErrorTrackHandler = (timestamp: number): (() => void) => (): void => {
|
||||||
const currentTime = timestamp;
|
const currentTime = timestamp;
|
||||||
const tPlusOne = timestamp + 60 * 1000;
|
const tPlusOne = timestamp + 60 * 1000;
|
||||||
|
|
||||||
@ -190,6 +197,7 @@ function Application(): JSX.Element {
|
|||||||
selectedTimeStamp={selectedTimeStamp}
|
selectedTimeStamp={selectedTimeStamp}
|
||||||
selectedTraceTags={selectedTraceTags}
|
selectedTraceTags={selectedTraceTags}
|
||||||
topLevelOperationsRoute={topLevelOperationsRoute}
|
topLevelOperationsRoute={topLevelOperationsRoute}
|
||||||
|
topLevelOperationsLoading={topLevelOperationsLoading}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
@ -221,28 +229,48 @@ function Application(): JSX.Element {
|
|||||||
</Row>
|
</Row>
|
||||||
<Row gutter={24}>
|
<Row gutter={24}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Button
|
<ColApDexContainer>
|
||||||
type="default"
|
<Button
|
||||||
size="small"
|
type="default"
|
||||||
id="Error_button"
|
size="small"
|
||||||
onClick={(): void => {
|
id="ApDex_button"
|
||||||
onErrorTrackHandler(selectedTimeStamp);
|
onClick={onViewTracePopupClick({
|
||||||
}}
|
servicename,
|
||||||
>
|
selectedTraceTags,
|
||||||
View Traces
|
timestamp: selectedTimeStamp,
|
||||||
</Button>
|
})}
|
||||||
|
>
|
||||||
|
View Traces
|
||||||
|
</Button>
|
||||||
|
<ApDex
|
||||||
|
handleGraphClick={handleGraphClick}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
|
topLevelOperationsRoute={topLevelOperationsRoute}
|
||||||
|
tagFilterItems={tagFilterItems}
|
||||||
|
/>
|
||||||
|
</ColApDexContainer>
|
||||||
|
<ColErrorContainer>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
id="Error_button"
|
||||||
|
onClick={onErrorTrackHandler(selectedTimeStamp)}
|
||||||
|
>
|
||||||
|
View Traces
|
||||||
|
</Button>
|
||||||
|
|
||||||
<TopLevelOperation
|
<TopLevelOperation
|
||||||
handleGraphClick={handleGraphClick}
|
handleGraphClick={handleGraphClick}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
topLevelOperationsError={topLevelOperationsError}
|
topLevelOperationsError={topLevelOperationsError}
|
||||||
topLevelOperationsLoading={topLevelOperationsLoading}
|
topLevelOperationsLoading={topLevelOperationsLoading}
|
||||||
topLevelOperationsIsError={topLevelOperationsIsError}
|
topLevelOperationsIsError={topLevelOperationsIsError}
|
||||||
name="error_percentage_%"
|
name="error_percentage_%"
|
||||||
widget={errorPercentageWidget}
|
widget={errorPercentageWidget}
|
||||||
yAxisUnit="%"
|
yAxisUnit="%"
|
||||||
opName="Error"
|
opName="Error"
|
||||||
/>
|
/>
|
||||||
|
</ColErrorContainer>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
import { Space, Typography } from 'antd';
|
||||||
|
import TextToolTip from 'components/TextToolTip';
|
||||||
|
import {
|
||||||
|
apDexToolTipText,
|
||||||
|
apDexToolTipUrl,
|
||||||
|
apDexToolTipUrlText,
|
||||||
|
} from 'constants/apDex';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import Graph from 'container/GridGraphLayout/Graph';
|
||||||
|
import DisplayThreshold from 'container/GridGraphLayout/WidgetHeader/DisplayThreshold';
|
||||||
|
import { GraphTitle } from 'container/MetricsApplication/constant';
|
||||||
|
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
||||||
|
import { apDexMetricsQueryBuilderQueries } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries';
|
||||||
|
import { ReactNode, useMemo } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
import { IServiceName } from '../../types';
|
||||||
|
import { ApDexMetricsProps } from './types';
|
||||||
|
|
||||||
|
function ApDexMetrics({
|
||||||
|
delta,
|
||||||
|
metricsBuckets,
|
||||||
|
thresholdValue,
|
||||||
|
onDragSelect,
|
||||||
|
tagFilterItems,
|
||||||
|
topLevelOperationsRoute,
|
||||||
|
handleGraphClick,
|
||||||
|
}: ApDexMetricsProps): JSX.Element {
|
||||||
|
const { servicename } = useParams<IServiceName>();
|
||||||
|
|
||||||
|
const apDexMetricsWidget = useMemo(
|
||||||
|
() =>
|
||||||
|
getWidgetQueryBuilder({
|
||||||
|
query: {
|
||||||
|
queryType: EQueryType.QUERY_BUILDER,
|
||||||
|
promql: [],
|
||||||
|
builder: apDexMetricsQueryBuilderQueries({
|
||||||
|
servicename,
|
||||||
|
tagFilterItems,
|
||||||
|
topLevelOperationsRoute,
|
||||||
|
threashold: thresholdValue || 0,
|
||||||
|
delta: delta || false,
|
||||||
|
metricsBuckets: metricsBuckets || [],
|
||||||
|
}),
|
||||||
|
clickhouse_sql: [],
|
||||||
|
id: uuid(),
|
||||||
|
},
|
||||||
|
title: (
|
||||||
|
<Space>
|
||||||
|
<Typography>{GraphTitle.APDEX}</Typography>
|
||||||
|
<TextToolTip
|
||||||
|
text={apDexToolTipText}
|
||||||
|
url={apDexToolTipUrl}
|
||||||
|
useFilledIcon={false}
|
||||||
|
urlText={apDexToolTipUrlText}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
delta,
|
||||||
|
metricsBuckets,
|
||||||
|
servicename,
|
||||||
|
tagFilterItems,
|
||||||
|
thresholdValue,
|
||||||
|
topLevelOperationsRoute,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const threshold: ReactNode = useMemo(() => {
|
||||||
|
if (thresholdValue) return <DisplayThreshold threshold={thresholdValue} />;
|
||||||
|
return null;
|
||||||
|
}, [thresholdValue]);
|
||||||
|
|
||||||
|
const isQueryEnabled =
|
||||||
|
topLevelOperationsRoute.length > 0 &&
|
||||||
|
metricsBuckets &&
|
||||||
|
metricsBuckets?.length > 0 &&
|
||||||
|
delta !== undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Graph
|
||||||
|
name="apdex"
|
||||||
|
widget={apDexMetricsWidget}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
|
onClickHandler={handleGraphClick('ApDex')}
|
||||||
|
yAxisUnit=""
|
||||||
|
threshold={threshold}
|
||||||
|
isQueryEnabled={isQueryEnabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApDexMetrics.defaultProps = {
|
||||||
|
delta: undefined,
|
||||||
|
le: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApDexMetrics;
|
@ -0,0 +1,36 @@
|
|||||||
|
import Spinner from 'components/Spinner';
|
||||||
|
import { useGetMetricMeta } from 'hooks/apDex/useGetMetricMeta';
|
||||||
|
import useErrorNotification from 'hooks/useErrorNotification';
|
||||||
|
|
||||||
|
import ApDexMetrics from './ApDexMetrics';
|
||||||
|
import { metricMeta } from './constants';
|
||||||
|
import { ApDexDataSwitcherProps } from './types';
|
||||||
|
|
||||||
|
function ApDexMetricsApplication({
|
||||||
|
handleGraphClick,
|
||||||
|
onDragSelect,
|
||||||
|
tagFilterItems,
|
||||||
|
topLevelOperationsRoute,
|
||||||
|
thresholdValue,
|
||||||
|
}: ApDexDataSwitcherProps): JSX.Element {
|
||||||
|
const { data, isLoading, error } = useGetMetricMeta(metricMeta);
|
||||||
|
useErrorNotification(error);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Spinner height="40vh" tip="Loading..." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ApDexMetrics
|
||||||
|
handleGraphClick={handleGraphClick}
|
||||||
|
delta={data?.data.delta}
|
||||||
|
metricsBuckets={data?.data.le}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
|
topLevelOperationsRoute={topLevelOperationsRoute}
|
||||||
|
tagFilterItems={tagFilterItems}
|
||||||
|
thresholdValue={thresholdValue}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApDexMetricsApplication;
|
@ -0,0 +1,62 @@
|
|||||||
|
// This component is not been used in the application as we support only metrics for ApDex as of now.
|
||||||
|
// This component is been kept for future reference.
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import Graph from 'container/GridGraphLayout/Graph';
|
||||||
|
import { GraphTitle } from 'container/MetricsApplication/constant';
|
||||||
|
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
||||||
|
import { apDexTracesQueryBuilderQueries } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
import { IServiceName } from '../../types';
|
||||||
|
import { ApDexDataSwitcherProps } from './types';
|
||||||
|
|
||||||
|
function ApDexTraces({
|
||||||
|
handleGraphClick,
|
||||||
|
onDragSelect,
|
||||||
|
topLevelOperationsRoute,
|
||||||
|
tagFilterItems,
|
||||||
|
thresholdValue,
|
||||||
|
}: ApDexDataSwitcherProps): JSX.Element {
|
||||||
|
const { servicename } = useParams<IServiceName>();
|
||||||
|
|
||||||
|
const apDexTracesWidget = useMemo(
|
||||||
|
() =>
|
||||||
|
getWidgetQueryBuilder({
|
||||||
|
query: {
|
||||||
|
queryType: EQueryType.QUERY_BUILDER,
|
||||||
|
promql: [],
|
||||||
|
builder: apDexTracesQueryBuilderQueries({
|
||||||
|
servicename,
|
||||||
|
tagFilterItems,
|
||||||
|
topLevelOperationsRoute,
|
||||||
|
threashold: thresholdValue || 0,
|
||||||
|
}),
|
||||||
|
clickhouse_sql: [],
|
||||||
|
id: uuid(),
|
||||||
|
},
|
||||||
|
title: GraphTitle.APDEX,
|
||||||
|
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||||
|
}),
|
||||||
|
[servicename, tagFilterItems, thresholdValue, topLevelOperationsRoute],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isQueryEnabled =
|
||||||
|
topLevelOperationsRoute.length > 0 && thresholdValue !== undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Graph
|
||||||
|
name="apdex"
|
||||||
|
widget={apDexTracesWidget}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
|
onClickHandler={handleGraphClick('ApDex')}
|
||||||
|
yAxisUnit=""
|
||||||
|
threshold={thresholdValue}
|
||||||
|
isQueryEnabled={isQueryEnabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApDexTraces;
|
@ -0,0 +1 @@
|
|||||||
|
export const metricMeta = 'signoz_latency_bucket';
|
@ -0,0 +1,47 @@
|
|||||||
|
import Spinner from 'components/Spinner';
|
||||||
|
import { Card, GraphContainer } from 'container/MetricsApplication/styles';
|
||||||
|
import { useGetApDexSettings } from 'hooks/apDex/useGetApDexSettings';
|
||||||
|
import useErrorNotification from 'hooks/useErrorNotification';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { IServiceName } from '../../types';
|
||||||
|
import ApDexMetricsApplication from './ApDexMetricsApplication';
|
||||||
|
import { ApDexApplicationProps } from './types';
|
||||||
|
|
||||||
|
function ApDexApplication({
|
||||||
|
handleGraphClick,
|
||||||
|
onDragSelect,
|
||||||
|
topLevelOperationsRoute,
|
||||||
|
tagFilterItems,
|
||||||
|
}: ApDexApplicationProps): JSX.Element {
|
||||||
|
const { servicename } = useParams<IServiceName>();
|
||||||
|
const { data, isLoading, error, isRefetching } = useGetApDexSettings(
|
||||||
|
servicename,
|
||||||
|
);
|
||||||
|
useErrorNotification(error);
|
||||||
|
|
||||||
|
if (isLoading || isRefetching) {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<Spinner height="40vh" tip="Loading..." />
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<GraphContainer>
|
||||||
|
<ApDexMetricsApplication
|
||||||
|
handleGraphClick={handleGraphClick}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
|
topLevelOperationsRoute={topLevelOperationsRoute}
|
||||||
|
tagFilterItems={tagFilterItems}
|
||||||
|
thresholdValue={data?.data[0].threshold}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(ApDexApplication);
|
@ -0,0 +1,19 @@
|
|||||||
|
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
import { ClickHandlerType } from '../../Overview';
|
||||||
|
|
||||||
|
export interface ApDexApplicationProps {
|
||||||
|
handleGraphClick: (type: string) => ClickHandlerType;
|
||||||
|
onDragSelect: (start: number, end: number) => void;
|
||||||
|
topLevelOperationsRoute: string[];
|
||||||
|
tagFilterItems: TagFilterItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApDexDataSwitcherProps extends ApDexApplicationProps {
|
||||||
|
thresholdValue?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApDexMetricsProps extends ApDexDataSwitcherProps {
|
||||||
|
delta?: boolean;
|
||||||
|
metricsBuckets?: number[];
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import Spinner from 'components/Spinner';
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import Graph from 'container/GridGraphLayout/Graph/';
|
import Graph from 'container/GridGraphLayout/Graph/';
|
||||||
@ -24,6 +25,7 @@ function ServiceOverview({
|
|||||||
selectedTraceTags,
|
selectedTraceTags,
|
||||||
selectedTimeStamp,
|
selectedTimeStamp,
|
||||||
topLevelOperationsRoute,
|
topLevelOperationsRoute,
|
||||||
|
topLevelOperationsLoading,
|
||||||
}: ServiceOverviewProps): JSX.Element {
|
}: ServiceOverviewProps): JSX.Element {
|
||||||
const { servicename } = useParams<IServiceName>();
|
const { servicename } = useParams<IServiceName>();
|
||||||
|
|
||||||
@ -63,6 +65,14 @@ function ServiceOverview({
|
|||||||
|
|
||||||
const isQueryEnabled = topLevelOperationsRoute.length > 0;
|
const isQueryEnabled = topLevelOperationsRoute.length > 0;
|
||||||
|
|
||||||
|
if (topLevelOperationsLoading) {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<Spinner height="40vh" tip="Loading..." />
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
@ -99,6 +109,7 @@ interface ServiceOverviewProps {
|
|||||||
onDragSelect: (start: number, end: number) => void;
|
onDragSelect: (start: number, end: number) => void;
|
||||||
handleGraphClick: (type: string) => ClickHandlerType;
|
handleGraphClick: (type: string) => ClickHandlerType;
|
||||||
topLevelOperationsRoute: string[];
|
topLevelOperationsRoute: string[];
|
||||||
|
topLevelOperationsLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ServiceOverview;
|
export default ServiceOverview;
|
||||||
|
@ -56,7 +56,19 @@ export interface LatencyProps {
|
|||||||
topLevelOperationsRoute: string[];
|
topLevelOperationsRoute: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApDexProps {
|
||||||
|
servicename: IServiceName['servicename'];
|
||||||
|
tagFilterItems: TagFilterItem[];
|
||||||
|
topLevelOperationsRoute: string[];
|
||||||
|
threashold: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TableRendererProps {
|
export interface TableRendererProps {
|
||||||
columnName: string;
|
columnName: string;
|
||||||
renderFunction: (record: RowData) => ReactNode;
|
renderFunction: (record: RowData) => ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApDexMetricsQueryBuilderQueriesProps extends ApDexProps {
|
||||||
|
delta: boolean;
|
||||||
|
metricsBuckets: number[];
|
||||||
|
}
|
||||||
|
@ -14,9 +14,13 @@ export const OPERATION_LEGENDS = ['Operations'];
|
|||||||
export enum FORMULA {
|
export enum FORMULA {
|
||||||
ERROR_PERCENTAGE = 'A*100/B',
|
ERROR_PERCENTAGE = 'A*100/B',
|
||||||
DATABASE_CALLS_AVG_DURATION = 'A/B',
|
DATABASE_CALLS_AVG_DURATION = 'A/B',
|
||||||
|
APDEX_TRACES = '((B + C)/2)/A',
|
||||||
|
APDEX_DELTA_SPAN_METRICS = '(B + C/2)/A',
|
||||||
|
APDEX_CUMULATIVE_SPAN_METRICS = '((B + C)/2)/A',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum GraphTitle {
|
export enum GraphTitle {
|
||||||
|
APDEX = 'Apdex',
|
||||||
LATENCY = 'Latency',
|
LATENCY = 'Latency',
|
||||||
RATE_PER_OPS = 'Rate (ops/s)',
|
RATE_PER_OPS = 'Rate (ops/s)',
|
||||||
ERROR_PERCENTAGE = 'Error Percentage',
|
ERROR_PERCENTAGE = 'Error Percentage',
|
||||||
@ -41,6 +45,7 @@ export enum DataType {
|
|||||||
STRING = 'string',
|
STRING = 'string',
|
||||||
FLOAT64 = 'float64',
|
FLOAT64 = 'float64',
|
||||||
INT64 = 'int64',
|
INT64 = 'int64',
|
||||||
|
BOOL = 'bool',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MetricsType {
|
export enum MetricsType {
|
||||||
@ -49,7 +54,9 @@ export enum MetricsType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum WidgetKeys {
|
export enum WidgetKeys {
|
||||||
|
Le = 'le',
|
||||||
Name = 'name',
|
Name = 'name',
|
||||||
|
HasError = 'hasError',
|
||||||
Address = 'address',
|
Address = 'address',
|
||||||
DurationNano = 'durationNano',
|
DurationNano = 'durationNano',
|
||||||
StatusCode = 'status_code',
|
StatusCode = 'status_code',
|
||||||
|
@ -29,6 +29,14 @@ export const Col = styled(ColComponent)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const ColApDexContainer = styled(ColComponent)`
|
||||||
|
padding: 0 !important;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ColErrorContainer = styled(ColComponent)`
|
||||||
|
padding: 2rem 0 !important;
|
||||||
|
`;
|
||||||
|
|
||||||
export const GraphContainer = styled.div`
|
export const GraphContainer = styled.div`
|
||||||
height: 40vh;
|
height: 40vh;
|
||||||
`;
|
`;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
@ -5,7 +6,7 @@ import { IServiceName } from './Tabs/types';
|
|||||||
|
|
||||||
export interface GetWidgetQueryBuilderProps {
|
export interface GetWidgetQueryBuilderProps {
|
||||||
query: Widgets['query'];
|
query: Widgets['query'];
|
||||||
title?: string;
|
title?: ReactNode;
|
||||||
panelTypes: Widgets['panelTypes'];
|
panelTypes: Widgets['panelTypes'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,3 +24,14 @@ export const navigateToTrace = ({
|
|||||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1`,
|
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getNearestHighestBucketValue = (
|
||||||
|
value: number,
|
||||||
|
buckets: number[],
|
||||||
|
): string => {
|
||||||
|
const nearestBucket = buckets.find((bucket) => bucket >= value);
|
||||||
|
return nearestBucket?.toString() || '+Inf';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const convertMilSecToNanoSec = (value: number): number =>
|
||||||
|
value * 1000000000;
|
||||||
|
@ -67,7 +67,9 @@ function NewWidget({ selectedGraph, saveSettingOfPanel }: Props): JSX.Element {
|
|||||||
|
|
||||||
const selectedWidget = getWidget();
|
const selectedWidget = getWidget();
|
||||||
|
|
||||||
const [title, setTitle] = useState<string>(selectedWidget?.title || '');
|
const [title, setTitle] = useState<string>(
|
||||||
|
selectedWidget?.title?.toString() || '',
|
||||||
|
);
|
||||||
const [description, setDescription] = useState<string>(
|
const [description, setDescription] = useState<string>(
|
||||||
selectedWidget?.description || '',
|
selectedWidget?.description || '',
|
||||||
);
|
);
|
||||||
|
12
frontend/src/hooks/apDex/useGetApDexSettings.ts
Normal file
12
frontend/src/hooks/apDex/useGetApDexSettings.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { getApDexSettings } from 'api/metrics/ApDex/getApDexSettings';
|
||||||
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
|
import { useQuery, UseQueryResult } from 'react-query';
|
||||||
|
import { ApDexPayloadAndSettingsProps } from 'types/api/metrics/getApDex';
|
||||||
|
|
||||||
|
export const useGetApDexSettings = (
|
||||||
|
servicename: string,
|
||||||
|
): UseQueryResult<AxiosResponse<ApDexPayloadAndSettingsProps[]>, AxiosError> =>
|
||||||
|
useQuery<AxiosResponse<ApDexPayloadAndSettingsProps[]>, AxiosError>({
|
||||||
|
queryKey: [{ servicename }],
|
||||||
|
queryFn: async () => getApDexSettings(servicename),
|
||||||
|
});
|
12
frontend/src/hooks/apDex/useGetMetricMeta.ts
Normal file
12
frontend/src/hooks/apDex/useGetMetricMeta.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { getMetricMeta } from 'api/metrics/ApDex/getMetricMeta';
|
||||||
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
|
import { useQuery, UseQueryResult } from 'react-query';
|
||||||
|
import { MetricMetaProps } from 'types/api/metrics/getApDex';
|
||||||
|
|
||||||
|
export const useGetMetricMeta = (
|
||||||
|
metricName: string,
|
||||||
|
): UseQueryResult<AxiosResponse<MetricMetaProps>, AxiosError> =>
|
||||||
|
useQuery<AxiosResponse<MetricMetaProps>, AxiosError>({
|
||||||
|
queryKey: [{ metricName }],
|
||||||
|
queryFn: async () => getMetricMeta(metricName),
|
||||||
|
});
|
21
frontend/src/hooks/apDex/useSetApDexSettings.ts
Normal file
21
frontend/src/hooks/apDex/useSetApDexSettings.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { setApDexSettings } from 'api/metrics/ApDex/apDexSettings';
|
||||||
|
import { useMutation, UseMutationResult } from 'react-query';
|
||||||
|
import {
|
||||||
|
ApDexPayloadAndSettingsProps,
|
||||||
|
SetApDexPayloadProps,
|
||||||
|
} from 'types/api/metrics/getApDex';
|
||||||
|
|
||||||
|
export const useSetApDexSettings = ({
|
||||||
|
servicename,
|
||||||
|
threshold,
|
||||||
|
excludeStatusCode,
|
||||||
|
}: ApDexPayloadAndSettingsProps): UseMutationResult<
|
||||||
|
SetApDexPayloadProps,
|
||||||
|
Error,
|
||||||
|
ApDexPayloadAndSettingsProps
|
||||||
|
> =>
|
||||||
|
useMutation<SetApDexPayloadProps, Error, ApDexPayloadAndSettingsProps>({
|
||||||
|
mutationKey: [servicename, threshold.toString(), excludeStatusCode],
|
||||||
|
mutationFn: async () =>
|
||||||
|
setApDexSettings({ servicename, threshold, excludeStatusCode }),
|
||||||
|
});
|
@ -0,0 +1,65 @@
|
|||||||
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { APPLICATION_SETTINGS } from '../constants';
|
||||||
|
import { thresholdMockData } from './__mock__/thresholdMockData';
|
||||||
|
import ApDexApplication from './ApDexApplication';
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...jest.requireActual('react-router-dom'),
|
||||||
|
useParams: (): {
|
||||||
|
servicename: string;
|
||||||
|
} => ({ servicename: 'mockServiceName' }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('hooks/apDex/useGetApDexSettings', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
useGetApDexSettings: jest.fn().mockReturnValue({
|
||||||
|
data: thresholdMockData,
|
||||||
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
refetch: jest.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('hooks/apDex/useSetApDexSettings', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
useSetApDexSettings: jest.fn().mockReturnValue({
|
||||||
|
mutateAsync: jest.fn(),
|
||||||
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('ApDexApplication', () => {
|
||||||
|
it('should render the component', () => {
|
||||||
|
render(<ApDexApplication />);
|
||||||
|
|
||||||
|
expect(screen.getByText('Settings')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open the popover when the settings button is clicked', async () => {
|
||||||
|
render(<ApDexApplication />);
|
||||||
|
|
||||||
|
const button = screen.getByText('Settings');
|
||||||
|
fireEvent.click(button);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(APPLICATION_SETTINGS)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should close the popover when the close button is clicked', async () => {
|
||||||
|
render(<ApDexApplication />);
|
||||||
|
|
||||||
|
const button = screen.getByText('Settings');
|
||||||
|
fireEvent.click(button);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(APPLICATION_SETTINGS)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeButton = screen.getByText('Cancel');
|
||||||
|
fireEvent.click(closeButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText(APPLICATION_SETTINGS)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,56 @@
|
|||||||
|
import { SettingOutlined } from '@ant-design/icons';
|
||||||
|
import { Popover } from 'antd';
|
||||||
|
import { IServiceName } from 'container/MetricsApplication/Tabs/types';
|
||||||
|
import { useGetApDexSettings } from 'hooks/apDex/useGetApDexSettings';
|
||||||
|
import useErrorNotification from 'hooks/useErrorNotification';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { Button } from '../styles';
|
||||||
|
import ApDexSettings from './ApDexSettings';
|
||||||
|
|
||||||
|
function ApDexApplication(): JSX.Element {
|
||||||
|
const { servicename } = useParams<IServiceName>();
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
refetch: refetchGetApDexSetting,
|
||||||
|
} = useGetApDexSettings(servicename);
|
||||||
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||||
|
useErrorNotification(error);
|
||||||
|
|
||||||
|
const handlePopOverClose = (): void => {
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenChange = (newOpen: boolean): void => {
|
||||||
|
setIsOpen(newOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
placement="bottomRight"
|
||||||
|
destroyTooltipOnHide
|
||||||
|
trigger={['click']}
|
||||||
|
showArrow={false}
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={handleOpenChange}
|
||||||
|
content={
|
||||||
|
<ApDexSettings
|
||||||
|
servicename={servicename}
|
||||||
|
handlePopOverClose={handlePopOverClose}
|
||||||
|
isLoading={isLoading}
|
||||||
|
data={data}
|
||||||
|
refetchGetApDexSetting={refetchGetApDexSetting}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button size="middle" icon={<SettingOutlined />}>
|
||||||
|
Settings
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApDexApplication;
|
@ -0,0 +1,62 @@
|
|||||||
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { axiosResponseThresholdData } from './__mock__/axiosResponseMockThresholdData';
|
||||||
|
import ApDexSettings from './ApDexSettings';
|
||||||
|
|
||||||
|
jest.mock('hooks/apDex/useSetApDexSettings', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
useSetApDexSettings: jest.fn().mockReturnValue({
|
||||||
|
mutateAsync: jest.fn(),
|
||||||
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('ApDexSettings', () => {
|
||||||
|
it('should render the component', () => {
|
||||||
|
render(
|
||||||
|
<ApDexSettings
|
||||||
|
servicename="mockServiceName"
|
||||||
|
handlePopOverClose={jest.fn()}
|
||||||
|
isLoading={false}
|
||||||
|
data={axiosResponseThresholdData}
|
||||||
|
refetchGetApDexSetting={jest.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Application Settings')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the spinner when the data is loading', () => {
|
||||||
|
render(
|
||||||
|
<ApDexSettings
|
||||||
|
servicename="mockServiceName"
|
||||||
|
handlePopOverClose={jest.fn()}
|
||||||
|
isLoading
|
||||||
|
data={axiosResponseThresholdData}
|
||||||
|
refetchGetApDexSetting={jest.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should close the popover when the cancel button is clicked', async () => {
|
||||||
|
const mockHandlePopOverClose = jest.fn();
|
||||||
|
render(
|
||||||
|
<ApDexSettings
|
||||||
|
servicename="mockServiceName"
|
||||||
|
handlePopOverClose={mockHandlePopOverClose}
|
||||||
|
isLoading={false}
|
||||||
|
data={axiosResponseThresholdData}
|
||||||
|
refetchGetApDexSetting={jest.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const button = screen.getByText('Cancel');
|
||||||
|
fireEvent.click(button);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockHandlePopOverClose).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
119
frontend/src/pages/MetricsApplication/ApDex/ApDexSettings.tsx
Normal file
119
frontend/src/pages/MetricsApplication/ApDex/ApDexSettings.tsx
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
|
import { Card, InputNumber } from 'antd';
|
||||||
|
import Spinner from 'components/Spinner';
|
||||||
|
import TextToolTip from 'components/TextToolTip';
|
||||||
|
import {
|
||||||
|
apDexToolTipText,
|
||||||
|
apDexToolTipUrl,
|
||||||
|
apDexToolTipUrlText,
|
||||||
|
} from 'constants/apDex';
|
||||||
|
import { themeColors } from 'constants/theme';
|
||||||
|
import { useSetApDexSettings } from 'hooks/apDex/useSetApDexSettings';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { APPLICATION_SETTINGS } from '../constants';
|
||||||
|
import {
|
||||||
|
AppDexThresholdContainer,
|
||||||
|
Button,
|
||||||
|
SaveAndCancelContainer,
|
||||||
|
SaveButton,
|
||||||
|
Typography,
|
||||||
|
} from '../styles';
|
||||||
|
import { onSaveApDexSettings } from '../utils';
|
||||||
|
import { ApDexSettingsProps } from './types';
|
||||||
|
|
||||||
|
function ApDexSettings({
|
||||||
|
servicename,
|
||||||
|
handlePopOverClose,
|
||||||
|
isLoading,
|
||||||
|
data,
|
||||||
|
refetchGetApDexSetting,
|
||||||
|
}: ApDexSettingsProps): JSX.Element {
|
||||||
|
const [thresholdValue, setThresholdValue] = useState(() => {
|
||||||
|
if (data) {
|
||||||
|
return data.data[0].threshold;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
|
const { isLoading: isApDexLoading, mutateAsync } = useSetApDexSettings({
|
||||||
|
servicename,
|
||||||
|
threshold: thresholdValue,
|
||||||
|
excludeStatusCode: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleThreadholdChange = (value: number | null): void => {
|
||||||
|
if (value !== null) {
|
||||||
|
setThresholdValue(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Typography.Text style={{ color: themeColors.white }}>
|
||||||
|
<Spinner height="5vh" tip="Loading..." />
|
||||||
|
</Typography.Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={APPLICATION_SETTINGS}
|
||||||
|
extra={<CloseOutlined width={10} height={10} onClick={handlePopOverClose} />}
|
||||||
|
actions={[
|
||||||
|
<SaveAndCancelContainer key="SaveAndCancelContainer">
|
||||||
|
<Button onClick={handlePopOverClose}>Cancel</Button>
|
||||||
|
<SaveButton
|
||||||
|
onClick={onSaveApDexSettings({
|
||||||
|
handlePopOverClose,
|
||||||
|
mutateAsync,
|
||||||
|
notifications,
|
||||||
|
refetchGetApDexSetting,
|
||||||
|
servicename,
|
||||||
|
thresholdValue,
|
||||||
|
})}
|
||||||
|
type="primary"
|
||||||
|
loading={isApDexLoading}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</SaveButton>
|
||||||
|
</SaveAndCancelContainer>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<AppDexThresholdContainer>
|
||||||
|
<Typography>
|
||||||
|
Apdex threshold (in seconds){' '}
|
||||||
|
<TextToolTip
|
||||||
|
text={apDexToolTipText}
|
||||||
|
url={apDexToolTipUrl}
|
||||||
|
useFilledIcon={false}
|
||||||
|
urlText={apDexToolTipUrlText}
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<InputNumber
|
||||||
|
value={thresholdValue}
|
||||||
|
onChange={handleThreadholdChange}
|
||||||
|
min={0}
|
||||||
|
step={0.1}
|
||||||
|
/>
|
||||||
|
</AppDexThresholdContainer>
|
||||||
|
{/* TODO: Add this feature later when backend is ready to support it. */}
|
||||||
|
{/* <ExcludeErrorCodeContainer>
|
||||||
|
<Typography.Text>
|
||||||
|
Exclude following error codes from error rate calculation
|
||||||
|
</Typography.Text>
|
||||||
|
<Input placeholder="e.g. 406, 418" />
|
||||||
|
</ExcludeErrorCodeContainer> */}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApDexSettings.defaultProps = {
|
||||||
|
isLoading: undefined,
|
||||||
|
data: undefined,
|
||||||
|
refetchGetApDexSetting: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApDexSettings;
|
@ -0,0 +1,9 @@
|
|||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
export const axiosResponseThresholdData = {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
threshold: 0.5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as AxiosResponse;
|
@ -0,0 +1,7 @@
|
|||||||
|
export const thresholdMockData = {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
threshold: 0.5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
10
frontend/src/pages/MetricsApplication/ApDex/types.ts
Normal file
10
frontend/src/pages/MetricsApplication/ApDex/types.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { ApDexPayloadAndSettingsProps } from 'types/api/metrics/getApDex';
|
||||||
|
|
||||||
|
export interface ApDexSettingsProps {
|
||||||
|
servicename: string;
|
||||||
|
handlePopOverClose: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
data?: AxiosResponse<ApDexPayloadAndSettingsProps[]>;
|
||||||
|
refetchGetApDexSetting?: () => void;
|
||||||
|
}
|
1
frontend/src/pages/MetricsApplication/constants.ts
Normal file
1
frontend/src/pages/MetricsApplication/constants.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const APPLICATION_SETTINGS = 'Application Settings';
|
@ -8,6 +8,7 @@ import history from 'lib/history';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { generatePath, useParams } from 'react-router-dom';
|
import { generatePath, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import ApDexApplication from './ApDex/ApDexApplication';
|
||||||
import { MetricsApplicationTab, TAB_KEY_VS_LABEL } from './types';
|
import { MetricsApplicationTab, TAB_KEY_VS_LABEL } from './types';
|
||||||
import useMetricsApplicationTabKey from './useMetricsApplicationTabKey';
|
import useMetricsApplicationTabKey from './useMetricsApplicationTabKey';
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ function MetricsApplication(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ResourceAttributesFilter />
|
<ResourceAttributesFilter />
|
||||||
|
<ApDexApplication />
|
||||||
<RouteTab routes={routes} history={history} activeKey={activeKey} />
|
<RouteTab routes={routes} history={history} activeKey={activeKey} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
44
frontend/src/pages/MetricsApplication/styles.ts
Normal file
44
frontend/src/pages/MetricsApplication/styles.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import {
|
||||||
|
Button as ButtonComponent,
|
||||||
|
Typography as TypographyComponent,
|
||||||
|
} from 'antd';
|
||||||
|
import { themeColors } from 'constants/theme';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const Button = styled(ButtonComponent)`
|
||||||
|
&&& {
|
||||||
|
width: min-content;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const AppDexThresholdContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Typography = styled(TypographyComponent)`
|
||||||
|
&&& {
|
||||||
|
width: 24rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
color: ${themeColors.white};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SaveAndCancelContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-right: 1rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SaveButton = styled(ButtonComponent)`
|
||||||
|
&&& {
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ExcludeErrorCodeContainer = styled.div`
|
||||||
|
margin: 1rem 0;
|
||||||
|
`;
|
@ -1,3 +1,10 @@
|
|||||||
|
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||||
|
import { UseMutateAsyncFunction } from 'react-query';
|
||||||
|
import {
|
||||||
|
ApDexPayloadAndSettingsProps,
|
||||||
|
SetApDexPayloadProps,
|
||||||
|
} from 'types/api/metrics/getApDex';
|
||||||
|
|
||||||
export enum MetricsApplicationTab {
|
export enum MetricsApplicationTab {
|
||||||
OVER_METRICS = 'OVER_METRICS',
|
OVER_METRICS = 'OVER_METRICS',
|
||||||
DB_CALL_METRICS = 'DB_CALL_METRICS',
|
DB_CALL_METRICS = 'DB_CALL_METRICS',
|
||||||
@ -9,3 +16,16 @@ export const TAB_KEY_VS_LABEL = {
|
|||||||
[MetricsApplicationTab.DB_CALL_METRICS]: 'DB Call Metrics',
|
[MetricsApplicationTab.DB_CALL_METRICS]: 'DB Call Metrics',
|
||||||
[MetricsApplicationTab.EXTERNAL_METRICS]: 'External Metrics',
|
[MetricsApplicationTab.EXTERNAL_METRICS]: 'External Metrics',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface OnSaveApDexSettingsProps {
|
||||||
|
thresholdValue: number;
|
||||||
|
servicename: string;
|
||||||
|
notifications: NotificationInstance;
|
||||||
|
refetchGetApDexSetting?: VoidFunction;
|
||||||
|
mutateAsync: UseMutateAsyncFunction<
|
||||||
|
SetApDexPayloadProps,
|
||||||
|
Error,
|
||||||
|
ApDexPayloadAndSettingsProps
|
||||||
|
>;
|
||||||
|
handlePopOverClose: VoidFunction;
|
||||||
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
|
|
||||||
import { TAB_KEYS_VS_METRICS_APPLICATION_KEY } from './config';
|
import { TAB_KEYS_VS_METRICS_APPLICATION_KEY } from './config';
|
||||||
import { MetricsApplicationTab } from './types';
|
import { MetricsApplicationTab, OnSaveApDexSettingsProps } from './types';
|
||||||
|
|
||||||
export const isMetricsApplicationTab = (
|
export const isMetricsApplicationTab = (
|
||||||
tab: string,
|
tab: string,
|
||||||
@ -15,3 +18,29 @@ export const getMetricsApplicationKey = (
|
|||||||
|
|
||||||
return MetricsApplicationTab.OVER_METRICS;
|
return MetricsApplicationTab.OVER_METRICS;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const onSaveApDexSettings = ({
|
||||||
|
thresholdValue,
|
||||||
|
refetchGetApDexSetting,
|
||||||
|
mutateAsync,
|
||||||
|
notifications,
|
||||||
|
handlePopOverClose,
|
||||||
|
servicename,
|
||||||
|
}: OnSaveApDexSettingsProps) => async (): Promise<void> => {
|
||||||
|
if (!refetchGetApDexSetting) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mutateAsync({
|
||||||
|
servicename,
|
||||||
|
threshold: thresholdValue,
|
||||||
|
excludeStatusCode: '',
|
||||||
|
});
|
||||||
|
await refetchGetApDexSetting();
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error({
|
||||||
|
message: axios.isAxiosError(err) ? err.message : SOMETHING_WENT_WRONG,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
handlePopOverClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
import { Layout } from 'react-grid-layout';
|
import { Layout } from 'react-grid-layout';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ export interface IBaseWidget {
|
|||||||
isStacked: boolean;
|
isStacked: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
panelTypes: PANEL_TYPES;
|
panelTypes: PANEL_TYPES;
|
||||||
title: string;
|
title: ReactNode;
|
||||||
description: string;
|
description: string;
|
||||||
opacity: string;
|
opacity: string;
|
||||||
nullZeroValues: string;
|
nullZeroValues: string;
|
||||||
|
14
frontend/src/types/api/metrics/getApDex.ts
Normal file
14
frontend/src/types/api/metrics/getApDex.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export interface ApDexPayloadAndSettingsProps {
|
||||||
|
servicename: string;
|
||||||
|
threshold: number;
|
||||||
|
excludeStatusCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetApDexPayloadProps {
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MetricMetaProps {
|
||||||
|
delta: boolean;
|
||||||
|
le: number[];
|
||||||
|
}
|
@ -21,7 +21,7 @@ export interface TagFilterItem {
|
|||||||
id: string;
|
id: string;
|
||||||
key?: BaseAutocompleteData;
|
key?: BaseAutocompleteData;
|
||||||
op: string;
|
op: string;
|
||||||
value: string[] | string;
|
value: string[] | string | number | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TagFilter {
|
export interface TagFilter {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user