Skip to content

Commit

Permalink
feat: client changes for query stats (#5706)
Browse files Browse the repository at this point in the history
* feat: changes for the query stats websockets

* chore: remove unwanted files

* fix: work on random id rather than hash

* fix: improve the icons and design

* feat: webpack and docker file changes

* fix: test cases

* chore: format the units

* chore: address review comments

* chore: update the id to uuid package

* fix: build issues

* chore: remove docker file changes

* chore: remove docker file changes
  • Loading branch information
vikrantgupta25 authored Aug 16, 2024
1 parent 65280cf commit 1b9683d
Show file tree
Hide file tree
Showing 15 changed files with 197 additions and 5 deletions.
1 change: 1 addition & 0 deletions frontend/public/Icons/solid-x-circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions frontend/src/api/common/getQueryStats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import getLocalStorageApi from 'api/browser/localstorage/get';
import { ENVIRONMENT } from 'constants/env';
import { LOCALSTORAGE } from 'constants/localStorage';

export interface WsDataEvent {
read_rows: number;
read_bytes: number;
elapsed_ms: number;
}
interface GetQueryStatsProps {
queryId: string;
setData: React.Dispatch<React.SetStateAction<WsDataEvent | undefined>>;
}

export function getQueryStats(props: GetQueryStatsProps): void {
const { queryId, setData } = props;

const token = getLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN) || '';
const socket = new WebSocket(
`${ENVIRONMENT.wsURL}api/v3/query_progress?q=${queryId}`,
token,
);

socket.addEventListener('message', (event) => {
try {
const parsedData = JSON.parse(event?.data);
setData(parsedData);
} catch {
setData(event?.data);
}
});
}
10 changes: 8 additions & 2 deletions frontend/src/api/metrics/getQueryRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ export const getMetricsQueryRange = async (
props: QueryRangePayload,
version: string,
signal: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => {
try {
if (version && version === ENTITY_VERSION_V4) {
const response = await ApiV4Instance.post('/query_range', props, { signal });
const response = await ApiV4Instance.post('/query_range', props, {
signal,
});

return {
statusCode: 200,
Expand All @@ -26,7 +29,10 @@ export const getMetricsQueryRange = async (
};
}

const response = await ApiV3Instance.post('/query_range', props, { signal });
const response = await ApiV3Instance.post('/query_range', props, {
signal,
headers,
});

return {
statusCode: 200,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export const ENVIRONMENT = {
process?.env?.FRONTEND_API_ENDPOINT ||
process?.env?.GITPOD_WORKSPACE_URL?.replace('://', '://8080-') ||
'',
wsURL: process?.env?.WEBSOCKET_API_ENDPOINT || 'ws://localhost:8080/',
};
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,36 @@
position: relative;
}
}
.query-stats {
display: flex;
align-items: center;
gap: 12px;
.rows {
color: var(--bg-vanilla-400);
font-family: 'Geist Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
letter-spacing: 0.36px;
}

.divider {
width: 1px;
height: 14px;
background: #242834;
}

.time {
color: var(--bg-vanilla-400);
font-family: 'Geist Mono';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
letter-spacing: 0.36px;
}
}
}

.logs-actions-container {
Expand Down Expand Up @@ -149,6 +179,15 @@
background: var(--bg-robin-400);
}
}
.query-stats {
.rows {
color: var(--bg-ink-400);
}

.time {
color: var(--bg-ink-400);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.query-status {
display: flex;
align-items: center;
}
42 changes: 42 additions & 0 deletions frontend/src/container/LogsExplorerViews/QueryStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import './QueryStatus.styles.scss';

import { LoadingOutlined } from '@ant-design/icons';
import { Color } from '@signozhq/design-tokens';
import { Spin } from 'antd';
import { CircleCheck } from 'lucide-react';
import React, { useMemo } from 'react';

interface IQueryStatusProps {
loading: boolean;
error: boolean;
success: boolean;
}

export default function QueryStatus(
props: IQueryStatusProps,
): React.ReactElement {
const { loading, error, success } = props;

const content = useMemo((): React.ReactElement => {
if (loading) {
return <Spin spinning size="small" indicator={<LoadingOutlined spin />} />;
}
if (error) {
return (
<img
src="/Icons/solid-x-circle.svg"
alt="header"
className="error"
style={{ height: '14px', width: '14px' }}
/>
);
}
if (success) {
return (
<CircleCheck className="success" size={14} fill={Color.BG_ROBIN_500} />
);
}
return <div />;
}, [error, loading, success]);
return <div className="query-status">{content}</div>;
}
60 changes: 58 additions & 2 deletions frontend/src/container/LogsExplorerViews/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* eslint-disable sonarjs/cognitive-complexity */
import './LogsExplorerViews.styles.scss';

import { Button } from 'antd';
import { Button, Typography } from 'antd';
import { getQueryStats, WsDataEvent } from 'api/common/getQueryStats';
import logEvent from 'api/common/logEvent';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { LOCALSTORAGE } from 'constants/localStorage';
Expand Down Expand Up @@ -77,6 +79,8 @@ import { GlobalReducer } from 'types/reducer/globalTime';
import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink';
import { v4 } from 'uuid';

import QueryStatus from './QueryStatus';

function LogsExplorerViews({
selectedView,
showFrequencyChart,
Expand Down Expand Up @@ -130,6 +134,8 @@ function LogsExplorerViews({
const [logs, setLogs] = useState<ILog[]>([]);
const [requestData, setRequestData] = useState<Query | null>(null);
const [showFormatMenuItems, setShowFormatMenuItems] = useState(false);
const [queryId, setQueryId] = useState<string>(v4());
const [queryStats, setQueryStats] = useState<WsDataEvent>();

const handleAxisError = useAxiosError();

Expand Down Expand Up @@ -233,7 +239,13 @@ function LogsExplorerViews({
chartQueryKeyRef,
);

const { data, isLoading, isFetching, isError } = useGetExplorerQueryRange(
const {
data,
isLoading,
isFetching,
isError,
isSuccess,
} = useGetExplorerQueryRange(
requestData,
panelType,
DEFAULT_ENTITY_VERSION,
Expand All @@ -251,6 +263,9 @@ function LogsExplorerViews({
},
undefined,
listQueryKeyRef,
{
...(!isEmpty(queryId) && { 'X-SIGNOZ-QUERY-ID': queryId }),
},
);

const getRequestData = useCallback(
Expand Down Expand Up @@ -337,6 +352,23 @@ function LogsExplorerViews({
],
);

useEffect(() => {
setQueryId(v4());
}, [isError, isSuccess]);

useEffect(() => {
if (
!isEmpty(queryId) &&
(isLoading || isFetching) &&
selectedPanelType !== PANEL_TYPES.LIST
) {
setQueryStats(undefined);
setTimeout(() => {
getQueryStats({ queryId, setData: setQueryStats });
}, 500);
}
}, [queryId, isLoading, isFetching, selectedPanelType]);

const logEventCalledRef = useRef(false);
useEffect(() => {
if (!logEventCalledRef.current && !isUndefined(data?.payload)) {
Expand Down Expand Up @@ -703,6 +735,30 @@ function LogsExplorerViews({
</div>
</div>
)}
{(selectedPanelType === PANEL_TYPES.TIME_SERIES ||
selectedPanelType === PANEL_TYPES.TABLE) && (
<div className="query-stats">
<QueryStatus
loading={isLoading || isFetching}
error={isError}
success={isSuccess}
/>
{queryStats?.read_rows && (
<Typography.Text className="rows">
{getYAxisFormattedValue(queryStats.read_rows?.toString(), 'short')}{' '}
rows
</Typography.Text>
)}
{queryStats?.elapsed_ms && (
<>
<div className="divider" />
<Typography.Text className="time">
{getYAxisFormattedValue(queryStats?.elapsed_ms?.toString(), 'ms')}
</Typography.Text>
</>
)}
</div>
)}
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jest.mock(
},
);

jest.mock('api/common/getQueryStats', () => jest.fn());

jest.mock('constants/panelTypes', () => ({
AVAILABLE_EXPORT_PANEL_TYPES: ['graph', 'table'],
}));
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/hooks/queryBuilder/useGetExplorerQueryRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const useGetExplorerQueryRange = (
params?: Record<string, unknown>,
isDependentOnQB = true,
keyRef?: MutableRefObject<any>,
headers?: Record<string, string>,
): UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error> => {
const { isEnabledQuery } = useQueryBuilder();
const { selectedTime: globalSelectedInterval, minTime, maxTime } = useSelector<
Expand Down Expand Up @@ -61,5 +62,6 @@ export const useGetExplorerQueryRange = (
queryKey: [key, globalSelectedInterval, requestData, minTime, maxTime],
enabled: isEnabled,
},
headers,
);
};
4 changes: 3 additions & 1 deletion frontend/src/hooks/queryBuilder/useGetQueryRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ type UseGetQueryRange = (
requestData: GetQueryResultsProps,
version: string,
options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
headers?: Record<string, string>,
) => UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error>;

export const useGetQueryRange: UseGetQueryRange = (
requestData,
version,
options,
headers,
) => {
const newRequestData: GetQueryResultsProps = useMemo(
() => ({
Expand All @@ -45,7 +47,7 @@ export const useGetQueryRange: UseGetQueryRange = (

return useQuery<SuccessResponse<MetricRangePayloadProps>, Error>({
queryFn: async ({ signal }) =>
GetMetricQueryRange(requestData, version, signal),
GetMetricQueryRange(requestData, version, signal, headers),
...options,
queryKey,
});
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib/dashboard/getQueryResults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ export async function GetMetricQueryRange(
props: GetQueryResultsProps,
version: string,
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponse<MetricRangePayloadProps>> {
const { legendMap, queryPayload } = prepareQueryRangePayload(props);

const response = await getMetricsQueryRange(
queryPayload,
version || 'v3',
signal,
headers,
);

if (response.statusCode >= 400) {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/typings/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ declare global {
namespace NodeJS {
interface ProcessEnv {
FRONTEND_API_ENDPOINT: string | undefined;
WEBSOCKET_API_ENDPOINT: string | undefined;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions frontend/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const plugins = [
'process.env': JSON.stringify({
NODE_ENV: process.env.NODE_ENV,
FRONTEND_API_ENDPOINT: process.env.FRONTEND_API_ENDPOINT,
WEBSOCKET_API_ENDPOINT: process.env.WEBSOCKET_API_ENDPOINT,
INTERCOM_APP_ID: process.env.INTERCOM_APP_ID,
SEGMENT_ID: process.env.SEGMENT_ID,
POSTHOG_KEY: process.env.POSTHOG_KEY,
Expand Down
1 change: 1 addition & 0 deletions frontend/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const plugins = [
new webpack.DefinePlugin({
'process.env': JSON.stringify({
FRONTEND_API_ENDPOINT: process.env.FRONTEND_API_ENDPOINT,
WEBSOCKET_API_ENDPOINT: process.env.WEBSOCKET_API_ENDPOINT,
INTERCOM_APP_ID: process.env.INTERCOM_APP_ID,
SEGMENT_ID: process.env.SEGMENT_ID,
POSTHOG_KEY: process.env.POSTHOG_KEY,
Expand Down

0 comments on commit 1b9683d

Please sign in to comment.