Compare commits
24 Commits
ssl-certs-
...
v0.18.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efe34d2582 | ||
|
|
d63a35e937 | ||
|
|
c49bb0696b | ||
|
|
9a58cc652c | ||
|
|
5ee0bb57cc | ||
|
|
c8f3e9024c | ||
|
|
8f6178f0a9 | ||
|
|
5ff9172103 | ||
|
|
67ba46abde | ||
|
|
20b1f96c19 | ||
|
|
61a1d04252 | ||
|
|
80171eddea | ||
|
|
28684423d1 | ||
|
|
037559537b | ||
|
|
31a89bfdb3 | ||
|
|
36610c809e | ||
|
|
1a0c76a43b | ||
|
|
c1d00c1155 | ||
|
|
3f96325ad8 | ||
|
|
99ed314fc9 | ||
|
|
12e56932ee | ||
|
|
c4944370ce | ||
|
|
192d3881a1 | ||
|
|
9d20c2f787 |
6
.github/workflows/staging-deployment.yaml
vendored
6
.github/workflows/staging-deployment.yaml
vendored
@@ -11,21 +11,23 @@ jobs:
|
||||
environment: staging
|
||||
steps:
|
||||
- name: Executing remote ssh commands using ssh key
|
||||
uses: appleboy/ssh-action@v0.1.6
|
||||
uses: appleboy/ssh-action@v0.1.8
|
||||
env:
|
||||
GITHUB_BRANCH: develop
|
||||
GITHUB_SHA: ${{ github.sha }}
|
||||
with:
|
||||
host: ${{ secrets.HOST_DNS }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.EC2_SSH_KEY }}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
envs: GITHUB_BRANCH,GITHUB_SHA
|
||||
command_timeout: 60m
|
||||
script: |
|
||||
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
|
||||
echo "GITHUB_SHA: ${GITHUB_SHA}"
|
||||
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
|
||||
export OTELCOL_TAG="main"
|
||||
docker system prune --force
|
||||
docker pull signoz/signoz-otel-collector:main
|
||||
cd ~/signoz
|
||||
git status
|
||||
git add .
|
||||
|
||||
4
.github/workflows/testing-deployment.yaml
vendored
4
.github/workflows/testing-deployment.yaml
vendored
@@ -11,14 +11,14 @@ jobs:
|
||||
if: ${{ github.event.label.name == 'testing-deploy' }}
|
||||
steps:
|
||||
- name: Executing remote ssh commands using ssh key
|
||||
uses: appleboy/ssh-action@v0.1.6
|
||||
uses: appleboy/ssh-action@v0.1.8
|
||||
env:
|
||||
GITHUB_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||
GITHUB_SHA: ${{ github.sha }}
|
||||
with:
|
||||
host: ${{ secrets.HOST_DNS }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.EC2_SSH_KEY }}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
envs: GITHUB_BRANCH,GITHUB_SHA
|
||||
command_timeout: 60m
|
||||
script: |
|
||||
|
||||
@@ -137,7 +137,7 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.17.0
|
||||
image: signoz/query-service:0.18.2
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
@@ -166,7 +166,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.17.0
|
||||
image: signoz/frontend:0.18.2
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@@ -179,7 +179,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:0.66.6
|
||||
image: signoz/signoz-otel-collector:0.66.7
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
user: root # required for reading docker container logs
|
||||
volumes:
|
||||
@@ -208,7 +208,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/signoz-otel-collector:0.66.6
|
||||
image: signoz/signoz-otel-collector:0.66.7
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
|
||||
@@ -41,7 +41,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
otel-collector:
|
||||
container_name: otel-collector
|
||||
image: signoz/signoz-otel-collector:0.66.6
|
||||
image: signoz/signoz-otel-collector:0.66.7
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
# user: root # required for reading docker container logs
|
||||
volumes:
|
||||
@@ -67,7 +67,7 @@ services:
|
||||
|
||||
otel-collector-metrics:
|
||||
container_name: otel-collector-metrics
|
||||
image: signoz/signoz-otel-collector:0.66.6
|
||||
image: signoz/signoz-otel-collector:0.66.7
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
|
||||
@@ -153,7 +153,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.17.0}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.18.2}
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
# ports:
|
||||
@@ -181,7 +181,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.17.0}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.18.2}
|
||||
container_name: frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@@ -193,7 +193,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.6}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.7}
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
user: root # required for reading docker container logs
|
||||
volumes:
|
||||
@@ -219,7 +219,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.6}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.7}
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
|
||||
@@ -4,6 +4,7 @@ import Spinner from 'components/Spinner';
|
||||
import AppLayout from 'container/AppLayout';
|
||||
import { useThemeConfig } from 'hooks/useDarkMode';
|
||||
import { NotificationProvider } from 'hooks/useNotifications';
|
||||
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
||||
import history from 'lib/history';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import React, { Suspense } from 'react';
|
||||
@@ -17,30 +18,32 @@ function App(): JSX.Element {
|
||||
|
||||
return (
|
||||
<ConfigProvider theme={themeConfig}>
|
||||
<NotificationProvider>
|
||||
<Router history={history}>
|
||||
<Router history={history}>
|
||||
<NotificationProvider>
|
||||
<PrivateRoute>
|
||||
<QueryBuilderProvider>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
<ResourceProvider>
|
||||
<QueryBuilderProvider>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
</QueryBuilderProvider>
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
</QueryBuilderProvider>
|
||||
</ResourceProvider>
|
||||
</PrivateRoute>
|
||||
</Router>
|
||||
</NotificationProvider>
|
||||
</NotificationProvider>
|
||||
</Router>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const apiV1 = '/api/v1/';
|
||||
|
||||
export const apiV2 = '/api/v2/';
|
||||
export const apiV3 = '/api/v3/';
|
||||
export const apiAlertManager = '/api/alertmanager';
|
||||
|
||||
export default apiV1;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/errors/getAll';
|
||||
|
||||
@@ -9,11 +8,17 @@ const getAll = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/listErrors?${createQueryParams({
|
||||
...props,
|
||||
})}`,
|
||||
);
|
||||
const response = await axios.post(`/listErrors`, {
|
||||
start: `${props.start}`,
|
||||
end: `${props.end}`,
|
||||
order: props.order,
|
||||
orderParam: props.orderParam,
|
||||
limit: props.limit,
|
||||
offset: props.offset,
|
||||
exceptionType: props.exceptionType,
|
||||
serviceName: props.serviceName,
|
||||
tags: props.tags,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/errors/getErrorCounts';
|
||||
|
||||
@@ -9,11 +8,13 @@ const getErrorCounts = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/countErrors?${createQueryParams({
|
||||
...props,
|
||||
})}`,
|
||||
);
|
||||
const response = await axios.post(`/countErrors`, {
|
||||
start: `${props.start}`,
|
||||
end: `${props.end}`,
|
||||
exceptionType: props.exceptionType,
|
||||
serviceName: props.serviceName,
|
||||
tags: props.tags,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ENVIRONMENT } from 'constants/env';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import store from 'store';
|
||||
|
||||
import apiV1, { apiAlertManager, apiV2 } from './apiV1';
|
||||
import apiV1, { apiAlertManager, apiV2, apiV3 } from './apiV1';
|
||||
import { Logout } from './utils';
|
||||
|
||||
const interceptorsResponse = (
|
||||
@@ -109,6 +109,17 @@ ApiV2Instance.interceptors.response.use(
|
||||
);
|
||||
ApiV2Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
|
||||
// axios V3
|
||||
export const ApiV3Instance = axios.create({
|
||||
baseURL: `${ENVIRONMENT.baseURL}${apiV3}`,
|
||||
});
|
||||
ApiV3Instance.interceptors.response.use(
|
||||
interceptorsResponse,
|
||||
interceptorRejected,
|
||||
);
|
||||
ApiV3Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
//
|
||||
|
||||
AxiosAlertManagerInstance.interceptors.response.use(
|
||||
interceptorsResponse,
|
||||
interceptorRejected,
|
||||
|
||||
33
frontend/src/api/queryBuilder/getAggregateAttribute.ts
Normal file
33
frontend/src/api/queryBuilder/getAggregateAttribute.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { ApiV3Instance } from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
// ** Helpers
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
// ** Types
|
||||
import { IGetAggregateAttributePayload } from 'types/api/queryBuilder/getAggregatorAttribute';
|
||||
import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
|
||||
export const getAggregateAttribute = async ({
|
||||
aggregateOperator,
|
||||
searchText,
|
||||
dataSource,
|
||||
}: IGetAggregateAttributePayload): Promise<
|
||||
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response: AxiosResponse<{
|
||||
data: IQueryAutocompleteResponse;
|
||||
}> = await ApiV3Instance.get(
|
||||
`autocomplete/aggregate_attributes?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&searchText=${searchText}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.statusText,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (e) {
|
||||
return ErrorResponseHandler(e as AxiosError);
|
||||
}
|
||||
};
|
||||
33
frontend/src/api/queryBuilder/getAttributeKeys.ts
Normal file
33
frontend/src/api/queryBuilder/getAttributeKeys.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { ApiV3Instance } from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
// ** Types
|
||||
import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys';
|
||||
import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
|
||||
export const getAggregateKeys = async ({
|
||||
aggregateOperator,
|
||||
searchText,
|
||||
dataSource,
|
||||
aggregateAttribute,
|
||||
}: IGetAttributeKeysPayload): Promise<
|
||||
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response: AxiosResponse<{
|
||||
data: IQueryAutocompleteResponse;
|
||||
}> = await ApiV3Instance.get(
|
||||
`autocomplete/attribute_keys?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&aggregateAttribute=${aggregateAttribute}&searchText=${searchText}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.statusText,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (e) {
|
||||
return ErrorResponseHandler(e as AxiosError);
|
||||
}
|
||||
};
|
||||
@@ -10,7 +10,7 @@ const getSpans = async (
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const updatedSelectedTags = props.selectedTags.map((e) => ({
|
||||
Key: e.Key,
|
||||
Key: `${e.Key}.(string)`,
|
||||
Operator: e.Operator,
|
||||
StringValues: e.StringValues,
|
||||
NumberValues: e.NumberValues,
|
||||
|
||||
@@ -28,7 +28,7 @@ const getSpanAggregate = async (
|
||||
});
|
||||
|
||||
const updatedSelectedTags = props.selectedTags.map((e) => ({
|
||||
Key: e.Key,
|
||||
Key: `${e.Key}.(string)`,
|
||||
Operator: e.Operator,
|
||||
StringValues: e.StringValues,
|
||||
NumberValues: e.NumberValues,
|
||||
|
||||
@@ -1,23 +1,40 @@
|
||||
/* eslint-disable react/no-unstable-nested-components */
|
||||
import { grey } from '@ant-design/colors';
|
||||
import { QuestionCircleFilled } from '@ant-design/icons';
|
||||
import { Tooltip } from 'antd';
|
||||
import React from 'react';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { style } from './styles';
|
||||
|
||||
function TextToolTip({ text, url }: TextToolTipProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const overlay = useMemo(
|
||||
() => (
|
||||
<div>
|
||||
{`${text} `}
|
||||
{url && (
|
||||
<a href={url} rel="noopener noreferrer" target="_blank">
|
||||
here
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
[text, url],
|
||||
);
|
||||
|
||||
const iconStyle = useMemo(
|
||||
() => ({
|
||||
...style,
|
||||
color: isDarkMode ? themeColors.whiteCream : grey[0],
|
||||
}),
|
||||
[isDarkMode],
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
overlay={(): JSX.Element => (
|
||||
<div>
|
||||
{`${text} `}
|
||||
{url && (
|
||||
<a href={url} rel="noopener noreferrer" target="_blank">
|
||||
here
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<QuestionCircleFilled style={{ fontSize: '1.3125rem' }} />
|
||||
<Tooltip overlay={overlay}>
|
||||
<QuestionCircleFilled style={iconStyle} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
1
frontend/src/components/TextToolTip/styles.ts
Normal file
1
frontend/src/components/TextToolTip/styles.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const style = { fontSize: '1.3125rem' };
|
||||
3
frontend/src/constants/useQueryKeys.ts
Normal file
3
frontend/src/constants/useQueryKeys.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export enum QueryBuilderKeys {
|
||||
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
||||
}
|
||||
@@ -18,6 +18,8 @@ import { ResizeTable } from 'components/ResizeTable';
|
||||
import ROUTES from 'constants/routes';
|
||||
import dayjs from 'dayjs';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import history from 'lib/history';
|
||||
@@ -93,9 +95,11 @@ function AllErrors(): JSX.Element {
|
||||
],
|
||||
);
|
||||
|
||||
const { queries } = useResourceAttribute();
|
||||
|
||||
const [{ isLoading, data }, errorCountResponse] = useQueries([
|
||||
{
|
||||
queryKey: ['getAllErrors', updatedPath, maxTime, minTime],
|
||||
queryKey: ['getAllErrors', updatedPath, maxTime, minTime, queries],
|
||||
queryFn: (): Promise<SuccessResponse<PayloadProps> | ErrorResponse> =>
|
||||
getAll({
|
||||
end: maxTime,
|
||||
@@ -106,6 +110,7 @@ function AllErrors(): JSX.Element {
|
||||
orderParam: getUpdatedParams,
|
||||
exceptionType: getUpdatedExceptionType,
|
||||
serviceName: getUpdatedServiceName,
|
||||
tags: convertRawQueriesToTraceSelectedTags(queries),
|
||||
}),
|
||||
enabled: !loading,
|
||||
},
|
||||
@@ -116,6 +121,7 @@ function AllErrors(): JSX.Element {
|
||||
minTime,
|
||||
getUpdatedExceptionType,
|
||||
getUpdatedServiceName,
|
||||
queries,
|
||||
],
|
||||
queryFn: (): Promise<ErrorResponse | SuccessResponse<number>> =>
|
||||
getErrorCounts({
|
||||
@@ -123,6 +129,7 @@ function AllErrors(): JSX.Element {
|
||||
start: minTime,
|
||||
exceptionType: getUpdatedExceptionType,
|
||||
serviceName: getUpdatedServiceName,
|
||||
tags: convertRawQueriesToTraceSelectedTags(queries),
|
||||
}),
|
||||
enabled: !loading,
|
||||
},
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
import { Button, Row } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
import { QueryFields } from './utils';
|
||||
|
||||
interface SearchFieldsActionBarProps {
|
||||
fieldsQuery: QueryFields[][];
|
||||
applyUpdate: () => void;
|
||||
clearFilters: () => void;
|
||||
applyUpdate: VoidFunction;
|
||||
clearFilters: VoidFunction;
|
||||
}
|
||||
|
||||
export function SearchFieldsActionBar({
|
||||
fieldsQuery,
|
||||
applyUpdate,
|
||||
clearFilters,
|
||||
}: SearchFieldsActionBarProps): JSX.Element | null {
|
||||
if (fieldsQuery.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Row style={{ justifyContent: 'flex-end', paddingRight: '2.4rem' }}>
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { reverseParser } from 'lib/logql';
|
||||
import { flatten } from 'lodash-es';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -18,13 +19,13 @@ import {
|
||||
} from './utils';
|
||||
|
||||
export interface SearchFieldsProps {
|
||||
updateParsedQuery: (query: QueryFields[]) => void;
|
||||
onDropDownToggleHandler: (value: boolean) => VoidFunction;
|
||||
updateQueryString: (value: string) => void;
|
||||
}
|
||||
|
||||
function SearchFields({
|
||||
updateParsedQuery,
|
||||
onDropDownToggleHandler,
|
||||
updateQueryString,
|
||||
}: SearchFieldsProps): JSX.Element {
|
||||
const {
|
||||
searchFilter: { parsedQuery },
|
||||
@@ -90,15 +91,15 @@ function SearchFields({
|
||||
}
|
||||
|
||||
keyPrefixRef.current = hashCode(JSON.stringify(flatParsedQuery));
|
||||
updateParsedQuery(flatParsedQuery);
|
||||
updateQueryString(reverseParser(flatParsedQuery));
|
||||
onDropDownToggleHandler(false)();
|
||||
}, [onDropDownToggleHandler, fieldsQuery, updateParsedQuery, notifications]);
|
||||
}, [fieldsQuery, notifications, onDropDownToggleHandler, updateQueryString]);
|
||||
|
||||
const clearFilters = useCallback((): void => {
|
||||
keyPrefixRef.current = hashCode(JSON.stringify([]));
|
||||
updateParsedQuery([]);
|
||||
onDropDownToggleHandler(false)();
|
||||
}, [onDropDownToggleHandler, updateParsedQuery]);
|
||||
setFieldsQuery([]);
|
||||
updateQueryString('');
|
||||
}, [updateQueryString]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -113,7 +114,6 @@ function SearchFields({
|
||||
<SearchFieldsActionBar
|
||||
applyUpdate={applyUpdate}
|
||||
clearFilters={clearFilters}
|
||||
fieldsQuery={fieldsQuery}
|
||||
/>
|
||||
<Suggestions applySuggestion={addSuggestedField} />
|
||||
</>
|
||||
|
||||
@@ -36,11 +36,7 @@ function SearchFilter({
|
||||
getLogsAggregate,
|
||||
getLogsFields,
|
||||
}: SearchFilterProps): JSX.Element {
|
||||
const {
|
||||
updateParsedQuery,
|
||||
updateQueryString,
|
||||
queryString,
|
||||
} = useSearchParser();
|
||||
const { updateQueryString, queryString } = useSearchParser();
|
||||
const [searchText, setSearchText] = useState(queryString);
|
||||
const [showDropDown, setShowDropDown] = useState(false);
|
||||
const searchRef = useRef<InputRef>(null);
|
||||
@@ -187,8 +183,8 @@ function SearchFilter({
|
||||
content={
|
||||
<DropDownContainer>
|
||||
<SearchFields
|
||||
updateQueryString={updateQueryString}
|
||||
onDropDownToggleHandler={onDropDownToggleHandler}
|
||||
updateParsedQuery={updateParsedQuery as never}
|
||||
/>
|
||||
</DropDownContainer>
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import history from 'lib/history';
|
||||
import { parseQuery, reverseParser } from 'lib/logql';
|
||||
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
||||
import { parseQuery } from 'lib/logql';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@@ -21,7 +20,6 @@ import { getGlobalTime } from './utils';
|
||||
export function useSearchParser(): {
|
||||
queryString: string;
|
||||
parsedQuery: unknown;
|
||||
updateParsedQuery: (arg0: ILogQLParsedQueryItem[]) => void;
|
||||
updateQueryString: (arg0: string) => void;
|
||||
} {
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
@@ -64,7 +62,7 @@ export function useSearchParser(): {
|
||||
},
|
||||
// need to hide this warning as we don't want to update the query string on every change
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[dispatch, parsedQuery],
|
||||
[dispatch, parsedQuery, selectedTime],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -75,32 +73,9 @@ export function useSearchParser(): {
|
||||
}
|
||||
}, [queryString, updateQueryString, parsedFilters]);
|
||||
|
||||
const updateParsedQuery = useCallback(
|
||||
(updatedParsedPayload: ILogQLParsedQueryItem[]) => {
|
||||
dispatch({
|
||||
type: SET_SEARCH_QUERY_PARSED_PAYLOAD,
|
||||
payload: updatedParsedPayload,
|
||||
});
|
||||
const reversedParsedQuery = reverseParser(updatedParsedPayload);
|
||||
if (
|
||||
!isEqual(queryString, reversedParsedQuery) ||
|
||||
(queryString === '' && reversedParsedQuery === '')
|
||||
) {
|
||||
dispatch({
|
||||
type: SET_SEARCH_QUERY_STRING,
|
||||
payload: {
|
||||
searchQueryString: reversedParsedQuery,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[dispatch, queryString],
|
||||
);
|
||||
|
||||
return {
|
||||
queryString,
|
||||
parsedQuery,
|
||||
updateParsedQuery,
|
||||
updateQueryString,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { convertMetricKeyToTrace } from 'lib/resourceAttributes';
|
||||
import React from 'react';
|
||||
|
||||
import { QueryChipContainer, QueryChipItem } from './styles';
|
||||
import { IResourceAttributeQuery } from './types';
|
||||
|
||||
interface IQueryChipProps {
|
||||
queryData: IResourceAttributeQuery;
|
||||
onClose: (id: string) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export default function QueryChip({
|
||||
queryData,
|
||||
onClose,
|
||||
disabled,
|
||||
}: IQueryChipProps): JSX.Element {
|
||||
return (
|
||||
<QueryChipContainer>
|
||||
<QueryChipItem>{convertMetricKeyToTrace(queryData.tagKey)}</QueryChipItem>
|
||||
<QueryChipItem>{queryData.operator}</QueryChipItem>
|
||||
<QueryChipItem
|
||||
closable={!disabled}
|
||||
onClose={(): void => {
|
||||
if (!disabled) onClose(queryData.id);
|
||||
}}
|
||||
>
|
||||
{queryData.tagValue.join(', ')}
|
||||
</QueryChipItem>
|
||||
</QueryChipContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { createMachine } from 'xstate';
|
||||
|
||||
export const ResourceAttributesFilterMachine =
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QBECGsAWAjA9qgThAAQDKYBAxhkQIIB2xAYgJYA2ALmPgHQAqqUANJgAngGIAcgFEAGr0SgADjljN2zHHQUgAHogAcAFgAM3AOz6ATAEYAzJdsA2Y4cOWAnABoQIxAFpDR2tuQ319AFYTcKdbFycAX3jvNExcAmIySmp6JjZOHn4hUTFNACFWAFd8bWVVdU1tPQQzY1MXY2tDdzNHM3dHd0NvXwR7biMTa313S0i+63DE5PRsPEJScnwqWgYiFg4uPgFhcQAlKRIpeSQQWrUNLRumx3Czbg8TR0sbS31jfUcw38fW47gBHmm4XCVms3SWIBSq3SGyyO1yBx4AHlFFxUOwcPhJLJrkoVPcGk9ENYFuF3i5YR0wtEHECEAEgiEmV8zH1DLYzHZ4Yi0utMltsrt9vluNjcfjCWVKtUbnd6o9QE1rMYBtxbGFvsZ3NrZj1WdYOfotUZLX0XEFHEKViKMpttjk9nlDrL8HiCWJzpcSbcyWrGoh3NCQj0zK53P1ph1WeFLLqnJZ2s5vmZLA6kginWsXaj3VLDoUAGqoSpgEp0cpVGohh5hhDWDy0sz8zruakzamWVm-Qyg362V5-AZOayO1KFlHitEejFHKCV6v+i5XRt1ZuU1s52zjNOOaZfdOWIY+RDZ0Hc6ZmKEXqyLPPCudit2Sz08ACSEFYNbSHI27kuquiIOEjiONwjJgrM3RWJYZisgEIJgnYPTmuEdi2OaiR5nQOAQHA2hvsiH4Sui0qFCcIGhnuLSmP0YJuJ2xjJsmKELG8XZTK0tjdHG06vgW5GupRS7St6vrKqSO4UhqVL8TBWp8o4eqdl0A5Xmy3G6gK56-B4uERDOSKiuJi6lgUAhrhUYB0buimtrEKZBDYrxaS0OZca8+ltheybOI4hivGZzrzp+VGHH+AGOQp4EIHy+ghNYnawtG4TsbYvk8QKfHGAJfQ9uF76WSW37xWBTSGJ0qXpd0vRZdEKGPqC2YeO2-zfO4+HxEAA */
|
||||
createMachine({
|
||||
tsTypes: {} as import('./ResourceAttributesFilter.Machine.typegen').Typegen0,
|
||||
initial: 'Idle',
|
||||
states: {
|
||||
TagKey: {
|
||||
on: {
|
||||
NEXT: {
|
||||
actions: 'onSelectOperator',
|
||||
target: 'Operator',
|
||||
},
|
||||
onBlur: {
|
||||
actions: 'onBlurPurge',
|
||||
target: 'Idle',
|
||||
},
|
||||
RESET: {
|
||||
target: 'Idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
Operator: {
|
||||
on: {
|
||||
NEXT: {
|
||||
actions: 'onSelectTagValue',
|
||||
target: 'TagValue',
|
||||
},
|
||||
onBlur: {
|
||||
actions: 'onBlurPurge',
|
||||
target: 'Idle',
|
||||
},
|
||||
RESET: {
|
||||
target: 'Idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
TagValue: {
|
||||
on: {
|
||||
onBlur: {
|
||||
actions: ['onValidateQuery', 'onBlurPurge'],
|
||||
target: 'Idle',
|
||||
},
|
||||
RESET: {
|
||||
target: 'Idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
Idle: {
|
||||
on: {
|
||||
NEXT: {
|
||||
actions: 'onSelectTagKey',
|
||||
description: 'Select Category',
|
||||
target: 'TagKey',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
id: 'Dashboard Search And Filter',
|
||||
});
|
||||
@@ -1,219 +0,0 @@
|
||||
import { CloseCircleFilled } from '@ant-design/icons';
|
||||
import { useMachine } from '@xstate/react';
|
||||
import { Button, Select, Spin } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { convertMetricKeyToTrace } from 'lib/resourceAttributes';
|
||||
import { map } from 'lodash-es';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { ResetInitialData } from 'store/actions/metrics/resetInitialData';
|
||||
import { SetResourceAttributeQueries } from 'store/actions/metrics/setResourceAttributeQueries';
|
||||
import { AppState } from 'store/reducers';
|
||||
import MetricReducer from 'types/reducer/metrics';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import QueryChip from './QueryChip';
|
||||
import { ResourceAttributesFilterMachine } from './ResourceAttributesFilter.Machine';
|
||||
import { QueryChipItem, SearchContainer } from './styles';
|
||||
import { IOption, IResourceAttributeQuery } from './types';
|
||||
import { createQuery, GetTagKeys, GetTagValues, OperatorSchema } from './utils';
|
||||
|
||||
function ResourceAttributesFilter(): JSX.Element | null {
|
||||
const dispatch = useDispatch();
|
||||
const [disabled, setDisabled] = useState(
|
||||
!(history.location.pathname === ROUTES.APPLICATION),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const unListen = history.listen(({ pathname }) => {
|
||||
setDisabled(!(pathname === ROUTES.APPLICATION));
|
||||
});
|
||||
return (): void => {
|
||||
if (!history.location.pathname.startsWith(`${ROUTES.APPLICATION}/`)) {
|
||||
dispatch(ResetInitialData());
|
||||
}
|
||||
unListen();
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
|
||||
(state) => state.metrics,
|
||||
);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>([]);
|
||||
const [staging, setStaging] = useState<string[]>([]);
|
||||
const [queries, setQueries] = useState<IResourceAttributeQuery[]>([]);
|
||||
const [optionsData, setOptionsData] = useState<{
|
||||
mode: undefined | 'tags' | 'multiple';
|
||||
options: IOption[];
|
||||
}>({
|
||||
mode: undefined,
|
||||
options: [],
|
||||
});
|
||||
|
||||
const dispatchQueries = (updatedQueries: IResourceAttributeQuery[]): void => {
|
||||
dispatch(SetResourceAttributeQueries(updatedQueries));
|
||||
};
|
||||
const handleLoading = (isLoading: boolean): void => {
|
||||
setLoading(isLoading);
|
||||
if (isLoading) {
|
||||
setOptionsData({ mode: undefined, options: [] });
|
||||
}
|
||||
};
|
||||
const [state, send] = useMachine(ResourceAttributesFilterMachine, {
|
||||
actions: {
|
||||
onSelectTagKey: () => {
|
||||
handleLoading(true);
|
||||
GetTagKeys()
|
||||
.then((tagKeys) => setOptionsData({ options: tagKeys, mode: undefined }))
|
||||
.finally(() => {
|
||||
handleLoading(false);
|
||||
});
|
||||
},
|
||||
onSelectOperator: () => {
|
||||
setOptionsData({ options: OperatorSchema, mode: undefined });
|
||||
},
|
||||
onSelectTagValue: () => {
|
||||
handleLoading(true);
|
||||
|
||||
GetTagValues(staging[0])
|
||||
.then((tagValuesOptions) =>
|
||||
setOptionsData({ options: tagValuesOptions, mode: 'multiple' }),
|
||||
)
|
||||
.finally(() => {
|
||||
handleLoading(false);
|
||||
});
|
||||
},
|
||||
onBlurPurge: () => {
|
||||
setSelectedValues([]);
|
||||
setStaging([]);
|
||||
},
|
||||
onValidateQuery: (): void => {
|
||||
if (staging.length < 2 || selectedValues.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const generatedQuery = createQuery([...staging, selectedValues]);
|
||||
if (generatedQuery) {
|
||||
dispatchQueries([...queries, generatedQuery]);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setQueries(resourceAttributeQueries);
|
||||
}, [resourceAttributeQueries]);
|
||||
|
||||
const handleFocus = (): void => {
|
||||
if (state.value === 'Idle') {
|
||||
send('NEXT');
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur = (): void => {
|
||||
send('onBlur');
|
||||
};
|
||||
const handleChange = (value: never): void => {
|
||||
if (!optionsData.mode) {
|
||||
setStaging((prevStaging) => [...prevStaging, value]);
|
||||
setSelectedValues([]);
|
||||
send('NEXT');
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedValues([...value]);
|
||||
};
|
||||
|
||||
const handleClose = (id: string): void => {
|
||||
dispatchQueries(queries.filter((queryData) => queryData.id !== id));
|
||||
};
|
||||
|
||||
const handleClearAll = (): void => {
|
||||
send('RESET');
|
||||
dispatchQueries([]);
|
||||
setStaging([]);
|
||||
setSelectedValues([]);
|
||||
};
|
||||
const disabledAndEmpty = !!(
|
||||
!queries.length &&
|
||||
!staging.length &&
|
||||
!selectedValues.length &&
|
||||
disabled
|
||||
);
|
||||
const disabledOrEmpty = !!(
|
||||
queries.length ||
|
||||
staging.length ||
|
||||
selectedValues.length ||
|
||||
disabled
|
||||
);
|
||||
|
||||
if (disabledAndEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SearchContainer disabled={disabled}>
|
||||
<div
|
||||
style={{
|
||||
maxWidth: disabled ? '100%' : '70%',
|
||||
display: 'flex',
|
||||
overflowX: 'auto',
|
||||
}}
|
||||
>
|
||||
{map(
|
||||
queries,
|
||||
(query): JSX.Element => (
|
||||
<QueryChip
|
||||
disabled={disabled}
|
||||
key={query.id}
|
||||
queryData={query}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
{map(staging, (item, idx) => (
|
||||
<QueryChipItem key={uuid()}>
|
||||
{idx === 0 ? convertMetricKeyToTrace(item) : item}
|
||||
</QueryChipItem>
|
||||
))}
|
||||
</div>
|
||||
{!disabled && (
|
||||
<Select
|
||||
placeholder={
|
||||
disabledOrEmpty ? '' : 'Search and Filter based on resource attributes.'
|
||||
}
|
||||
disabled={disabled}
|
||||
onChange={handleChange}
|
||||
bordered={false}
|
||||
value={selectedValues as never}
|
||||
style={{ flex: 1 }}
|
||||
options={optionsData.options}
|
||||
mode={optionsData?.mode}
|
||||
showArrow={false}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
notFoundContent={
|
||||
loading ? (
|
||||
<span>
|
||||
<Spin size="small" /> Loading...{' '}
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
No resource attributes available to filter. Please refer docs to send
|
||||
attributes.
|
||||
</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(queries.length || staging.length || selectedValues.length) && !disabled ? (
|
||||
<Button onClick={handleClearAll} icon={<CloseCircleFilled />} type="text" />
|
||||
) : null}
|
||||
</SearchContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResourceAttributesFilter;
|
||||
@@ -1,11 +0,0 @@
|
||||
export interface IOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IResourceAttributeQuery {
|
||||
id: string;
|
||||
tagKey: string;
|
||||
operator: string;
|
||||
tagValue: string[];
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import {
|
||||
getResourceAttributesTagKeys,
|
||||
getResourceAttributesTagValues,
|
||||
} from 'api/metrics/getResourceAttributes';
|
||||
import { OperatorConversions } from 'constants/resourceAttributes';
|
||||
import { convertMetricKeyToTrace } from 'lib/resourceAttributes';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { IOption, IResourceAttributeQuery } from './types';
|
||||
|
||||
export const OperatorSchema: IOption[] = OperatorConversions.map(
|
||||
(operator) => ({
|
||||
label: operator.label,
|
||||
value: operator.label,
|
||||
}),
|
||||
);
|
||||
|
||||
export const GetTagKeys = async (): Promise<IOption[]> => {
|
||||
// if (TagKeysCache) {
|
||||
// return new Promise((resolve) => {
|
||||
// resolve(TagKeysCache);
|
||||
// });
|
||||
// }
|
||||
const { payload } = await getResourceAttributesTagKeys({
|
||||
metricName: 'signoz_calls_total',
|
||||
match: 'resource_',
|
||||
});
|
||||
if (!payload || !payload?.data) {
|
||||
return [];
|
||||
}
|
||||
return payload.data.map((tagKey: string) => ({
|
||||
label: convertMetricKeyToTrace(tagKey),
|
||||
value: tagKey,
|
||||
}));
|
||||
};
|
||||
|
||||
export const GetTagValues = async (tagKey: string): Promise<IOption[]> => {
|
||||
const { payload } = await getResourceAttributesTagValues({
|
||||
tagKey,
|
||||
metricName: 'signoz_calls_total',
|
||||
});
|
||||
|
||||
if (!payload || !payload?.data) {
|
||||
return [];
|
||||
}
|
||||
return payload.data.map((tagValue: string) => ({
|
||||
label: tagValue,
|
||||
value: tagValue,
|
||||
}));
|
||||
};
|
||||
|
||||
export const createQuery = (
|
||||
selectedItems: Array<string | string[]> = [],
|
||||
): IResourceAttributeQuery | null => {
|
||||
if (selectedItems.length === 3) {
|
||||
return {
|
||||
id: uuid().slice(0, 8),
|
||||
tagKey: selectedItems[0] as string,
|
||||
operator: selectedItems[1] as string,
|
||||
tagValue: selectedItems[2] as string[],
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -4,21 +4,20 @@ import {
|
||||
databaseCallsAvgDuration,
|
||||
databaseCallsRPS,
|
||||
} from 'container/MetricsApplication/MetricsPageQueries/DBCallQueries';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import {
|
||||
convertRawQueriesToTraceSelectedTags,
|
||||
resourceAttributesToTagFilterItems,
|
||||
} from 'lib/resourceAttributes';
|
||||
} from 'hooks/useResourceAttribute/utils';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import MetricReducer from 'types/reducer/metrics';
|
||||
|
||||
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
|
||||
import { Button } from './styles';
|
||||
import {
|
||||
dbSystemTags,
|
||||
handleNonInQueryRange,
|
||||
onGraphClickHandler,
|
||||
onViewTracePopupClick,
|
||||
} from './util';
|
||||
@@ -26,22 +25,22 @@ import {
|
||||
function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
|
||||
const { servicename } = useParams<{ servicename?: string }>();
|
||||
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
||||
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
|
||||
(state) => state.metrics,
|
||||
);
|
||||
const { queries } = useResourceAttribute();
|
||||
|
||||
const tagFilterItems = useMemo(
|
||||
() => resourceAttributesToTagFilterItems(resourceAttributeQueries) || [],
|
||||
[resourceAttributeQueries],
|
||||
() =>
|
||||
handleNonInQueryRange(resourceAttributesToTagFilterItems(queries)) || [],
|
||||
[queries],
|
||||
);
|
||||
|
||||
const selectedTraceTags: string = useMemo(
|
||||
() =>
|
||||
JSON.stringify(
|
||||
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries).concat(
|
||||
...dbSystemTags,
|
||||
) || [],
|
||||
convertRawQueriesToTraceSelectedTags(queries).concat(...dbSystemTags) || [],
|
||||
),
|
||||
[resourceAttributeQueries],
|
||||
[queries],
|
||||
);
|
||||
|
||||
const legend = '{{db_system}}';
|
||||
|
||||
const databaseCallsRPSWidget = useMemo(
|
||||
|
||||
@@ -6,33 +6,34 @@ import {
|
||||
externalCallErrorPercent,
|
||||
externalCallRpsByAddress,
|
||||
} from 'container/MetricsApplication/MetricsPageQueries/ExternalQueries';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import {
|
||||
convertRawQueriesToTraceSelectedTags,
|
||||
resourceAttributesToTagFilterItems,
|
||||
} from 'lib/resourceAttributes';
|
||||
} from 'hooks/useResourceAttribute/utils';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import MetricReducer from 'types/reducer/metrics';
|
||||
|
||||
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
|
||||
import { legend } from './constant';
|
||||
import { Button } from './styles';
|
||||
import { onGraphClickHandler, onViewTracePopupClick } from './util';
|
||||
import {
|
||||
handleNonInQueryRange,
|
||||
onGraphClickHandler,
|
||||
onViewTracePopupClick,
|
||||
} from './util';
|
||||
|
||||
function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
||||
|
||||
const { servicename } = useParams<{ servicename?: string }>();
|
||||
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
|
||||
(state) => state.metrics,
|
||||
);
|
||||
const { queries } = useResourceAttribute();
|
||||
|
||||
const tagFilterItems = useMemo(
|
||||
() => resourceAttributesToTagFilterItems(resourceAttributeQueries) || [],
|
||||
[resourceAttributeQueries],
|
||||
() =>
|
||||
handleNonInQueryRange(resourceAttributesToTagFilterItems(queries)) || [],
|
||||
[queries],
|
||||
);
|
||||
|
||||
const externalCallErrorWidget = useMemo(
|
||||
@@ -51,11 +52,8 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
);
|
||||
|
||||
const selectedTraceTags = useMemo(
|
||||
() =>
|
||||
JSON.stringify(
|
||||
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [],
|
||||
),
|
||||
[resourceAttributeQueries],
|
||||
() => JSON.stringify(convertRawQueriesToTraceSelectedTags(queries) || []),
|
||||
[queries],
|
||||
);
|
||||
|
||||
const externalCallDurationWidget = useMemo(
|
||||
|
||||
@@ -3,13 +3,14 @@ import Graph from 'components/Graph';
|
||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder';
|
||||
import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond';
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
import history from 'lib/history';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import {
|
||||
convertRawQueriesToTraceSelectedTags,
|
||||
resourceAttributesToTagFilterItems,
|
||||
} from 'lib/resourceAttributes';
|
||||
} from 'hooks/useResourceAttribute/utils';
|
||||
import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond';
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -25,7 +26,11 @@ import {
|
||||
import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles';
|
||||
import TopOperationsTable from '../TopOperationsTable';
|
||||
import { Button } from './styles';
|
||||
import { onGraphClickHandler, onViewTracePopupClick } from './util';
|
||||
import {
|
||||
handleNonInQueryRange,
|
||||
onGraphClickHandler,
|
||||
onViewTracePopupClick,
|
||||
} from './util';
|
||||
|
||||
function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
||||
const { servicename } = useParams<{ servicename?: string }>();
|
||||
@@ -54,20 +59,21 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
||||
[handleSetTimeStamp],
|
||||
);
|
||||
|
||||
const {
|
||||
topOperations,
|
||||
serviceOverview,
|
||||
resourceAttributeQueries,
|
||||
topLevelOperations,
|
||||
} = useSelector<AppState, MetricReducer>((state) => state.metrics);
|
||||
const { topOperations, serviceOverview, topLevelOperations } = useSelector<
|
||||
AppState,
|
||||
MetricReducer
|
||||
>((state) => state.metrics);
|
||||
|
||||
const { queries } = useResourceAttribute();
|
||||
|
||||
const selectedTraceTags: string = JSON.stringify(
|
||||
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [],
|
||||
convertRawQueriesToTraceSelectedTags(queries) || [],
|
||||
);
|
||||
|
||||
const tagFilterItems = useMemo(
|
||||
() => resourceAttributesToTagFilterItems(resourceAttributeQueries) || [],
|
||||
[resourceAttributeQueries],
|
||||
() =>
|
||||
handleNonInQueryRange(resourceAttributesToTagFilterItems(queries)) || [],
|
||||
[queries],
|
||||
);
|
||||
|
||||
const operationPerSecWidget = useMemo(
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
|
||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { IQueryBuilderTagFilterItems } from 'types/api/dashboard/getAll';
|
||||
import { Tags } from 'types/reducer/trace';
|
||||
|
||||
export const dbSystemTags: Tags[] = [
|
||||
@@ -60,7 +61,7 @@ export function onGraphClickHandler(
|
||||
const points = chart.getElementsAtEventForMode(
|
||||
event.native,
|
||||
'nearest',
|
||||
{ intersect: true },
|
||||
{ intersect: false },
|
||||
true,
|
||||
);
|
||||
const id = `${from}_button`;
|
||||
@@ -84,3 +85,16 @@ export function onGraphClickHandler(
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const handleNonInQueryRange = (
|
||||
tags: IQueryBuilderTagFilterItems[],
|
||||
): IQueryBuilderTagFilterItems[] =>
|
||||
tags.map((tag) => {
|
||||
if (tag.op === 'Not IN') {
|
||||
return {
|
||||
...tag,
|
||||
op: 'NIN',
|
||||
};
|
||||
}
|
||||
return tag;
|
||||
});
|
||||
|
||||
@@ -3,25 +3,23 @@ import { ColumnsType } from 'antd/lib/table';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||
import history from 'lib/history';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import MetricReducer from 'types/reducer/metrics';
|
||||
|
||||
function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
|
||||
(state) => state.metrics,
|
||||
);
|
||||
const { queries } = useResourceAttribute();
|
||||
|
||||
const selectedTraceTags: string = JSON.stringify(
|
||||
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [],
|
||||
convertRawQueriesToTraceSelectedTags(queries) || [],
|
||||
);
|
||||
|
||||
const { data } = props;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import RouteTab from 'components/RouteTab';
|
||||
import ROUTES from 'constants/routes';
|
||||
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
|
||||
import React, { memo } from 'react';
|
||||
import { generatePath, useParams } from 'react-router-dom';
|
||||
import { useLocation } from 'react-use';
|
||||
|
||||
import { getWidgetQueryBuilder } from './MetricsApplication.factory';
|
||||
import ResourceAttributesFilter from './ResourceAttributesFilter';
|
||||
import DBCall from './Tabs/DBCall';
|
||||
import External from './Tabs/External';
|
||||
import Overview from './Tabs/Overview';
|
||||
|
||||
@@ -129,7 +129,8 @@ function VariableItem({
|
||||
|
||||
useEffect(() => {
|
||||
getOptions();
|
||||
}, [getOptions]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleChange = (value: string | string[]): void => {
|
||||
if (
|
||||
|
||||
@@ -2,31 +2,31 @@
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
eventsCausingActions: {
|
||||
onSelectOperator: 'NEXT';
|
||||
onBlurPurge: 'onBlur';
|
||||
onSelectTagValue: 'NEXT';
|
||||
onValidateQuery: 'onBlur';
|
||||
onSelectTagKey: 'NEXT';
|
||||
};
|
||||
internalEvents: {
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
};
|
||||
invokeSrcNameMap: {};
|
||||
missingImplementations: {
|
||||
actions:
|
||||
| 'onSelectOperator'
|
||||
| 'onBlurPurge'
|
||||
| 'onSelectOperator'
|
||||
| 'onSelectTagKey'
|
||||
| 'onSelectTagValue'
|
||||
| 'onValidateQuery'
|
||||
| 'onSelectTagKey';
|
||||
services: never;
|
||||
guards: never;
|
||||
| 'onValidateQuery';
|
||||
delays: never;
|
||||
guards: never;
|
||||
services: never;
|
||||
};
|
||||
eventsCausingActions: {
|
||||
onBlurPurge: 'onBlur';
|
||||
onSelectOperator: 'NEXT';
|
||||
onSelectTagKey: 'NEXT';
|
||||
onSelectTagValue: 'NEXT';
|
||||
onValidateQuery: 'onBlur';
|
||||
};
|
||||
eventsCausingServices: {};
|
||||
eventsCausingGuards: {};
|
||||
eventsCausingDelays: {};
|
||||
matchesStates: 'TagKey' | 'Operator' | 'TagValue' | 'Idle';
|
||||
eventsCausingGuards: {};
|
||||
eventsCausingServices: {};
|
||||
matchesStates: 'Idle' | 'Operator' | 'TagKey' | 'TagValue';
|
||||
tags: never;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/* eslint-disable */
|
||||
//@ts-nocheck
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { Button, Tabs } from 'antd';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||
import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import { QueryBuilder } from 'container/QueryBuilder';
|
||||
import { cloneDeep, isEqual } from 'lodash-es';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
@@ -31,6 +30,7 @@ import ClickHouseQueryContainer from './QueryBuilder/clickHouse';
|
||||
import PromQLQueryContainer from './QueryBuilder/promQL';
|
||||
import QueryBuilderQueryContainer from './QueryBuilder/queryBuilder';
|
||||
import TabHeader from './TabHeader';
|
||||
import { IHandleUpdatedQuery } from './types';
|
||||
import { getQueryKey } from './utils/getQueryKey';
|
||||
import { showUnstagedStashConfirmBox } from './utils/userSettings';
|
||||
|
||||
@@ -54,9 +54,7 @@ function QuerySection({
|
||||
const { search } = useLocation();
|
||||
const { widgets } = selectedDashboards.data;
|
||||
|
||||
const urlQuery = useMemo(() => {
|
||||
return new URLSearchParams(search);
|
||||
}, [search]);
|
||||
const urlQuery = useMemo(() => new URLSearchParams(search), [search]);
|
||||
|
||||
const getWidget = useCallback(() => {
|
||||
const widgetId = urlQuery.get('widgetId');
|
||||
@@ -169,6 +167,9 @@ function QuerySection({
|
||||
}
|
||||
selectedGraph={selectedGraph}
|
||||
/>
|
||||
|
||||
// TODO: uncomment for testing new QueryBuilder
|
||||
// <QueryBuilder />
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import {
|
||||
IBuilderFormula,
|
||||
IBuilderQuery,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export type QueryBuilderConfig =
|
||||
| {
|
||||
queryVariant: 'static';
|
||||
initialDataSource: DataSource;
|
||||
}
|
||||
| { queryVariant: 'dropdown' };
|
||||
|
||||
export type QueryBuilderProps = {
|
||||
queryData: IBuilderQuery[];
|
||||
queryFormula: IBuilderFormula[];
|
||||
config?: QueryBuilderConfig;
|
||||
};
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
import { useQueryBuilder } from 'hooks/useQueryBuilder';
|
||||
import React from 'react';
|
||||
|
||||
// ** Components
|
||||
import { Query } from './components';
|
||||
// ** Types
|
||||
import { QueryBuilderProps } from './QueryBuilder.interfaces';
|
||||
|
||||
// TODO: temporary eslint disable while variable isn't used
|
||||
// TODO: I think it can be components switcher, because if we have different views based on the data source, we can render based on source
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function QueryBuilder(props: QueryBuilderProps): JSX.Element {
|
||||
// TODO: temporary doesn't use
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function QueryBuilder({ config }: QueryBuilderProps): JSX.Element {
|
||||
const { queryBuilderData } = useQueryBuilder();
|
||||
|
||||
// Here we can use Form from antd library and fill context data or edit
|
||||
@@ -19,5 +19,17 @@ export function QueryBuilder(props: QueryBuilderProps): JSX.Element {
|
||||
// Each component can be part of antd Form list where we can add or remove items
|
||||
// Also need decide to make a copy of queryData for working with form or not and after it set the full new list with formulas or queries to the context
|
||||
// With button to add him
|
||||
return <div>null</div>;
|
||||
return (
|
||||
<div>
|
||||
{queryBuilderData.queryData.map((query, index) => (
|
||||
<Query
|
||||
key={query.queryName}
|
||||
index={index}
|
||||
isAvailableToDisable={queryBuilderData.queryData.length > 1}
|
||||
queryVariant={config?.queryVariant || 'dropdown'}
|
||||
query={query}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { SelectProps } from 'antd';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export type QueryLabelProps = {
|
||||
onChange: (value: DataSource) => void;
|
||||
} & Omit<SelectProps, 'onChange'>;
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Select } from 'antd';
|
||||
import React from 'react';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { SelectOption } from 'types/common/select';
|
||||
// ** Helpers
|
||||
import { transformToUpperCase } from 'utils/transformToUpperCase';
|
||||
|
||||
// ** Types
|
||||
import { QueryLabelProps } from './DataSourceDropdown.interfaces';
|
||||
|
||||
const dataSourceMap = [DataSource.LOGS, DataSource.METRICS, DataSource.TRACES];
|
||||
|
||||
export function DataSourceDropdown(props: QueryLabelProps): JSX.Element {
|
||||
const { onChange, value, style } = props;
|
||||
|
||||
const dataSourceOptions: SelectOption<
|
||||
DataSource,
|
||||
string
|
||||
>[] = dataSourceMap.map((source) => ({
|
||||
label: transformToUpperCase(source),
|
||||
value: source,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Select
|
||||
defaultValue={dataSourceOptions[0].value}
|
||||
options={dataSourceOptions}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { DataSourceDropdown } from './DataSourceDropdown';
|
||||
@@ -0,0 +1,6 @@
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
export type FilterLabelProps = {
|
||||
label: string;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const StyledLabel = styled.div`
|
||||
padding: 0 0.6875rem;
|
||||
min-width: 6.5rem;
|
||||
width: fit-content;
|
||||
min-height: 2rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 0.125rem;
|
||||
border: 0.0625rem solid #434343;
|
||||
background-color: #141414;
|
||||
`;
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
// ** Types
|
||||
import { FilterLabelProps } from './FilterLabel.interfaces';
|
||||
// ** Styles
|
||||
import { StyledLabel } from './FilterLabel.styled';
|
||||
|
||||
export function FilterLabel({ label }: FilterLabelProps): JSX.Element {
|
||||
return <StyledLabel>{label}</StyledLabel>;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { FilterLabel } from './FilterLabel';
|
||||
@@ -0,0 +1,2 @@
|
||||
// TODO: temporary type
|
||||
export type FormulaProps = { test: string };
|
||||
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export function Formula(): JSX.Element {
|
||||
return <div>null</div>;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { Formula } from './Formula';
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
export type ListMarkerProps = {
|
||||
isDisabled: boolean;
|
||||
labelName: string;
|
||||
@@ -5,4 +7,5 @@ export type ListMarkerProps = {
|
||||
className?: string;
|
||||
isAvailableToDisable: boolean;
|
||||
toggleDisabled: (index: number) => void;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ export function ListMarker({
|
||||
isAvailableToDisable,
|
||||
className,
|
||||
toggleDisabled,
|
||||
style,
|
||||
}: ListMarkerProps): JSX.Element {
|
||||
const buttonProps: Partial<ButtonProps> = isAvailableToDisable
|
||||
? {
|
||||
@@ -29,6 +30,7 @@ export function ListMarker({
|
||||
icon={buttonProps.icon}
|
||||
onClick={buttonProps.onClick}
|
||||
className={className}
|
||||
style={{ marginRight: '0.1rem', ...style }}
|
||||
>
|
||||
{labelName}
|
||||
</StyledButton>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export type QueryProps = {
|
||||
index: number;
|
||||
isAvailableToDisable: boolean;
|
||||
query: IBuilderQueryForm;
|
||||
queryVariant: 'static' | 'dropdown';
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import { Col, Row } from 'antd';
|
||||
// ** Components
|
||||
import {
|
||||
DataSourceDropdown,
|
||||
FilterLabel,
|
||||
ListMarker,
|
||||
} from 'container/QueryBuilder/components';
|
||||
import {
|
||||
AggregatorFilter,
|
||||
OperatorsSelect,
|
||||
} from 'container/QueryBuilder/filters';
|
||||
// Context
|
||||
import { useQueryBuilder } from 'hooks/useQueryBuilder';
|
||||
// ** Hooks
|
||||
import React from 'react';
|
||||
import { AutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
// ** Constants
|
||||
import {
|
||||
LogsAggregatorOperator,
|
||||
MetricAggregateOperator,
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { transformToUpperCase } from 'utils/transformToUpperCase';
|
||||
|
||||
// ** Types
|
||||
import { QueryProps } from './Query.interfaces';
|
||||
|
||||
const mapOfOperators: Record<DataSource, string[]> = {
|
||||
metrics: Object.values(MetricAggregateOperator),
|
||||
logs: Object.values(LogsAggregatorOperator),
|
||||
traces: Object.values(TracesAggregatorOperator),
|
||||
};
|
||||
|
||||
export function Query({
|
||||
index,
|
||||
isAvailableToDisable,
|
||||
queryVariant,
|
||||
query,
|
||||
}: QueryProps): JSX.Element {
|
||||
const { handleSetQueryData } = useQueryBuilder();
|
||||
|
||||
const currentListOfOperators = mapOfOperators[query.dataSource];
|
||||
|
||||
const handleChangeOperator = (value: string): void => {
|
||||
handleSetQueryData(index, { aggregateOperator: value });
|
||||
};
|
||||
|
||||
const handleChangeDataSource = (nextSource: DataSource): void => {
|
||||
handleSetQueryData(index, { dataSource: nextSource });
|
||||
};
|
||||
|
||||
const handleToggleDisableQuery = (): void => {
|
||||
handleSetQueryData(index, { disabled: !query.disabled });
|
||||
};
|
||||
|
||||
const handleChangeAggregatorAttribute = (value: AutocompleteData): void => {
|
||||
handleSetQueryData(index, { aggregateAttribute: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<Row style={{ gap: '0.75rem' }}>
|
||||
<Col span={12}>
|
||||
<ListMarker
|
||||
isDisabled={query.disabled}
|
||||
toggleDisabled={handleToggleDisableQuery}
|
||||
labelName={query.queryName}
|
||||
index={index}
|
||||
isAvailableToDisable={isAvailableToDisable}
|
||||
/>
|
||||
{queryVariant === 'dropdown' ? (
|
||||
<DataSourceDropdown
|
||||
onChange={handleChangeDataSource}
|
||||
value={query.dataSource}
|
||||
/>
|
||||
) : (
|
||||
<FilterLabel label={transformToUpperCase(query.dataSource)} />
|
||||
)}
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<OperatorsSelect
|
||||
value={query.aggregateOperator || currentListOfOperators[0]}
|
||||
onChange={handleChangeOperator}
|
||||
operators={currentListOfOperators}
|
||||
style={{ minWidth: 104, marginRight: '0.75rem' }}
|
||||
/>
|
||||
<AggregatorFilter
|
||||
onChange={handleChangeAggregatorAttribute}
|
||||
query={query}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { Query } from './Query';
|
||||
@@ -1,17 +0,0 @@
|
||||
import { SelectProps } from 'antd';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
type StaticLabel = { variant: 'static'; dataSource: DataSource };
|
||||
|
||||
export type DropdownLabel = {
|
||||
variant: 'dropdown';
|
||||
onChange: (value: DataSource) => void;
|
||||
} & Omit<SelectProps, 'onChange'>;
|
||||
|
||||
export type QueryLabelProps = StaticLabel | DropdownLabel;
|
||||
|
||||
export function isLabelDropdown(
|
||||
label: QueryLabelProps,
|
||||
): label is DropdownLabel {
|
||||
return label.variant === 'dropdown';
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Select } from 'antd';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
// ** Types
|
||||
import { DropdownLabel } from './QueryLabel.interfaces';
|
||||
|
||||
const LabelStyle = css`
|
||||
width: fit-content;
|
||||
min-width: 5.75rem;
|
||||
`;
|
||||
|
||||
export const StyledSingleLabel = styled(Select)`
|
||||
pointer-events: none;
|
||||
${LabelStyle}
|
||||
`;
|
||||
|
||||
export const StyledDropdownLabel = styled(Select)<DropdownLabel>`
|
||||
${LabelStyle}
|
||||
`;
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Select } from 'antd';
|
||||
import React from 'react';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { SelectOption } from 'types/common/select';
|
||||
|
||||
// ** Types
|
||||
import { isLabelDropdown, QueryLabelProps } from './QueryLabel.interfaces';
|
||||
// ** Styles
|
||||
import { StyledSingleLabel } from './QueryLabel.styled';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const dataSourceMap = [DataSource.LOGS, DataSource.METRICS, DataSource.TRACES];
|
||||
|
||||
export function QueryLabel(props: QueryLabelProps): JSX.Element {
|
||||
const isDropdown = isLabelDropdown(props);
|
||||
|
||||
if (!isDropdown) {
|
||||
const { dataSource } = props;
|
||||
|
||||
return (
|
||||
<StyledSingleLabel
|
||||
defaultValue={dataSource}
|
||||
showArrow={false}
|
||||
dropdownStyle={{ display: 'none' }}
|
||||
>
|
||||
<Option value={dataSource}>{dataSource}</Option>
|
||||
</StyledSingleLabel>
|
||||
);
|
||||
}
|
||||
|
||||
const { onChange } = props;
|
||||
|
||||
const dataSourceOptions: SelectOption<
|
||||
DataSource,
|
||||
string
|
||||
>[] = dataSourceMap.map((source) => ({
|
||||
label: source.charAt(0).toUpperCase() + source.slice(1),
|
||||
value: source,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Select
|
||||
defaultValue={dataSourceOptions[0].value}
|
||||
options={dataSourceOptions}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { QueryLabel } from './QueryLabel';
|
||||
@@ -1,2 +1,5 @@
|
||||
export { DataSourceDropdown } from './DataSourceDropdown';
|
||||
export { FilterLabel } from './FilterLabel';
|
||||
export { Formula } from './Formula';
|
||||
export { ListMarker } from './ListMarker';
|
||||
export { QueryLabel } from './QueryLabel';
|
||||
export { Query } from './Query';
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { AutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export type AgregatorFilterProps = {
|
||||
onChange: (value: AutocompleteData) => void;
|
||||
query: IBuilderQueryForm;
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
// ** Components
|
||||
import { AutoComplete, Spin } from 'antd';
|
||||
// ** Api
|
||||
import { getAggregateAttribute } from 'api/queryBuilder/getAggregateAttribute';
|
||||
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { SelectOption } from 'types/common/select';
|
||||
import { transformToUpperCase } from 'utils/transformToUpperCase';
|
||||
|
||||
// ** Types
|
||||
import { AgregatorFilterProps } from './AggregatorFilter.intefaces';
|
||||
|
||||
export function AggregatorFilter({
|
||||
onChange,
|
||||
query,
|
||||
}: AgregatorFilterProps): JSX.Element {
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
|
||||
const { data, isFetching } = useQuery(
|
||||
[
|
||||
'GET_AGGREGATE_ATTRIBUTE',
|
||||
searchText,
|
||||
query.aggregateOperator,
|
||||
query.dataSource,
|
||||
],
|
||||
async () =>
|
||||
getAggregateAttribute({
|
||||
aggregateOperator: query.aggregateOperator,
|
||||
dataSource: query.dataSource,
|
||||
searchText,
|
||||
}),
|
||||
{ enabled: !!query.aggregateOperator && !!query.dataSource },
|
||||
);
|
||||
|
||||
const handleSearchAttribute = (searchText: string): void => {
|
||||
setSearchText(searchText);
|
||||
};
|
||||
|
||||
const optionsData: SelectOption<string, string>[] =
|
||||
data?.payload?.attributeKeys?.map((item) => ({
|
||||
label: transformStringWithPrefix({
|
||||
str: item.key,
|
||||
prefix: item.type || '',
|
||||
condition: !item.isColumn,
|
||||
}),
|
||||
value: item.key,
|
||||
})) || [];
|
||||
|
||||
const handleChangeAttribute = (value: string): void => {
|
||||
const currentAttributeObj = data?.payload?.attributeKeys?.find(
|
||||
(item) => item.key === value,
|
||||
) || { key: value, type: null, dataType: null, isColumn: null };
|
||||
|
||||
onChange(currentAttributeObj);
|
||||
};
|
||||
|
||||
const value = useMemo(
|
||||
() =>
|
||||
transformStringWithPrefix({
|
||||
str: query.aggregateAttribute.key,
|
||||
prefix: query.aggregateAttribute.type || '',
|
||||
condition: !query.aggregateAttribute.isColumn,
|
||||
}),
|
||||
[query],
|
||||
);
|
||||
|
||||
return (
|
||||
<AutoComplete
|
||||
showSearch
|
||||
placeholder={`${transformToUpperCase(
|
||||
query.dataSource,
|
||||
)} Name (Start typing to get suggestions)`}
|
||||
style={{ flex: 1, minWidth: 200 }}
|
||||
showArrow={false}
|
||||
filterOption={false}
|
||||
onSearch={handleSearchAttribute}
|
||||
notFoundContent={isFetching ? <Spin size="small" /> : null}
|
||||
options={optionsData}
|
||||
value={value}
|
||||
onChange={handleChangeAttribute}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { AggregatorFilter } from './AggregatorFilter';
|
||||
@@ -0,0 +1,7 @@
|
||||
import { SelectProps } from 'antd';
|
||||
|
||||
export type OperatorsSelectProps = Omit<SelectProps, 'onChange' | 'value'> & {
|
||||
operators: string[];
|
||||
onChange: (value: string) => void;
|
||||
value: string;
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Select } from 'antd';
|
||||
import React from 'react';
|
||||
// ** Types
|
||||
import { SelectOption } from 'types/common/select';
|
||||
// ** Helpers
|
||||
import { transformToUpperCase } from 'utils/transformToUpperCase';
|
||||
|
||||
import { OperatorsSelectProps } from './OperatorsSelect.interfaces';
|
||||
|
||||
export function OperatorsSelect({
|
||||
operators,
|
||||
value,
|
||||
onChange,
|
||||
...props
|
||||
}: OperatorsSelectProps): JSX.Element {
|
||||
const operatorsOptions: SelectOption<string, string>[] = operators.map(
|
||||
(operator) => ({
|
||||
label: transformToUpperCase(operator),
|
||||
value: operator,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
options={operatorsOptions}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { OperatorsSelect } from './OperatorsSelect';
|
||||
2
frontend/src/container/QueryBuilder/filters/index.ts
Normal file
2
frontend/src/container/QueryBuilder/filters/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { AggregatorFilter } from './AggregatorFilter';
|
||||
export { OperatorsSelect } from './OperatorsSelect';
|
||||
@@ -0,0 +1,88 @@
|
||||
import { CloseCircleFilled } from '@ant-design/icons';
|
||||
import { Button, Select, Spin } from 'antd';
|
||||
import useResourceAttribute, {
|
||||
isResourceEmpty,
|
||||
} from 'hooks/useResourceAttribute';
|
||||
import { convertMetricKeyToTrace } from 'hooks/useResourceAttribute/utils';
|
||||
import React, { useMemo } from 'react';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import QueryChip from './components/QueryChip';
|
||||
import { QueryChipItem, SearchContainer } from './styles';
|
||||
|
||||
function ResourceAttributesFilter({
|
||||
suffixIcon,
|
||||
}: ResourceAttributesFilterProps): JSX.Element | null {
|
||||
const {
|
||||
queries,
|
||||
staging,
|
||||
handleClose,
|
||||
handleBlur,
|
||||
handleClearAll,
|
||||
handleFocus,
|
||||
handleChange,
|
||||
selectedQuery,
|
||||
optionsData,
|
||||
loading,
|
||||
} = useResourceAttribute();
|
||||
|
||||
const isEmpty = useMemo(
|
||||
() => isResourceEmpty(queries, staging, selectedQuery),
|
||||
[queries, selectedQuery, staging],
|
||||
);
|
||||
|
||||
return (
|
||||
<SearchContainer>
|
||||
<div>
|
||||
{queries.map((query) => (
|
||||
<QueryChip key={query.id} queryData={query} onClose={handleClose} />
|
||||
))}
|
||||
{staging.map((query, idx) => (
|
||||
<QueryChipItem key={uuid()}>
|
||||
{idx === 0 ? convertMetricKeyToTrace(query) : query}
|
||||
</QueryChipItem>
|
||||
))}
|
||||
</div>
|
||||
<Select
|
||||
placeholder={!isEmpty && 'Search and Filter based on resource attributes.'}
|
||||
onChange={handleChange}
|
||||
bordered={false}
|
||||
value={selectedQuery as never}
|
||||
style={{ flex: 1 }}
|
||||
options={optionsData.options}
|
||||
mode={optionsData?.mode}
|
||||
showArrow={!!suffixIcon}
|
||||
onClick={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onClear={handleClearAll}
|
||||
suffixIcon={suffixIcon}
|
||||
notFoundContent={
|
||||
loading ? (
|
||||
<span>
|
||||
<Spin size="small" /> Loading...
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
No resource attributes available to filter. Please refer docs to send
|
||||
attributes.
|
||||
</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{queries.length || staging.length || selectedQuery.length ? (
|
||||
<Button onClick={handleClearAll} icon={<CloseCircleFilled />} type="text" />
|
||||
) : null}
|
||||
</SearchContainer>
|
||||
);
|
||||
}
|
||||
|
||||
interface ResourceAttributesFilterProps {
|
||||
suffixIcon?: React.ReactNode;
|
||||
}
|
||||
|
||||
ResourceAttributesFilter.defaultProps = {
|
||||
suffixIcon: undefined,
|
||||
};
|
||||
|
||||
export default ResourceAttributesFilter;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { convertMetricKeyToTrace } from 'hooks/useResourceAttribute/utils';
|
||||
import React from 'react';
|
||||
|
||||
import { QueryChipContainer, QueryChipItem } from '../../styles';
|
||||
import { IQueryChipProps } from './types';
|
||||
|
||||
function QueryChip({ queryData, onClose }: IQueryChipProps): JSX.Element {
|
||||
const onCloseHandler = (): void => {
|
||||
onClose(queryData.id);
|
||||
};
|
||||
|
||||
return (
|
||||
<QueryChipContainer>
|
||||
<QueryChipItem>{convertMetricKeyToTrace(queryData.tagKey)}</QueryChipItem>
|
||||
<QueryChipItem>{queryData.operator}</QueryChipItem>
|
||||
<QueryChipItem closable onClose={onCloseHandler}>
|
||||
{queryData.tagValue.join(', ')}
|
||||
</QueryChipItem>
|
||||
</QueryChipContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default QueryChip;
|
||||
@@ -0,0 +1,3 @@
|
||||
import QueryChip from './QueryChip';
|
||||
|
||||
export default QueryChip;
|
||||
@@ -0,0 +1,6 @@
|
||||
import { IResourceAttribute } from 'hooks/useResourceAttribute/types';
|
||||
|
||||
export interface IQueryChipProps {
|
||||
queryData: IResourceAttribute;
|
||||
onClose: (id: string) => void;
|
||||
}
|
||||
3
frontend/src/container/ResourceAttributesFilter/index.ts
Normal file
3
frontend/src/container/ResourceAttributesFilter/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import ResourceAttributesFilter from './ResourceAttributesFilter';
|
||||
|
||||
export default ResourceAttributesFilter;
|
||||
@@ -2,9 +2,7 @@ import { grey } from '@ant-design/colors';
|
||||
import { Tag } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SearchContainer = styled.div<{
|
||||
disabled: boolean;
|
||||
}>`
|
||||
export const SearchContainer = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -12,8 +10,8 @@ export const SearchContainer = styled.div<{
|
||||
padding: 0.2rem;
|
||||
margin: 1rem 0;
|
||||
border: 1px solid #ccc5;
|
||||
${({ disabled }): string => (disabled ? `cursor: not-allowed;` : '')}
|
||||
`;
|
||||
|
||||
export const QueryChipContainer = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -48,7 +48,7 @@ function SideNav(): JSX.Element {
|
||||
const onClickHandler = useCallback(
|
||||
(to: string) => {
|
||||
if (pathname !== to) {
|
||||
history.push(to);
|
||||
history.push(`${to}`);
|
||||
}
|
||||
},
|
||||
[pathname],
|
||||
|
||||
@@ -29,12 +29,12 @@ const menus: SidebarMenu[] = [
|
||||
to: ROUTES.LOGS,
|
||||
name: 'Logs',
|
||||
// tags: ['Beta'],
|
||||
children: [
|
||||
{
|
||||
key: ROUTES.LOGS,
|
||||
label: 'Search',
|
||||
},
|
||||
],
|
||||
// children: [
|
||||
// {
|
||||
// key: ROUTES.LOGS,
|
||||
// label: 'Search',
|
||||
// },
|
||||
// ],
|
||||
},
|
||||
{
|
||||
Icon: DashboardFilled,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import CustomDateTimeModal from './index';
|
||||
|
||||
describe('CustomDateTimeModal', () => {
|
||||
const handleCreate = jest.fn();
|
||||
const handleCancel = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
render(
|
||||
<CustomDateTimeModal
|
||||
visible
|
||||
onCreate={handleCreate}
|
||||
onCancel={handleCancel}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders the modal with title and buttons', () => {
|
||||
expect(screen.getByText('Chose date and time range')).toBeInTheDocument();
|
||||
expect(screen.getByText('Apply')).toBeInTheDocument();
|
||||
expect(screen.getByText('Cancel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('donot calls onCreate when the Apply button is clicked without selecting dates', () => {
|
||||
fireEvent.click(screen.getByText('Apply'));
|
||||
|
||||
expect(handleCreate).toHaveBeenCalledTimes(0);
|
||||
expect(handleCreate).not.toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
it('calls onCancel when Cancel button is clicked', () => {
|
||||
fireEvent.click(screen.getByText('Cancel'));
|
||||
|
||||
expect(handleCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
import { DatePicker, Modal } from 'antd';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import React, { useState } from 'react';
|
||||
@@ -12,18 +11,21 @@ function CustomDateTimeModal({
|
||||
onCreate,
|
||||
onCancel,
|
||||
}: CustomDateTimeModalProps): JSX.Element {
|
||||
const [customDateTimeRange, setCustomDateTimeRange] = useState();
|
||||
const [selectedDate, setDateTime] = useState<DateTimeRangeType>();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function handleRangePickerOk(date_time: any): void {
|
||||
setCustomDateTimeRange(date_time);
|
||||
}
|
||||
const onModalOkHandler = (date_time: any): void => {
|
||||
onCreate(date_time);
|
||||
setDateTime(date_time);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function disabledDate(current: any): boolean {
|
||||
const disabledDate = (current: Dayjs): boolean => {
|
||||
const currentDay = dayjs(current);
|
||||
return currentDay.isAfter(dayjs());
|
||||
}
|
||||
};
|
||||
|
||||
const onOk = (): void => {
|
||||
if (selectedDate) onCreate(selectedDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -32,13 +34,14 @@ function CustomDateTimeModal({
|
||||
okText="Apply"
|
||||
cancelText="Cancel"
|
||||
onCancel={onCancel}
|
||||
style={{ position: 'absolute', top: 60, right: 40 }}
|
||||
onOk={(): void => onCreate(customDateTimeRange || null)}
|
||||
onOk={onOk}
|
||||
>
|
||||
<RangePicker
|
||||
disabledDate={disabledDate}
|
||||
onOk={handleRangePickerOk}
|
||||
allowClear
|
||||
onOk={onModalOkHandler}
|
||||
showTime
|
||||
onCalendarChange={onModalOkHandler}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -178,7 +178,7 @@ function Duration(): JSX.Element {
|
||||
if (value === undefined) {
|
||||
return <div />;
|
||||
}
|
||||
return <div>{`${getMs(value?.toString())}ms`}</div>;
|
||||
return <div>{`${value?.toString()}ms`}</div>;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import {
|
||||
QueryBuilderContext,
|
||||
QueryBuilderContextType,
|
||||
} from 'providers/QueryBuilder';
|
||||
import { QueryBuilderContext } from 'providers/QueryBuilder';
|
||||
import { useContext } from 'react';
|
||||
import { QueryBuilderContextType } from 'types/common/queryBuilder';
|
||||
|
||||
export function useQueryBuilder(): QueryBuilderContextType {
|
||||
return useContext(QueryBuilderContext);
|
||||
|
||||
188
frontend/src/hooks/useResourceAttribute/ResourceProvider.tsx
Normal file
188
frontend/src/hooks/useResourceAttribute/ResourceProvider.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import { useMachine } from '@xstate/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { encode } from 'js-base64';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { whilelistedKeys } from './config';
|
||||
import { ResourceContext } from './context';
|
||||
import { ResourceAttributesFilterMachine } from './machine';
|
||||
import {
|
||||
IResourceAttribute,
|
||||
IResourceAttributeProps,
|
||||
OptionsData,
|
||||
} from './types';
|
||||
import {
|
||||
createQuery,
|
||||
getResourceAttributeQueriesFromURL,
|
||||
GetTagKeys,
|
||||
GetTagValues,
|
||||
mappingWithRoutesAndKeys,
|
||||
OperatorSchema,
|
||||
} from './utils';
|
||||
|
||||
function ResourceProvider({ children }: Props): JSX.Element {
|
||||
const { pathname } = useLocation();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedQuery, setSelectedQueries] = useState<string[]>([]);
|
||||
const [staging, setStaging] = useState<string[]>([]);
|
||||
const [queries, setQueries] = useState<IResourceAttribute[]>(
|
||||
getResourceAttributeQueriesFromURL(),
|
||||
);
|
||||
|
||||
const [optionsData, setOptionsData] = useState<OptionsData>({
|
||||
mode: undefined,
|
||||
options: [],
|
||||
});
|
||||
|
||||
const handleLoading = (isLoading: boolean): void => {
|
||||
setLoading(isLoading);
|
||||
if (isLoading) {
|
||||
setOptionsData({ mode: undefined, options: [] });
|
||||
}
|
||||
};
|
||||
|
||||
const dispatchQueries = useCallback(
|
||||
(queries: IResourceAttribute[]): void => {
|
||||
history.replace({
|
||||
pathname,
|
||||
search:
|
||||
queries && queries.length
|
||||
? `?resourceAttribute=${encode(JSON.stringify(queries))}`
|
||||
: '',
|
||||
});
|
||||
setQueries(queries);
|
||||
},
|
||||
[pathname],
|
||||
);
|
||||
|
||||
const [state, send] = useMachine(ResourceAttributesFilterMachine, {
|
||||
actions: {
|
||||
onSelectTagKey: () => {
|
||||
handleLoading(true);
|
||||
GetTagKeys()
|
||||
.then((tagKeys) =>
|
||||
setOptionsData({
|
||||
options: mappingWithRoutesAndKeys(pathname, tagKeys),
|
||||
mode: undefined,
|
||||
}),
|
||||
)
|
||||
.finally(() => {
|
||||
handleLoading(false);
|
||||
});
|
||||
},
|
||||
onSelectOperator: () => {
|
||||
setOptionsData({ options: OperatorSchema, mode: undefined });
|
||||
},
|
||||
onSelectTagValue: () => {
|
||||
handleLoading(true);
|
||||
|
||||
GetTagValues(staging[0])
|
||||
.then((tagValuesOptions) =>
|
||||
setOptionsData({ options: tagValuesOptions, mode: 'multiple' }),
|
||||
)
|
||||
.finally(() => {
|
||||
handleLoading(false);
|
||||
});
|
||||
},
|
||||
onBlurPurge: () => {
|
||||
setSelectedQueries([]);
|
||||
setStaging([]);
|
||||
},
|
||||
onValidateQuery: (): void => {
|
||||
if (staging.length < 2 || selectedQuery.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const generatedQuery = createQuery([...staging, selectedQuery]);
|
||||
if (generatedQuery) {
|
||||
dispatchQueries([...queries, generatedQuery]);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const handleFocus = useCallback((): void => {
|
||||
if (state.value === 'Idle') {
|
||||
send('NEXT');
|
||||
}
|
||||
}, [send, state.value]);
|
||||
|
||||
const handleBlur = useCallback((): void => {
|
||||
send('onBlur');
|
||||
}, [send]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(value: string): void => {
|
||||
if (!optionsData.mode) {
|
||||
setStaging((prevStaging) => [...prevStaging, value]);
|
||||
setSelectedQueries([]);
|
||||
send('NEXT');
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedQueries([...value]);
|
||||
},
|
||||
[optionsData.mode, send],
|
||||
);
|
||||
|
||||
const handleClose = useCallback(
|
||||
(id: string): void => {
|
||||
dispatchQueries(queries.filter((queryData) => queryData.id !== id));
|
||||
},
|
||||
[dispatchQueries, queries],
|
||||
);
|
||||
|
||||
const handleClearAll = useCallback(() => {
|
||||
send('RESET');
|
||||
dispatchQueries([]);
|
||||
setStaging([]);
|
||||
setQueries([]);
|
||||
setOptionsData({ mode: undefined, options: [] });
|
||||
}, [dispatchQueries, send]);
|
||||
|
||||
const getVisibleQueries = useMemo(() => {
|
||||
if (pathname === ROUTES.SERVICE_MAP) {
|
||||
return queries.filter((query) => whilelistedKeys.includes(query.tagKey));
|
||||
}
|
||||
return queries;
|
||||
}, [queries, pathname]);
|
||||
|
||||
const value: IResourceAttributeProps = useMemo(
|
||||
() => ({
|
||||
queries: getVisibleQueries,
|
||||
staging,
|
||||
handleClearAll,
|
||||
handleClose,
|
||||
handleBlur,
|
||||
handleFocus,
|
||||
loading,
|
||||
handleChange,
|
||||
selectedQuery,
|
||||
optionsData,
|
||||
}),
|
||||
[
|
||||
handleBlur,
|
||||
handleChange,
|
||||
handleClearAll,
|
||||
handleClose,
|
||||
handleFocus,
|
||||
loading,
|
||||
staging,
|
||||
selectedQuery,
|
||||
optionsData,
|
||||
getVisibleQueries,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<ResourceContext.Provider value={value}>{children}</ResourceContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default ResourceProvider;
|
||||
5
frontend/src/hooks/useResourceAttribute/config.ts
Normal file
5
frontend/src/hooks/useResourceAttribute/config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const whilelistedKeys = [
|
||||
'resource_deployment_environment',
|
||||
'resource_k8s_cluster_name',
|
||||
'resource_k8s_cluster_namespace',
|
||||
];
|
||||
7
frontend/src/hooks/useResourceAttribute/context.ts
Normal file
7
frontend/src/hooks/useResourceAttribute/context.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { IResourceAttributeProps } from './types';
|
||||
|
||||
export const ResourceContext = createContext<IResourceAttributeProps>(
|
||||
{} as IResourceAttributeProps,
|
||||
);
|
||||
7
frontend/src/hooks/useResourceAttribute/index.ts
Normal file
7
frontend/src/hooks/useResourceAttribute/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import ResourceProvider from './ResourceProvider';
|
||||
import useResourceAttribute from './useResourceAttribute';
|
||||
import { convertMetricKeyToTrace, isResourceEmpty } from './utils';
|
||||
|
||||
export default useResourceAttribute;
|
||||
|
||||
export { convertMetricKeyToTrace, isResourceEmpty, ResourceProvider };
|
||||
61
frontend/src/hooks/useResourceAttribute/machine.ts
Normal file
61
frontend/src/hooks/useResourceAttribute/machine.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { createMachine } from 'xstate';
|
||||
|
||||
export const ResourceAttributesFilterMachine =
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QBECGsAWAjA9qgThAAQDKYBAxhkQIIB2xAYgJYA2ALmPgHQAqqUANJgAngGIAcgFEAGrwDaABgC6iUAAccsZu2Y46akAA9EATkUB2bgEYAbBYsBWWwA5HAFkW3F7gDQgRRABaU3duFwsXAGZbWwAmF3co01jTAF80-zRMXAJiMkpqeiY2Th5+IVExfQAhVgBXfCVVJBBNbV19QxMEcys7B2c3T28-AOC4xUduKItrSbiEuNMo6zcMrPRsPEJScnwqWgYiFg4uPgFhcQAlKRIpBRVDdp09A1aevpt7J1cPLx8-kCCCCcUcURmcwWSxWa0cGxA2W2eT2hSOJTOPAA8uouKh2Dh8JJZI8WhotK8uh9EPM4tYZl4IrZHNY1rZrEDgqFwpEoi43HEnMt3NYEUjcrsCgcisdTmVuDi8QSibUGk0nq0Xp13qAerT6VFGRZmayXOzOSDJtNZrT3I44t5bHaLGKthL8vtDsUTqVzor8PjCWJbvdSc8KdrujTFgajSa2RzxpbwZDbfbHc7XTkdh60d65ecKgA1VANMDVOh1RrNcMdN6GYFBayOKw2xZ2h1eZ3+PX2+mxFzWEWmFymBxRLPIyWemUY+XF0v1cshh41zUR+vUhDNuncAdD6wjscWKIW0FTVPt9NdluT92o6Xon2Y7gASQgrHL0jka-JdapuqIPEcTcIoihxHyTh2Pa-JntyETRO4ngig6yTuBkmQgHQOAQHAhjijmD5erKvr4LWlI6sYiDJIo3Aiieh7Gk4UynkmQRRJ44TARYijJC4AJRBOmEESiUrEXOhaXKI5GRluPG0SkI7uIKhr2vaZ7Nq2cxrGByQWKYpiisJbqEWJs7PvK-qBmR67-pReq6aB1g+DEkEcaYcQaS2l7gTCqzrMZ2aiTOT4FuUAglmWMmboB258hCESmNeLgQR4jheVp8y+SlsIBZsQXTnmJEvu+n7RQBVEIEkLh0dYDFjvYjgsRlqY6bxY4GUZGRAA */
|
||||
createMachine({
|
||||
tsTypes: {} as import('./machine.typegen').Typegen0,
|
||||
initial: 'Idle',
|
||||
states: {
|
||||
TagKey: {
|
||||
on: {
|
||||
NEXT: {
|
||||
actions: 'onSelectOperator',
|
||||
target: 'Operator',
|
||||
},
|
||||
onBlur: {
|
||||
actions: 'onBlurPurge',
|
||||
target: 'Idle',
|
||||
},
|
||||
RESET: {
|
||||
target: 'Idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
Operator: {
|
||||
on: {
|
||||
NEXT: {
|
||||
actions: 'onSelectTagValue',
|
||||
target: 'TagValue',
|
||||
},
|
||||
onBlur: {
|
||||
actions: 'onBlurPurge',
|
||||
target: 'Idle',
|
||||
},
|
||||
RESET: {
|
||||
target: 'Idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
TagValue: {
|
||||
on: {
|
||||
onBlur: {
|
||||
actions: ['onValidateQuery', 'onBlurPurge'],
|
||||
target: 'Idle',
|
||||
},
|
||||
RESET: {
|
||||
target: 'Idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
Idle: {
|
||||
on: {
|
||||
NEXT: {
|
||||
actions: 'onSelectTagKey',
|
||||
description: 'Select Category',
|
||||
target: 'TagKey',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
id: 'ResourceAttributesFilterMachine',
|
||||
});
|
||||
@@ -2,31 +2,31 @@
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
eventsCausingActions: {
|
||||
onSelectOperator: 'NEXT';
|
||||
onBlurPurge: 'onBlur';
|
||||
onSelectTagValue: 'NEXT';
|
||||
onValidateQuery: 'onBlur';
|
||||
onSelectTagKey: 'NEXT';
|
||||
};
|
||||
internalEvents: {
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
};
|
||||
invokeSrcNameMap: {};
|
||||
missingImplementations: {
|
||||
actions:
|
||||
| 'onSelectOperator'
|
||||
| 'onBlurPurge'
|
||||
| 'onSelectOperator'
|
||||
| 'onSelectTagKey'
|
||||
| 'onSelectTagValue'
|
||||
| 'onValidateQuery'
|
||||
| 'onSelectTagKey';
|
||||
services: never;
|
||||
guards: never;
|
||||
| 'onValidateQuery';
|
||||
delays: never;
|
||||
guards: never;
|
||||
services: never;
|
||||
};
|
||||
eventsCausingActions: {
|
||||
onBlurPurge: 'onBlur';
|
||||
onSelectOperator: 'NEXT';
|
||||
onSelectTagKey: 'NEXT';
|
||||
onSelectTagValue: 'NEXT';
|
||||
onValidateQuery: 'onBlur';
|
||||
};
|
||||
eventsCausingServices: {};
|
||||
eventsCausingGuards: {};
|
||||
eventsCausingDelays: {};
|
||||
matchesStates: 'TagKey' | 'Operator' | 'TagValue' | 'Idle';
|
||||
eventsCausingGuards: {};
|
||||
eventsCausingServices: {};
|
||||
matchesStates: 'Idle' | 'Operator' | 'TagKey' | 'TagValue';
|
||||
tags: never;
|
||||
}
|
||||
31
frontend/src/hooks/useResourceAttribute/types.ts
Normal file
31
frontend/src/hooks/useResourceAttribute/types.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export interface IResourceAttribute {
|
||||
id: string;
|
||||
tagKey: string;
|
||||
operator: string;
|
||||
tagValue: string[];
|
||||
}
|
||||
|
||||
export interface IOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
type Modes = 'tags' | 'multiple';
|
||||
|
||||
export interface OptionsData {
|
||||
mode?: Modes;
|
||||
options: IOption[];
|
||||
}
|
||||
|
||||
export interface IResourceAttributeProps {
|
||||
queries: IResourceAttribute[];
|
||||
staging: string[];
|
||||
handleClearAll: VoidFunction;
|
||||
handleClose: (id: string) => void;
|
||||
handleBlur: VoidFunction;
|
||||
handleFocus: VoidFunction;
|
||||
loading: boolean;
|
||||
handleChange: (value: string) => void;
|
||||
selectedQuery: string[];
|
||||
optionsData: OptionsData;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { ResourceContext } from './context';
|
||||
import { IResourceAttributeProps } from './types';
|
||||
|
||||
const useResourceAttribute = (): IResourceAttributeProps =>
|
||||
useContext(ResourceContext);
|
||||
|
||||
export default useResourceAttribute;
|
||||
154
frontend/src/hooks/useResourceAttribute/utils.ts
Normal file
154
frontend/src/hooks/useResourceAttribute/utils.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import {
|
||||
getResourceAttributesTagKeys,
|
||||
getResourceAttributesTagValues,
|
||||
} from 'api/metrics/getResourceAttributes';
|
||||
import { OperatorConversions } from 'constants/resourceAttributes';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
IOption,
|
||||
IResourceAttribute,
|
||||
IResourceAttributeProps,
|
||||
} from 'hooks/useResourceAttribute/types';
|
||||
import { decode } from 'js-base64';
|
||||
import history from 'lib/history';
|
||||
import { IQueryBuilderTagFilterItems } from 'types/api/dashboard/getAll';
|
||||
import { OperatorValues, Tags } from 'types/reducer/trace';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { whilelistedKeys } from './config';
|
||||
|
||||
/**
|
||||
* resource_x_y -> x.y
|
||||
*/
|
||||
export const convertMetricKeyToTrace = (key: string): string => {
|
||||
const splittedKey = key.split('_');
|
||||
|
||||
if (splittedKey.length <= 1) {
|
||||
return '';
|
||||
}
|
||||
return splittedKey.splice(1).join('.');
|
||||
};
|
||||
|
||||
/**
|
||||
* x.y -> resource_x_y
|
||||
*/
|
||||
export const convertTraceKeyToMetric = (key: string): string => {
|
||||
const splittedKey = key.split('.');
|
||||
return `resource_${splittedKey.join('_')}`;
|
||||
};
|
||||
|
||||
export const convertOperatorLabelToMetricOperator = (label: string): string =>
|
||||
OperatorConversions.find((operator) => operator.label === label)
|
||||
?.metricValue || '';
|
||||
|
||||
export const convertOperatorLabelToTraceOperator = (
|
||||
label: string,
|
||||
): OperatorValues =>
|
||||
OperatorConversions.find((operator) => operator.label === label)
|
||||
?.traceValue as OperatorValues;
|
||||
|
||||
export const convertRawQueriesToTraceSelectedTags = (
|
||||
queries: IResourceAttribute[],
|
||||
tagType = 'ResourceAttribute',
|
||||
): Tags[] =>
|
||||
queries.map((query) => ({
|
||||
Key: convertMetricKeyToTrace(query.tagKey),
|
||||
Operator: convertOperatorLabelToTraceOperator(query.operator),
|
||||
StringValues: query.tagValue,
|
||||
NumberValues: [],
|
||||
BoolValues: [],
|
||||
TagType: tagType,
|
||||
}));
|
||||
|
||||
/* Convert resource attributes to tagFilter items for queryBuilder */
|
||||
export const resourceAttributesToTagFilterItems = (
|
||||
queries: IResourceAttribute[],
|
||||
): IQueryBuilderTagFilterItems[] =>
|
||||
queries.map((res) => ({
|
||||
id: `${res.id}`,
|
||||
key: `${res.tagKey}`,
|
||||
op: `${res.operator}`,
|
||||
value: `${res.tagValue}`.split(','),
|
||||
}));
|
||||
|
||||
export const OperatorSchema: IOption[] = OperatorConversions.map(
|
||||
(operator) => ({
|
||||
label: operator.label,
|
||||
value: operator.label,
|
||||
}),
|
||||
);
|
||||
|
||||
export const GetTagKeys = async (): Promise<IOption[]> => {
|
||||
const { payload } = await getResourceAttributesTagKeys({
|
||||
metricName: 'signoz_calls_total',
|
||||
match: 'resource_',
|
||||
});
|
||||
if (!payload || !payload?.data) {
|
||||
return [];
|
||||
}
|
||||
return payload.data.map((tagKey: string) => ({
|
||||
label: convertMetricKeyToTrace(tagKey),
|
||||
value: tagKey,
|
||||
}));
|
||||
};
|
||||
|
||||
export const GetTagValues = async (tagKey: string): Promise<IOption[]> => {
|
||||
const { payload } = await getResourceAttributesTagValues({
|
||||
tagKey,
|
||||
metricName: 'signoz_calls_total',
|
||||
});
|
||||
|
||||
if (!payload || !payload?.data) {
|
||||
return [];
|
||||
}
|
||||
return payload.data.map((tagValue: string) => ({
|
||||
label: tagValue,
|
||||
value: tagValue,
|
||||
}));
|
||||
};
|
||||
|
||||
export const createQuery = (
|
||||
selectedItems: Array<string | string[]> = [],
|
||||
): IResourceAttribute | null => {
|
||||
if (selectedItems.length === 3) {
|
||||
return {
|
||||
id: uuid().slice(0, 8),
|
||||
tagKey: selectedItems[0] as string,
|
||||
operator: selectedItems[1] as string,
|
||||
tagValue: selectedItems[2] as string[],
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export function getResourceAttributeQueriesFromURL(): IResourceAttribute[] {
|
||||
const resourceAttributeQuery = new URLSearchParams(
|
||||
history.location.search,
|
||||
).get('resourceAttribute');
|
||||
|
||||
try {
|
||||
if (resourceAttributeQuery) {
|
||||
return JSON.parse(decode(resourceAttributeQuery)) as IResourceAttribute[];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export const isResourceEmpty = (
|
||||
queries: IResourceAttributeProps['queries'],
|
||||
staging: IResourceAttributeProps['staging'],
|
||||
selectedQuery: IResourceAttributeProps['selectedQuery'],
|
||||
): boolean => !!(queries.length || staging.length || selectedQuery.length);
|
||||
|
||||
export const mappingWithRoutesAndKeys = (
|
||||
pathname: string,
|
||||
filters: IOption[],
|
||||
): IOption[] => {
|
||||
if (ROUTES.SERVICE_MAP === pathname) {
|
||||
return filters.filter((filter) => whilelistedKeys.includes(filter.value));
|
||||
}
|
||||
return filters;
|
||||
};
|
||||
16
frontend/src/lib/query/transformStringWithPrefix.ts
Normal file
16
frontend/src/lib/query/transformStringWithPrefix.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
type TransformStringWithPrefixParams = {
|
||||
str: string;
|
||||
prefix: string;
|
||||
condition: boolean;
|
||||
};
|
||||
|
||||
export const transformStringWithPrefix = ({
|
||||
str,
|
||||
prefix,
|
||||
condition,
|
||||
}: TransformStringWithPrefixParams): string => {
|
||||
if (prefix) {
|
||||
return condition ? `${prefix}_${str}` : str;
|
||||
}
|
||||
return str;
|
||||
};
|
||||
@@ -1,76 +0,0 @@
|
||||
import { OperatorConversions } from 'constants/resourceAttributes';
|
||||
import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types';
|
||||
import { IQueryBuilderTagFilterItems } from 'types/api/dashboard/getAll';
|
||||
import { OperatorValues, Tags } from 'types/reducer/trace';
|
||||
|
||||
/**
|
||||
* resource_x_y -> x.y
|
||||
*/
|
||||
export const convertMetricKeyToTrace = (key: string): string => {
|
||||
const splittedKey = key.split('_');
|
||||
|
||||
if (splittedKey.length <= 1) {
|
||||
return '';
|
||||
}
|
||||
return splittedKey.splice(1).join('.');
|
||||
};
|
||||
|
||||
/**
|
||||
* x.y -> resource_x_y
|
||||
*/
|
||||
export const convertTraceKeyToMetric = (key: string): string => {
|
||||
const splittedKey = key.split('.');
|
||||
return `resource_${splittedKey.join('_')}`;
|
||||
};
|
||||
|
||||
export const convertOperatorLabelToMetricOperator = (label: string): string =>
|
||||
OperatorConversions.find((operator) => operator.label === label)
|
||||
?.metricValue || '';
|
||||
|
||||
export const convertOperatorLabelToTraceOperator = (
|
||||
label: string,
|
||||
): OperatorValues =>
|
||||
OperatorConversions.find((operator) => operator.label === label)
|
||||
?.traceValue as OperatorValues;
|
||||
|
||||
export const convertRawQueriesToTraceSelectedTags = (
|
||||
queries: IResourceAttributeQuery[],
|
||||
): Tags[] =>
|
||||
queries.map((query) => ({
|
||||
Key: convertMetricKeyToTrace(query.tagKey),
|
||||
Operator: convertOperatorLabelToTraceOperator(query.operator),
|
||||
StringValues: query.tagValue,
|
||||
NumberValues: [],
|
||||
BoolValues: [],
|
||||
}));
|
||||
|
||||
/**
|
||||
* Converts Resource Attribute Queries to PromQL query string
|
||||
*/
|
||||
export const resourceAttributesQueryToPromQL = (
|
||||
queries: IResourceAttributeQuery[],
|
||||
): string => {
|
||||
let parsedQueryString = '';
|
||||
|
||||
if (Array.isArray(queries))
|
||||
queries.forEach((query) => {
|
||||
parsedQueryString += `, ${
|
||||
query.tagKey
|
||||
}${convertOperatorLabelToMetricOperator(
|
||||
query.operator,
|
||||
)}"${query.tagValue.join('|')}"`;
|
||||
});
|
||||
|
||||
return parsedQueryString;
|
||||
};
|
||||
|
||||
/* Convert resource attributes to tagFilter items for queryBuilder */
|
||||
export const resourceAttributesToTagFilterItems = (
|
||||
queries: IResourceAttributeQuery[],
|
||||
): IQueryBuilderTagFilterItems[] =>
|
||||
queries.map((res) => ({
|
||||
id: `${res.id}`,
|
||||
key: `${res.tagKey}`,
|
||||
op: `${res.operator}`,
|
||||
value: `${res.tagValue}`.split(','),
|
||||
}));
|
||||
56
frontend/src/modules/Servicemap/Map.tsx
Normal file
56
frontend/src/modules/Servicemap/Map.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
/* eslint-disable */
|
||||
//@ts-nocheck
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import React, { memo } from 'react';
|
||||
import { ForceGraph2D } from 'react-force-graph';
|
||||
|
||||
import { getGraphData, getTooltip, transformLabel } from './utils';
|
||||
|
||||
function ServiceMap({ fgRef, serviceMap }: any): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const { nodes, links } = getGraphData(serviceMap, isDarkMode);
|
||||
|
||||
const graphData = { nodes, links };
|
||||
|
||||
return (
|
||||
<ForceGraph2D
|
||||
ref={fgRef}
|
||||
cooldownTicks={100}
|
||||
graphData={graphData}
|
||||
linkLabel={getTooltip}
|
||||
linkAutoColorBy={(d) => d.target}
|
||||
linkDirectionalParticles="value"
|
||||
linkDirectionalParticleSpeed={(d) => d.value}
|
||||
nodeCanvasObject={(node, ctx) => {
|
||||
const label = transformLabel(node.id);
|
||||
const { fontSize } = node;
|
||||
ctx.font = `${fontSize}px Roboto`;
|
||||
const { width } = node;
|
||||
|
||||
ctx.fillStyle = node.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, width, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = isDarkMode ? '#ffffff' : '#000000';
|
||||
ctx.fillText(label, node.x, node.y);
|
||||
}}
|
||||
onLinkHover={(node) => {
|
||||
const tooltip = document.querySelector('.graph-tooltip');
|
||||
if (tooltip && node) {
|
||||
tooltip.innerHTML = getTooltip(node);
|
||||
}
|
||||
}}
|
||||
nodePointerAreaPaint={(node, color, ctx) => {
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, 5, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(ServiceMap);
|
||||
@@ -3,9 +3,12 @@
|
||||
|
||||
import { Card } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import { whilelistedKeys } from 'hooks/useResourceAttribute/config';
|
||||
import { IResourceAttribute } from 'hooks/useResourceAttribute/types';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { ForceGraph2D } from 'react-force-graph';
|
||||
import { connect } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import { getDetailedServiceMapItems, ServiceMapStore } from 'store/actions';
|
||||
@@ -13,7 +16,7 @@ import { AppState } from 'store/reducers';
|
||||
import styled from 'styled-components';
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
|
||||
import { getGraphData, getTooltip, getZoomPx, transformLabel } from './utils';
|
||||
import Map from './Map';
|
||||
|
||||
const Container = styled.div`
|
||||
.force-graph-container {
|
||||
@@ -38,7 +41,10 @@ const Container = styled.div`
|
||||
interface ServiceMapProps extends RouteComponentProps<any> {
|
||||
serviceMap: ServiceMapStore;
|
||||
globalTime: GlobalTime;
|
||||
getDetailedServiceMapItems: (time: GlobalTime) => void;
|
||||
getDetailedServiceMapItems: (
|
||||
time: GlobalTime,
|
||||
queries: IResourceAttribute[],
|
||||
) => void;
|
||||
}
|
||||
interface graphNode {
|
||||
id: string;
|
||||
@@ -60,17 +66,17 @@ export interface graphDataType {
|
||||
function ServiceMap(props: ServiceMapProps): JSX.Element {
|
||||
const fgRef = useRef();
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const { getDetailedServiceMapItems, globalTime, serviceMap } = props;
|
||||
|
||||
const { queries } = useResourceAttribute();
|
||||
|
||||
useEffect(() => {
|
||||
/*
|
||||
Call the apis only when the route is loaded.
|
||||
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
||||
*/
|
||||
getDetailedServiceMapItems(globalTime);
|
||||
}, [globalTime, getDetailedServiceMapItems]);
|
||||
getDetailedServiceMapItems(globalTime, queries);
|
||||
}, [globalTime, getDetailedServiceMapItems, queries]);
|
||||
|
||||
useEffect(() => {
|
||||
fgRef.current && fgRef.current.d3Force('charge').strength(-400);
|
||||
@@ -83,51 +89,26 @@ function ServiceMap(props: ServiceMapProps): JSX.Element {
|
||||
if (!serviceMap.loading && serviceMap.items.length === 0) {
|
||||
return (
|
||||
<Container>
|
||||
<ResourceAttributesFilter />
|
||||
<Card>No Service Found</Card>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const { nodes, links } = getGraphData(serviceMap, isDarkMode);
|
||||
const graphData = { nodes, links };
|
||||
return (
|
||||
<Container>
|
||||
<ForceGraph2D
|
||||
ref={fgRef}
|
||||
cooldownTicks={100}
|
||||
graphData={graphData}
|
||||
linkLabel={getTooltip}
|
||||
linkAutoColorBy={(d) => d.target}
|
||||
linkDirectionalParticles="value"
|
||||
linkDirectionalParticleSpeed={(d) => d.value}
|
||||
nodeCanvasObject={(node, ctx) => {
|
||||
const label = transformLabel(node.id);
|
||||
const { fontSize } = node;
|
||||
ctx.font = `${fontSize}px Roboto`;
|
||||
const { width } = node;
|
||||
|
||||
ctx.fillStyle = node.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, width, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = isDarkMode ? '#ffffff' : '#000000';
|
||||
ctx.fillText(label, node.x, node.y);
|
||||
}}
|
||||
onLinkHover={(node) => {
|
||||
const tooltip = document.querySelector('.graph-tooltip');
|
||||
if (tooltip && node) {
|
||||
tooltip.innerHTML = getTooltip(node);
|
||||
}
|
||||
}}
|
||||
nodePointerAreaPaint={(node, color, ctx) => {
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, 5, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
}}
|
||||
<ResourceAttributesFilter
|
||||
suffixIcon={
|
||||
<TextToolTip
|
||||
{...{
|
||||
text: `Currently, service map supports filtering of ${whilelistedKeys.join(
|
||||
', ',
|
||||
)} only, in resource attributes`,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Map fgRef={fgRef} serviceMap={serviceMap} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import RouteTab from 'components/RouteTab';
|
||||
import ROUTES from 'constants/routes';
|
||||
import AllErrorsContainer from 'container/AllError';
|
||||
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -8,18 +9,21 @@ function AllErrors(): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<RouteTab
|
||||
{...{
|
||||
routes: [
|
||||
{
|
||||
Component: AllErrorsContainer,
|
||||
name: t('routes.all_errors'),
|
||||
route: ROUTES.ALL_ERROR,
|
||||
},
|
||||
],
|
||||
activeKey: t('routes.all_errors'),
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<ResourceAttributesFilter />
|
||||
<RouteTab
|
||||
{...{
|
||||
routes: [
|
||||
{
|
||||
Component: AllErrorsContainer,
|
||||
name: t('routes.all_errors'),
|
||||
route: ROUTES.ALL_ERROR,
|
||||
},
|
||||
],
|
||||
activeKey: t('routes.all_errors'),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Typography } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import MetricsApplicationContainer from 'container/MetricsApplication';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -27,16 +28,11 @@ function MetricsApplication({ getInitialData }: MetricsProps): JSX.Element {
|
||||
>((state) => state.metrics);
|
||||
|
||||
const { servicename } = useParams<ServiceProps>();
|
||||
|
||||
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
|
||||
(state) => state.metrics,
|
||||
);
|
||||
const { queries } = useResourceAttribute();
|
||||
|
||||
const selectedTags = useMemo(
|
||||
() =>
|
||||
(convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) as Tags[]) ||
|
||||
[],
|
||||
[resourceAttributeQueries],
|
||||
() => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [],
|
||||
[queries],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -3,10 +3,11 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import ReleaseNote from 'components/ReleaseNote';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
||||
import ResourceAttributesFilter from 'container/MetricsApplication/ResourceAttributesFilter';
|
||||
import MetricTable from 'container/MetricsTable';
|
||||
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
@@ -25,12 +26,9 @@ function Metrics({ getService }: MetricsProps): JSX.Element {
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
const location = useLocation();
|
||||
const {
|
||||
services,
|
||||
resourceAttributeQueries,
|
||||
error,
|
||||
errorMessage,
|
||||
} = useSelector<AppState, MetricReducer>((state) => state.metrics);
|
||||
const { services, error, errorMessage } = useSelector<AppState, MetricReducer>(
|
||||
(state) => state.metrics,
|
||||
);
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -41,12 +39,13 @@ function Metrics({ getService }: MetricsProps): JSX.Element {
|
||||
}
|
||||
}, [error, errorMessage, notifications]);
|
||||
|
||||
const { queries } = useResourceAttribute();
|
||||
|
||||
const selectedTags = useMemo(
|
||||
() =>
|
||||
(convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) as Tags[]) ||
|
||||
[],
|
||||
[resourceAttributeQueries],
|
||||
() => (convertRawQueriesToTraceSelectedTags(queries, '') as Tags[]) || [],
|
||||
[queries],
|
||||
);
|
||||
|
||||
const isSkipped = getLocalStorageKey(SKIP_ONBOARDING) === 'true';
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ** Helpers
|
||||
import React, {
|
||||
createContext,
|
||||
PropsWithChildren,
|
||||
@@ -9,27 +10,21 @@ import React, {
|
||||
// TODO: Rename Types on the Reusable type for any source
|
||||
import {
|
||||
IBuilderFormula,
|
||||
IBuilderQuery,
|
||||
IBuilderQueryForm,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export type QueryBuilderData = {
|
||||
queryData: IBuilderQuery[];
|
||||
queryFormulas: IBuilderFormula[];
|
||||
};
|
||||
|
||||
// ** TODO: temporary types for context, fix it during development
|
||||
export type QueryBuilderContextType = {
|
||||
queryBuilderData: QueryBuilderData;
|
||||
resetQueryBuilderData: () => void;
|
||||
handleSetQueryData: (index: number, queryData: IBuilderQuery) => void;
|
||||
handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void;
|
||||
};
|
||||
import {
|
||||
DataSource,
|
||||
MetricAggregateOperator,
|
||||
QueryBuilderContextType,
|
||||
QueryBuilderData,
|
||||
} from 'types/common/queryBuilder';
|
||||
|
||||
export const QueryBuilderContext = createContext<QueryBuilderContextType>({
|
||||
queryBuilderData: { queryData: [], queryFormulas: [] },
|
||||
resetQueryBuilderData: () => {},
|
||||
handleSetQueryData: () => {},
|
||||
handleSetFormulaData: () => {},
|
||||
initQueryBuilderData: () => {},
|
||||
});
|
||||
|
||||
const initialQueryBuilderData: QueryBuilderData = {
|
||||
@@ -44,21 +39,51 @@ export function QueryBuilderProvider({
|
||||
// ** TODO: type the params which will be used for request of the data for query builder
|
||||
|
||||
const [queryBuilderData, setQueryBuilderData] = useState<QueryBuilderData>({
|
||||
queryData: [],
|
||||
// ** TODO temporary initial value for first query for testing first filters
|
||||
queryData: [
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
{
|
||||
dataSource: DataSource.METRICS,
|
||||
queryName: 'A',
|
||||
aggregateOperator: Object.values(MetricAggregateOperator)[0],
|
||||
aggregateAttribute: {
|
||||
dataType: null,
|
||||
key: '',
|
||||
isColumn: null,
|
||||
type: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
});
|
||||
|
||||
// ** TODO: Also in the future need to add AddFormula and AddQuery and remove them.
|
||||
|
||||
// ** Method for resetting query builder data
|
||||
const resetQueryBuilderData = useCallback((): void => {
|
||||
setQueryBuilderData(initialQueryBuilderData);
|
||||
}, []);
|
||||
|
||||
const handleSetQueryData = useCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
(index: number, queryData: IBuilderQuery): void => {},
|
||||
// ** Method for setupping query builder data
|
||||
const initQueryBuilderData = useCallback(
|
||||
(queryBuilderData: QueryBuilderData): void => {
|
||||
setQueryBuilderData(queryBuilderData);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleSetQueryData = useCallback(
|
||||
(index: number, newQueryData: Partial<IBuilderQueryForm>): void => {
|
||||
const updatedQueryBuilderData = queryBuilderData.queryData.map((item, idx) =>
|
||||
index === idx ? { ...item, ...newQueryData } : item,
|
||||
);
|
||||
|
||||
setQueryBuilderData((prevState) => ({
|
||||
...prevState,
|
||||
queryData: updatedQueryBuilderData,
|
||||
}));
|
||||
},
|
||||
[queryBuilderData],
|
||||
);
|
||||
const handleSetFormulaData = useCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
(index: number, formulaData: IBuilderFormula): void => {},
|
||||
@@ -76,12 +101,14 @@ export function QueryBuilderProvider({
|
||||
resetQueryBuilderData,
|
||||
handleSetQueryData,
|
||||
handleSetFormulaData,
|
||||
initQueryBuilderData,
|
||||
}),
|
||||
[
|
||||
queryBuilderData,
|
||||
resetQueryBuilderData,
|
||||
handleSetQueryData,
|
||||
handleSetFormulaData,
|
||||
initQueryBuilderData,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types';
|
||||
import { decode, encode } from 'js-base64';
|
||||
import history from 'lib/history';
|
||||
import { resourceAttributesQueryToPromQL } from 'lib/resourceAttributes';
|
||||
import { SET_RESOURCE_ATTRIBUTE_QUERIES } from 'types/actions/metrics';
|
||||
|
||||
export function GetResourceAttributeQueriesFromURL():
|
||||
| IResourceAttributeQuery[]
|
||||
| null {
|
||||
const resourceAttributeQuery = new URLSearchParams(
|
||||
history.location.search,
|
||||
).get('resourceAttribute');
|
||||
|
||||
try {
|
||||
if (resourceAttributeQuery) {
|
||||
return JSON.parse(
|
||||
decode(resourceAttributeQuery),
|
||||
) as IResourceAttributeQuery[];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export const SetResourceAttributeQueriesFromURL = (
|
||||
queries: IResourceAttributeQuery[],
|
||||
): void => {
|
||||
history.push({
|
||||
pathname: history.location.pathname,
|
||||
search:
|
||||
queries && queries.length
|
||||
? `?resourceAttribute=${encode(JSON.stringify(queries))}`
|
||||
: '',
|
||||
});
|
||||
};
|
||||
export const SetResourceAttributeQueries = (
|
||||
queries: IResourceAttributeQuery[],
|
||||
): {
|
||||
type: typeof SET_RESOURCE_ATTRIBUTE_QUERIES;
|
||||
payload: {
|
||||
queries: IResourceAttributeQuery[];
|
||||
promQLQuery: string;
|
||||
};
|
||||
} => {
|
||||
SetResourceAttributeQueriesFromURL(queries);
|
||||
return {
|
||||
type: SET_RESOURCE_ATTRIBUTE_QUERIES,
|
||||
payload: {
|
||||
queries,
|
||||
promQLQuery: resourceAttributesQueryToPromQL(queries),
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
import api from 'api';
|
||||
import { IResourceAttribute } from 'hooks/useResourceAttribute/types';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||
import { Dispatch } from 'redux';
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
|
||||
@@ -30,16 +32,17 @@ export interface ServiceMapLoading {
|
||||
};
|
||||
}
|
||||
|
||||
export const getDetailedServiceMapItems = (globalTime: GlobalTime) => async (
|
||||
dispatch: Dispatch,
|
||||
): Promise<void> => {
|
||||
export const getDetailedServiceMapItems = (
|
||||
globalTime: GlobalTime,
|
||||
queries: IResourceAttribute[],
|
||||
) => async (dispatch: Dispatch): Promise<void> => {
|
||||
const start = `${globalTime.minTime}`;
|
||||
const end = `${globalTime.maxTime}`;
|
||||
|
||||
const serviceMapPayload = {
|
||||
start,
|
||||
end,
|
||||
tags: [],
|
||||
tags: convertRawQueriesToTraceSelectedTags(queries),
|
||||
};
|
||||
const [dependencyGraphResponse] = await Promise.all([
|
||||
api.post<ServicesMapItem[]>(`/dependency_graph`, serviceMapPayload),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { resourceAttributesQueryToPromQL } from 'lib/resourceAttributes';
|
||||
import { GetResourceAttributeQueriesFromURL } from 'store/actions/metrics/setResourceAttributeQueries';
|
||||
import {
|
||||
GET_INITIAL_APPLICATION_ERROR,
|
||||
GET_INITIAL_APPLICATION_LOADING,
|
||||
@@ -9,7 +7,6 @@ import {
|
||||
GET_SERVICE_LIST_SUCCESS,
|
||||
MetricsActions,
|
||||
RESET_INITIAL_APPLICATION_DATA,
|
||||
SET_RESOURCE_ATTRIBUTE_QUERIES,
|
||||
} from 'types/actions/metrics';
|
||||
import InitialValueTypes from 'types/reducer/metrics';
|
||||
|
||||
@@ -25,10 +22,6 @@ const InitialValue: InitialValueTypes = {
|
||||
externalAverageDuration: [],
|
||||
externalError: [],
|
||||
serviceOverview: [],
|
||||
resourceAttributeQueries: GetResourceAttributeQueriesFromURL() || [],
|
||||
resourceAttributePromQLQuery: resourceAttributesQueryToPromQL(
|
||||
GetResourceAttributeQueriesFromURL() || [],
|
||||
),
|
||||
topLevelOperations: [],
|
||||
};
|
||||
|
||||
@@ -110,15 +103,6 @@ const metrics = (
|
||||
};
|
||||
}
|
||||
|
||||
case SET_RESOURCE_ATTRIBUTE_QUERIES: {
|
||||
const { queries, promQLQuery } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
resourceAttributeQueries: queries,
|
||||
resourceAttributePromQLQuery: promQLQuery,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
// import { DBOverView } from 'types/api/metrics/getDBOverview';
|
||||
// import { ExternalAverageDuration } from 'types/api/metrics/getExternalAverageDuration';
|
||||
// import { ExternalError } from 'types/api/metrics/getExternalError';
|
||||
// import { ExternalService } from 'types/api/metrics/getExternalService';
|
||||
import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types';
|
||||
import { ServicesList } from 'types/api/metrics/getService';
|
||||
import { ServiceOverview } from 'types/api/metrics/getServiceOverview';
|
||||
import { TopOperations } from 'types/api/metrics/getTopOperations';
|
||||
@@ -15,7 +10,6 @@ export const GET_INITIAL_APPLICATION_LOADING =
|
||||
export const GET_INITIAL_APPLICATION_ERROR = 'GET_INITIAL_APPLICATION_ERROR';
|
||||
export const GET_INTIAL_APPLICATION_DATA = 'GET_INTIAL_APPLICATION_DATA';
|
||||
export const RESET_INITIAL_APPLICATION_DATA = 'RESET_INITIAL_APPLICATION_DATA';
|
||||
export const SET_RESOURCE_ATTRIBUTE_QUERIES = 'SET_RESOURCE_ATTRIBUTE_QUERIES';
|
||||
|
||||
export interface GetServiceList {
|
||||
type: typeof GET_SERVICE_LIST_SUCCESS;
|
||||
@@ -52,18 +46,9 @@ export interface ResetInitialApplicationData {
|
||||
type: typeof RESET_INITIAL_APPLICATION_DATA;
|
||||
}
|
||||
|
||||
export interface SetResourceAttributeQueries {
|
||||
type: typeof SET_RESOURCE_ATTRIBUTE_QUERIES;
|
||||
payload: {
|
||||
queries: IResourceAttributeQuery[];
|
||||
promQLQuery: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type MetricsActions =
|
||||
| GetServiceListError
|
||||
| GetServiceListLoading
|
||||
| GetServiceList
|
||||
| GetInitialApplicationData
|
||||
| ResetInitialApplicationData
|
||||
| SetResourceAttributeQueries;
|
||||
| ResetInitialApplicationData;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
import { Tags } from 'types/reducer/trace';
|
||||
|
||||
export type Order = 'ascending' | 'descending';
|
||||
export type OrderBy =
|
||||
@@ -17,6 +18,7 @@ export interface Props {
|
||||
offset?: number;
|
||||
exceptionType?: string;
|
||||
serviceName?: string;
|
||||
tags?: Tags[];
|
||||
}
|
||||
|
||||
export interface Exception {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
import { Tags } from 'types/reducer/trace';
|
||||
|
||||
export type Props = {
|
||||
start: GlobalTime['minTime'];
|
||||
end: GlobalTime['minTime'];
|
||||
exceptionType: string;
|
||||
serviceName: string;
|
||||
tags: Tags[];
|
||||
};
|
||||
|
||||
export type PayloadProps = number;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user