diff --git a/frontend/package.json b/frontend/package.json index af78690982..6e2aded68e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -47,6 +47,7 @@ "@tanstack/react-virtual": "3.11.2", "@uiw/react-md-editor": "3.23.5", "@visx/group": "3.3.0", + "@visx/hierarchy": "3.12.0", "@visx/shape": "3.5.0", "@visx/tooltip": "3.3.0", "@xstate/react": "^3.0.0", @@ -69,6 +70,7 @@ "cross-env": "^7.0.3", "css-loader": "5.0.0", "css-minimizer-webpack-plugin": "5.0.1", + "d3-hierarchy": "3.1.2", "dayjs": "^1.10.7", "dompurify": "3.1.3", "dotenv": "8.2.0", diff --git a/frontend/src/api/metricsExplorer/getMetricsList.ts b/frontend/src/api/metricsExplorer/getMetricsList.ts new file mode 100644 index 0000000000..f1cb81da62 --- /dev/null +++ b/frontend/src/api/metricsExplorer/getMetricsList.ts @@ -0,0 +1,67 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { + OrderByPayload, + TreemapViewType, +} from 'container/MetricsExplorer/Summary/types'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +export interface MetricsListPayload { + filters: TagFilter; + groupBy?: BaseAutocompleteData[]; + offset?: number; + limit?: number; + orderBy?: OrderByPayload; +} + +export enum MetricType { + SUM = 'Sum', + GAUGE = 'Gauge', + HISTOGRAM = 'Histogram', + SUMMARY = 'Summary', + EXPONENTIAL_HISTOGRAM = 'ExponentialHistogram', +} + +export interface MetricsListItemData { + metric_name: string; + description: string; + type: MetricType; + unit: string; + [TreemapViewType.CARDINALITY]: number; + [TreemapViewType.DATAPOINTS]: number; + lastReceived: string; +} + +export interface MetricsListResponse { + status: string; + data: { + metrics: MetricsListItemData[]; + total?: number; + }; +} + +export const getMetricsList = async ( + props: MetricsListPayload, + signal?: AbortSignal, + headers?: Record, +): Promise | ErrorResponse> => { + try { + const response = await axios.post('/metrics', props, { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + params: props, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/api/metricsExplorer/getMetricsListFilterKeys.ts b/frontend/src/api/metricsExplorer/getMetricsListFilterKeys.ts new file mode 100644 index 0000000000..ce37f35935 --- /dev/null +++ b/frontend/src/api/metricsExplorer/getMetricsListFilterKeys.ts @@ -0,0 +1,34 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; + +export interface MetricsListFilterKeysResponse { + status: string; + data: { + metricColumns: string[]; + attributeKeys: BaseAutocompleteData[]; + }; +} + +export const getMetricsListFilterKeys = async ( + signal?: AbortSignal, + headers?: Record, +): Promise | ErrorResponse> => { + try { + const response = await axios.get('/metrics/filters/keys', { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/api/metricsExplorer/getMetricsListFilterValues.ts b/frontend/src/api/metricsExplorer/getMetricsListFilterValues.ts new file mode 100644 index 0000000000..addfc91750 --- /dev/null +++ b/frontend/src/api/metricsExplorer/getMetricsListFilterValues.ts @@ -0,0 +1,44 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; + +export interface MetricsListFilterValuesPayload { + filterAttributeKeyDataType: string; + filterKey: string; + searchText: string; + limit: number; +} + +export interface MetricsListFilterValuesResponse { + status: string; + data: { + FilterValues: BaseAutocompleteData[]; + }; +} + +export const getMetricsListFilterValues = async ( + props: MetricsListFilterValuesPayload, + signal?: AbortSignal, + headers?: Record, +): Promise< + SuccessResponse | ErrorResponse +> => { + try { + const response = await axios.post('/metrics/filters/values', props, { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + params: props, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/api/metricsExplorer/getMetricsTreeMap.ts b/frontend/src/api/metricsExplorer/getMetricsTreeMap.ts new file mode 100644 index 0000000000..0370d2f275 --- /dev/null +++ b/frontend/src/api/metricsExplorer/getMetricsTreeMap.ts @@ -0,0 +1,54 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { TreemapViewType } from 'container/MetricsExplorer/Summary/types'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +export interface MetricsTreeMapPayload { + filters: TagFilter; + limit?: number; + treemap?: TreemapViewType; +} + +export interface MetricsTreeMapResponse { + status: string; + data: { + [TreemapViewType.CARDINALITY]: CardinalityData[]; + [TreemapViewType.DATAPOINTS]: DatapointsData[]; + }; +} + +export interface CardinalityData { + percentage: number; + total_value: number; + metric_name: string; +} + +export interface DatapointsData { + percentage: number; + metric_name: string; +} + +export const getMetricsTreeMap = async ( + props: MetricsTreeMapPayload, + signal?: AbortSignal, + headers?: Record, +): Promise | ErrorResponse> => { + try { + const response = await axios.post('/metrics/treemap', props, { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + params: props, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/constants/reactQueryKeys.ts b/frontend/src/constants/reactQueryKeys.ts index d88dd7a04a..261539e631 100644 --- a/frontend/src/constants/reactQueryKeys.ts +++ b/frontend/src/constants/reactQueryKeys.ts @@ -43,4 +43,10 @@ export const REACT_QUERY_KEY = { AWS_GENERATE_CONNECTION_URL: 'AWS_GENERATE_CONNECTION_URL', AWS_GET_CONNECTION_PARAMS: 'AWS_GET_CONNECTION_PARAMS', GET_ATTRIBUTE_VALUES: 'GET_ATTRIBUTE_VALUES', + + // Metrics Explorer Query Keys + GET_METRICS_LIST: 'GET_METRICS_LIST', + GET_METRICS_TREE_MAP: 'GET_METRICS_TREE_MAP', + GET_METRICS_LIST_FILTER_KEYS: 'GET_METRICS_LIST_FILTER_KEYS', + GET_METRICS_LIST_FILTER_VALUES: 'GET_METRICS_LIST_FILTER_VALUES', }; diff --git a/frontend/src/container/MetricsExplorer/Summary/MetricsSearch.tsx b/frontend/src/container/MetricsExplorer/Summary/MetricsSearch.tsx new file mode 100644 index 0000000000..52116ba9a3 --- /dev/null +++ b/frontend/src/container/MetricsExplorer/Summary/MetricsSearch.tsx @@ -0,0 +1,40 @@ +import { Select } from 'antd'; +import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { HardHat } from 'lucide-react'; + +import { TREEMAP_VIEW_OPTIONS } from './constants'; +import { MetricsSearchProps } from './types'; + +function MetricsSearch({ + query, + onChange, + heatmapView, + setHeatmapView, +}: MetricsSearchProps): JSX.Element { + return ( +
+
+