mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
feat: meter explorer (#8741)
* feat: meter explorer * feat: meter explorer * fix: remove meter as data source * fix: change meter-explorer to meter - quick filter * chore: delete test file * fix: failing test cases
This commit is contained in:
parent
aa3bc16dcb
commit
932918e3a4
@ -46,5 +46,8 @@
|
|||||||
"ALERT_HISTORY": "SigNoz | Alert Rule History",
|
"ALERT_HISTORY": "SigNoz | Alert Rule History",
|
||||||
"ALERT_OVERVIEW": "SigNoz | Alert Rule Overview",
|
"ALERT_OVERVIEW": "SigNoz | Alert Rule Overview",
|
||||||
"INFRASTRUCTURE_MONITORING_HOSTS": "SigNoz | Infra Monitoring",
|
"INFRASTRUCTURE_MONITORING_HOSTS": "SigNoz | Infra Monitoring",
|
||||||
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring"
|
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring",
|
||||||
|
"METER_EXPLORER": "SigNoz | Meter Explorer",
|
||||||
|
"METER_EXPLORER_VIEWS": "SigNoz | Meter Explorer",
|
||||||
|
"METER_EXPLORER_BASE": "SigNoz | Meter Explorer"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,5 +69,8 @@
|
|||||||
"METRICS_EXPLORER": "SigNoz | Metrics Explorer",
|
"METRICS_EXPLORER": "SigNoz | Metrics Explorer",
|
||||||
"METRICS_EXPLORER_EXPLORER": "SigNoz | Metrics Explorer",
|
"METRICS_EXPLORER_EXPLORER": "SigNoz | Metrics Explorer",
|
||||||
"METRICS_EXPLORER_VIEWS": "SigNoz | Metrics Explorer",
|
"METRICS_EXPLORER_VIEWS": "SigNoz | Metrics Explorer",
|
||||||
"API_MONITORING": "SigNoz | External APIs"
|
"API_MONITORING": "SigNoz | External APIs",
|
||||||
|
"METER_EXPLORER": "SigNoz | Meter Explorer",
|
||||||
|
"METER_EXPLORER_VIEWS": "SigNoz | Meter Explorer",
|
||||||
|
"METER_EXPLORER_BASE": "SigNoz | Meter Explorer"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import MessagingQueues from 'pages/MessagingQueues';
|
import MessagingQueues from 'pages/MessagingQueues';
|
||||||
|
import MeterExplorer from 'pages/MeterExplorer';
|
||||||
import { RouteProps } from 'react-router-dom';
|
import { RouteProps } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -434,6 +435,28 @@ const routes: AppRoutes[] = [
|
|||||||
key: 'METRICS_EXPLORER_VIEWS',
|
key: 'METRICS_EXPLORER_VIEWS',
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: ROUTES.METER_EXPLORER_BASE,
|
||||||
|
exact: true,
|
||||||
|
component: MeterExplorer,
|
||||||
|
key: 'METER_EXPLORER_BASE',
|
||||||
|
isPrivate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ROUTES.METER_EXPLORER,
|
||||||
|
exact: true,
|
||||||
|
component: MeterExplorer,
|
||||||
|
key: 'METER_EXPLORER',
|
||||||
|
isPrivate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ROUTES.METER_EXPLORER_VIEWS,
|
||||||
|
exact: true,
|
||||||
|
component: MeterExplorer,
|
||||||
|
key: 'METER_EXPLORER_VIEWS',
|
||||||
|
isPrivate: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: ROUTES.API_MONITORING,
|
path: ROUTES.API_MONITORING,
|
||||||
exact: true,
|
exact: true,
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export const getAggregateAttribute = async ({
|
|||||||
aggregateOperator,
|
aggregateOperator,
|
||||||
searchText,
|
searchText,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
source,
|
||||||
}: IGetAggregateAttributePayload): Promise<
|
}: IGetAggregateAttributePayload): Promise<
|
||||||
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
||||||
> => {
|
> => {
|
||||||
@ -27,7 +28,7 @@ export const getAggregateAttribute = async ({
|
|||||||
`/autocomplete/aggregate_attributes?${createQueryParams({
|
`/autocomplete/aggregate_attributes?${createQueryParams({
|
||||||
aggregateOperator,
|
aggregateOperator,
|
||||||
searchText,
|
searchText,
|
||||||
dataSource,
|
dataSource: source === 'meter' ? 'meter' : dataSource,
|
||||||
})}`,
|
})}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export const getKeySuggestions = (
|
|||||||
metricName = '',
|
metricName = '',
|
||||||
fieldContext = '',
|
fieldContext = '',
|
||||||
fieldDataType = '',
|
fieldDataType = '',
|
||||||
|
signalSource = '',
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const encodedSignal = encodeURIComponent(signal);
|
const encodedSignal = encodeURIComponent(signal);
|
||||||
@ -21,8 +22,9 @@ export const getKeySuggestions = (
|
|||||||
const encodedMetricName = encodeURIComponent(metricName);
|
const encodedMetricName = encodeURIComponent(metricName);
|
||||||
const encodedFieldContext = encodeURIComponent(fieldContext);
|
const encodedFieldContext = encodeURIComponent(fieldContext);
|
||||||
const encodedFieldDataType = encodeURIComponent(fieldDataType);
|
const encodedFieldDataType = encodeURIComponent(fieldDataType);
|
||||||
|
const encodedSource = encodeURIComponent(signalSource);
|
||||||
|
|
||||||
return axios.get(
|
return axios.get(
|
||||||
`/fields/keys?signal=${encodedSignal}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&fieldContext=${encodedFieldContext}&fieldDataType=${encodedFieldDataType}`,
|
`/fields/keys?signal=${encodedSignal}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&fieldContext=${encodedFieldContext}&fieldDataType=${encodedFieldDataType}&source=${encodedSource}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,13 +8,14 @@ import {
|
|||||||
export const getValueSuggestions = (
|
export const getValueSuggestions = (
|
||||||
props: QueryKeyValueRequestProps,
|
props: QueryKeyValueRequestProps,
|
||||||
): Promise<AxiosResponse<QueryKeyValueSuggestionsResponseProps>> => {
|
): Promise<AxiosResponse<QueryKeyValueSuggestionsResponseProps>> => {
|
||||||
const { signal, key, searchText } = props;
|
const { signal, key, searchText, signalSource } = props;
|
||||||
|
|
||||||
const encodedSignal = encodeURIComponent(signal);
|
const encodedSignal = encodeURIComponent(signal);
|
||||||
const encodedKey = encodeURIComponent(key);
|
const encodedKey = encodeURIComponent(key);
|
||||||
const encodedSearchText = encodeURIComponent(searchText);
|
const encodedSearchText = encodeURIComponent(searchText);
|
||||||
|
const encodedSource = encodeURIComponent(signalSource || '');
|
||||||
|
|
||||||
return axios.get(
|
return axios.get(
|
||||||
`/fields/values?signal=${encodedSignal}&name=${encodedKey}&searchText=${encodedSearchText}`,
|
`/fields/values?signal=${encodedSignal}&name=${encodedKey}&searchText=${encodedSearchText}&source=${encodedSource}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,6 +4,6 @@ import { AllViewsProps } from 'types/api/saveViews/types';
|
|||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
export const getAllViews = (
|
export const getAllViews = (
|
||||||
sourcepage: DataSource,
|
sourcepage: DataSource | 'meter',
|
||||||
): Promise<AxiosResponse<AllViewsProps>> =>
|
): Promise<AxiosResponse<AllViewsProps>> =>
|
||||||
axios.get(`/explorer/views?sourcePage=${sourcepage}`);
|
axios.get(`/explorer/views?sourcePage=${sourcepage}`);
|
||||||
|
|||||||
@ -260,6 +260,7 @@ export function convertBuilderQueriesToV5(
|
|||||||
spec = {
|
spec = {
|
||||||
name: queryName,
|
name: queryName,
|
||||||
signal: 'metrics' as const,
|
signal: 'metrics' as const,
|
||||||
|
source: queryData.source || '',
|
||||||
...baseSpec,
|
...baseSpec,
|
||||||
aggregations: aggregations as MetricAggregation[],
|
aggregations: aggregations as MetricAggregation[],
|
||||||
// reduceTo: queryData.reduceTo,
|
// reduceTo: queryData.reduceTo,
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
.loading-panel-data {
|
||||||
|
padding: 24px 0;
|
||||||
|
height: 240px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.loading-panel-data-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.loading-gif {
|
||||||
|
height: 72px;
|
||||||
|
margin-left: -24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import './PanelDataLoading.styles.scss';
|
||||||
|
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
|
||||||
|
export function PanelDataLoading(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div className="loading-panel-data">
|
||||||
|
<div className="loading-panel-data-content">
|
||||||
|
<img
|
||||||
|
className="loading-gif"
|
||||||
|
src="/Icons/loading-plane.gif"
|
||||||
|
alt="wait-icon"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography.Text>Fetching data...</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -131,6 +131,7 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
|||||||
queryVariant={config?.queryVariant || 'dropdown'}
|
queryVariant={config?.queryVariant || 'dropdown'}
|
||||||
showOnlyWhereClause={showOnlyWhereClause}
|
showOnlyWhereClause={showOnlyWhereClause}
|
||||||
isListViewPanel={isListViewPanel}
|
isListViewPanel={isListViewPanel}
|
||||||
|
signalSource={config?.signalSource || ''}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
|||||||
@ -18,11 +18,13 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
|
|||||||
index,
|
index,
|
||||||
version,
|
version,
|
||||||
panelType,
|
panelType,
|
||||||
|
signalSource = '',
|
||||||
}: {
|
}: {
|
||||||
query: IBuilderQuery;
|
query: IBuilderQuery;
|
||||||
index: number;
|
index: number;
|
||||||
version: string;
|
version: string;
|
||||||
panelType: PANEL_TYPES | null;
|
panelType: PANEL_TYPES | null;
|
||||||
|
signalSource: string;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { setAggregationOptions } = useQueryBuilderV2Context();
|
const { setAggregationOptions } = useQueryBuilderV2Context();
|
||||||
const {
|
const {
|
||||||
@ -208,6 +210,7 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
|
|||||||
disabled={!queryAggregation.metricName}
|
disabled={!queryAggregation.metricName}
|
||||||
query={query}
|
query={query}
|
||||||
onChange={handleChangeGroupByKeys}
|
onChange={handleChangeGroupByKeys}
|
||||||
|
signalSource={signalSource}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -244,6 +247,7 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
|
|||||||
disabled={!queryAggregation.metricName}
|
disabled={!queryAggregation.metricName}
|
||||||
query={query}
|
query={query}
|
||||||
onChange={handleChangeGroupByKeys}
|
onChange={handleChangeGroupByKeys}
|
||||||
|
signalSource={signalSource}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,10 +9,12 @@ export const MetricsSelect = memo(function MetricsSelect({
|
|||||||
query,
|
query,
|
||||||
index,
|
index,
|
||||||
version,
|
version,
|
||||||
|
signalSource,
|
||||||
}: {
|
}: {
|
||||||
query: IBuilderQuery;
|
query: IBuilderQuery;
|
||||||
index: number;
|
index: number;
|
||||||
version: string;
|
version: string;
|
||||||
|
signalSource: 'meter' | '';
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { handleChangeAggregatorAttribute } = useQueryOperations({
|
const { handleChangeAggregatorAttribute } = useQueryOperations({
|
||||||
index,
|
index,
|
||||||
@ -26,6 +28,7 @@ export const MetricsSelect = memo(function MetricsSelect({
|
|||||||
onChange={handleChangeAggregatorAttribute}
|
onChange={handleChangeAggregatorAttribute}
|
||||||
query={query}
|
query={query}
|
||||||
index={index}
|
index={index}
|
||||||
|
signalSource={signalSource || ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -81,10 +81,12 @@ function QuerySearch({
|
|||||||
queryData,
|
queryData,
|
||||||
dataSource,
|
dataSource,
|
||||||
onRun,
|
onRun,
|
||||||
|
signalSource,
|
||||||
}: {
|
}: {
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
queryData: IBuilderQuery;
|
queryData: IBuilderQuery;
|
||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
|
signalSource?: string;
|
||||||
onRun?: (query: string) => void;
|
onRun?: (query: string) => void;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
@ -218,6 +220,7 @@ function QuerySearch({
|
|||||||
signal: dataSource,
|
signal: dataSource,
|
||||||
searchText: searchText || '',
|
searchText: searchText || '',
|
||||||
metricName: debouncedMetricName ?? undefined,
|
metricName: debouncedMetricName ?? undefined,
|
||||||
|
signalSource: signalSource as 'meter' | '',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data.data) {
|
if (response.data.data) {
|
||||||
@ -245,6 +248,7 @@ function QuerySearch({
|
|||||||
keySuggestions,
|
keySuggestions,
|
||||||
toggleSuggestions,
|
toggleSuggestions,
|
||||||
queryData.aggregateAttribute?.key,
|
queryData.aggregateAttribute?.key,
|
||||||
|
signalSource,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -378,6 +382,7 @@ function QuerySearch({
|
|||||||
key,
|
key,
|
||||||
searchText: sanitizedSearchText,
|
searchText: sanitizedSearchText,
|
||||||
signal: dataSource,
|
signal: dataSource,
|
||||||
|
signalSource: signalSource as 'meter' | '',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Skip updates if component unmounted or key changed
|
// Skip updates if component unmounted or key changed
|
||||||
@ -465,8 +470,13 @@ function QuerySearch({
|
|||||||
setIsFetchingCompleteValuesList(false);
|
setIsFetchingCompleteValuesList(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
[
|
||||||
[activeKey, dataSource, isFocused],
|
activeKey,
|
||||||
|
dataSource,
|
||||||
|
isLoadingSuggestions,
|
||||||
|
signalSource,
|
||||||
|
toggleSuggestions,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const debouncedFetchValueSuggestions = useMemo(
|
const debouncedFetchValueSuggestions = useMemo(
|
||||||
@ -1440,6 +1450,7 @@ function QuerySearch({
|
|||||||
|
|
||||||
QuerySearch.defaultProps = {
|
QuerySearch.defaultProps = {
|
||||||
onRun: undefined,
|
onRun: undefined,
|
||||||
|
signalSource: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default QuerySearch;
|
export default QuerySearch;
|
||||||
|
|||||||
@ -28,6 +28,7 @@ export const QueryV2 = memo(function QueryV2({
|
|||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
version,
|
version,
|
||||||
showOnlyWhereClause = false,
|
showOnlyWhereClause = false,
|
||||||
|
signalSource = '',
|
||||||
}: QueryProps & { ref: React.RefObject<HTMLDivElement> }): JSX.Element {
|
}: QueryProps & { ref: React.RefObject<HTMLDivElement> }): JSX.Element {
|
||||||
const { cloneQuery, panelType } = useQueryBuilder();
|
const { cloneQuery, panelType } = useQueryBuilder();
|
||||||
|
|
||||||
@ -175,6 +176,7 @@ export const QueryV2 = memo(function QueryV2({
|
|||||||
query={query}
|
query={query}
|
||||||
index={index}
|
index={index}
|
||||||
version={ENTITY_VERSION_V5}
|
version={ENTITY_VERSION_V5}
|
||||||
|
signalSource={signalSource as 'meter' | ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -186,6 +188,7 @@ export const QueryV2 = memo(function QueryV2({
|
|||||||
onChange={handleSearchChange}
|
onChange={handleSearchChange}
|
||||||
queryData={query}
|
queryData={query}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
|
signalSource={signalSource}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -218,6 +221,7 @@ export const QueryV2 = memo(function QueryV2({
|
|||||||
index={index}
|
index={index}
|
||||||
key={`metrics-aggregate-section-${query.queryName}-${query.dataSource}`}
|
key={`metrics-aggregate-section-${query.queryName}-${query.dataSource}`}
|
||||||
version="v4"
|
version="v4"
|
||||||
|
signalSource={signalSource as 'meter' | ''}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
|
|||||||
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||||
import { useGetAggregateValues } from 'hooks/queryBuilder/useGetAggregateValues';
|
import { useGetAggregateValues } from 'hooks/queryBuilder/useGetAggregateValues';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import { useGetQueryKeyValueSuggestions } from 'hooks/querySuggestions/useGetQueryKeyValueSuggestions';
|
||||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||||
import { cloneDeep, isArray, isEqual, isFunction } from 'lodash-es';
|
import { cloneDeep, isArray, isEqual, isFunction } from 'lodash-es';
|
||||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||||
@ -73,18 +74,59 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
|||||||
searchText: searchText ?? '',
|
searchText: searchText ?? '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: isOpen,
|
enabled: isOpen && source !== QuickFiltersSource.METER_EXPLORER,
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: keyValueSuggestions,
|
||||||
|
isLoading: isLoadingKeyValueSuggestions,
|
||||||
|
} = useGetQueryKeyValueSuggestions({
|
||||||
|
key: filter.attributeKey.key,
|
||||||
|
signal: filter.dataSource || DataSource.LOGS,
|
||||||
|
signalSource: 'meter',
|
||||||
|
options: {
|
||||||
|
enabled: isOpen && source === QuickFiltersSource.METER_EXPLORER,
|
||||||
|
keepPreviousData: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const attributeValues: string[] = useMemo(() => {
|
const attributeValues: string[] = useMemo(() => {
|
||||||
const dataType = filter.attributeKey.dataType || DataTypes.String;
|
const dataType = filter.attributeKey.dataType || DataTypes.String;
|
||||||
|
|
||||||
|
if (source === QuickFiltersSource.METER_EXPLORER && keyValueSuggestions) {
|
||||||
|
// Process the response data
|
||||||
|
const responseData = keyValueSuggestions?.data as any;
|
||||||
|
const values = responseData.data?.values || {};
|
||||||
|
const stringValues = values.stringValues || [];
|
||||||
|
const numberValues = values.numberValues || [];
|
||||||
|
|
||||||
|
// Generate options from string values - explicitly handle empty strings
|
||||||
|
const stringOptions = stringValues
|
||||||
|
// Strict filtering for empty string - we'll handle it as a special case if needed
|
||||||
|
.filter(
|
||||||
|
(value: string | null | undefined): value is string =>
|
||||||
|
value !== null && value !== undefined && value !== '',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generate options from number values
|
||||||
|
const numberOptions = numberValues
|
||||||
|
.filter(
|
||||||
|
(value: number | null | undefined): value is number =>
|
||||||
|
value !== null && value !== undefined,
|
||||||
|
)
|
||||||
|
.map((value: number) => value.toString());
|
||||||
|
|
||||||
|
// Combine all options and make sure we don't have duplicate labels
|
||||||
|
return [...stringOptions, ...numberOptions];
|
||||||
|
}
|
||||||
|
|
||||||
const key = DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY[dataType];
|
const key = DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY[dataType];
|
||||||
return (data?.payload?.[key] || []).filter(
|
return (data?.payload?.[key] || []).filter(
|
||||||
(val) => val !== undefined && val !== null,
|
(val) => val !== undefined && val !== null,
|
||||||
);
|
);
|
||||||
}, [data?.payload, filter.attributeKey.dataType]);
|
}, [data?.payload, filter.attributeKey.dataType, keyValueSuggestions, source]);
|
||||||
|
|
||||||
const currentAttributeKeys = attributeValues.slice(0, visibleItemsCount);
|
const currentAttributeKeys = attributeValues.slice(0, visibleItemsCount);
|
||||||
|
|
||||||
@ -478,12 +520,14 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
|||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
{isOpen && isLoading && !attributeValues.length && (
|
{isOpen &&
|
||||||
|
(isLoading || isLoadingKeyValueSuggestions) &&
|
||||||
|
!attributeValues.length && (
|
||||||
<section className="loading">
|
<section className="loading">
|
||||||
<Skeleton paragraph={{ rows: 4 }} />
|
<Skeleton paragraph={{ rows: 4 }} />
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
{isOpen && !isLoading && (
|
{isOpen && !isLoading && !isLoadingKeyValueSuggestions && (
|
||||||
<>
|
<>
|
||||||
{!isEmptyStateWithDocsEnabled && (
|
{!isEmptyStateWithDocsEnabled && (
|
||||||
<section className="search">
|
<section className="search">
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
.quick-filters-container {
|
.quick-filters-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.quick-filters-settings-container {
|
.quick-filters-settings-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@ -102,6 +104,37 @@
|
|||||||
margin: 8px 12px;
|
margin: 8px 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-filters-container {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.perilin-bg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
background: radial-gradient(circle, #fff 10%, transparent 0);
|
||||||
|
background-size: 12px 12px;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
mask-image: radial-gradient(
|
||||||
|
circle at 50% 0,
|
||||||
|
rgba(11, 12, 14, 0.1) 0,
|
||||||
|
rgba(11, 12, 14, 0) 100%
|
||||||
|
);
|
||||||
|
-webkit-mask-image: radial-gradient(
|
||||||
|
circle at 50% 0,
|
||||||
|
rgba(11, 12, 14, 0.1) 0,
|
||||||
|
rgba(11, 12, 14, 0) 100%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lightMode {
|
.lightMode {
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { LOCALSTORAGE } from 'constants/localStorage';
|
|||||||
import { useApiMonitoringParams } from 'container/ApiMonitoring/queryParams';
|
import { useApiMonitoringParams } from 'container/ApiMonitoring/queryParams';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { cloneDeep, isFunction, isNull } from 'lodash-es';
|
import { cloneDeep, isFunction, isNull } from 'lodash-es';
|
||||||
import { Settings2 as SettingsIcon } from 'lucide-react';
|
import { Frown, Settings2 as SettingsIcon } from 'lucide-react';
|
||||||
import { useAppContext } from 'providers/App/App';
|
import { useAppContext } from 'providers/App/App';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
@ -236,6 +236,13 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{filterConfig.length === 0 && (
|
||||||
|
<div className="no-filters-container">
|
||||||
|
<Frown size={16} />
|
||||||
|
<Typography.Text>No filters found</Typography.Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
</OverlayScrollbar>
|
</OverlayScrollbar>
|
||||||
|
|||||||
@ -6,4 +6,5 @@ export const SIGNAL_DATA_SOURCE_MAP = {
|
|||||||
[SignalType.TRACES]: DataSource.TRACES,
|
[SignalType.TRACES]: DataSource.TRACES,
|
||||||
[SignalType.EXCEPTIONS]: DataSource.TRACES,
|
[SignalType.EXCEPTIONS]: DataSource.TRACES,
|
||||||
[SignalType.API_MONITORING]: DataSource.TRACES,
|
[SignalType.API_MONITORING]: DataSource.TRACES,
|
||||||
|
[SignalType.METER_EXPLORER]: DataSource.METRICS,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -54,6 +54,7 @@ const quickFiltersListURL = `${BASE_URL}/api/v1/orgs/me/filters/${SIGNAL}`;
|
|||||||
const saveQuickFiltersURL = `${BASE_URL}/api/v1/orgs/me/filters`;
|
const saveQuickFiltersURL = `${BASE_URL}/api/v1/orgs/me/filters`;
|
||||||
const quickFiltersSuggestionsURL = `${BASE_URL}/api/v3/filter_suggestions`;
|
const quickFiltersSuggestionsURL = `${BASE_URL}/api/v3/filter_suggestions`;
|
||||||
const quickFiltersAttributeValuesURL = `${BASE_URL}/api/v3/autocomplete/attribute_values`;
|
const quickFiltersAttributeValuesURL = `${BASE_URL}/api/v3/autocomplete/attribute_values`;
|
||||||
|
const fieldsValuesURL = `${BASE_URL}/api/v1/fields/values`;
|
||||||
|
|
||||||
const FILTER_OS_DESCRIPTION = 'os.description';
|
const FILTER_OS_DESCRIPTION = 'os.description';
|
||||||
const FILTER_K8S_DEPLOYMENT_NAME = 'k8s.deployment.name';
|
const FILTER_K8S_DEPLOYMENT_NAME = 'k8s.deployment.name';
|
||||||
@ -77,7 +78,11 @@ const setupServer = (): void => {
|
|||||||
putHandler(await req.json());
|
putHandler(await req.json());
|
||||||
return res(ctx.status(200), ctx.json({}));
|
return res(ctx.status(200), ctx.json({}));
|
||||||
}),
|
}),
|
||||||
rest.get(quickFiltersAttributeValuesURL, (_, res, ctx) =>
|
|
||||||
|
rest.get(quickFiltersAttributeValuesURL, (req, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json(quickFiltersAttributeValuesResponse)),
|
||||||
|
),
|
||||||
|
rest.get(fieldsValuesURL, (req, res, ctx) =>
|
||||||
res(ctx.status(200), ctx.json(quickFiltersAttributeValuesResponse)),
|
res(ctx.status(200), ctx.json(quickFiltersAttributeValuesResponse)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -23,6 +23,7 @@ export enum SignalType {
|
|||||||
LOGS = 'logs',
|
LOGS = 'logs',
|
||||||
API_MONITORING = 'api_monitoring',
|
API_MONITORING = 'api_monitoring',
|
||||||
EXCEPTIONS = 'exceptions',
|
EXCEPTIONS = 'exceptions',
|
||||||
|
METER_EXPLORER = 'meter',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQuickFiltersConfig {
|
export interface IQuickFiltersConfig {
|
||||||
@ -53,4 +54,5 @@ export enum QuickFiltersSource {
|
|||||||
TRACES_EXPLORER = 'traces-explorer',
|
TRACES_EXPLORER = 'traces-explorer',
|
||||||
API_MONITORING = 'api-monitoring',
|
API_MONITORING = 'api-monitoring',
|
||||||
EXCEPTIONS = 'exceptions',
|
EXCEPTIONS = 'exceptions',
|
||||||
|
METER_EXPLORER = 'meter',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import {
|
|||||||
BoolOperators,
|
BoolOperators,
|
||||||
DataSource,
|
DataSource,
|
||||||
LogsAggregatorOperator,
|
LogsAggregatorOperator,
|
||||||
|
MeterAggregateOperator,
|
||||||
MetricAggregateOperator,
|
MetricAggregateOperator,
|
||||||
NumberOperators,
|
NumberOperators,
|
||||||
QueryAdditionalFilter,
|
QueryAdditionalFilter,
|
||||||
@ -36,6 +37,7 @@ import { v4 as uuid } from 'uuid';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
logsAggregateOperatorOptions,
|
logsAggregateOperatorOptions,
|
||||||
|
meterAggregateOperatorOptions,
|
||||||
metricAggregateOperatorOptions,
|
metricAggregateOperatorOptions,
|
||||||
metricsGaugeAggregateOperatorOptions,
|
metricsGaugeAggregateOperatorOptions,
|
||||||
metricsGaugeSpaceAggregateOperatorOptions,
|
metricsGaugeSpaceAggregateOperatorOptions,
|
||||||
@ -79,6 +81,7 @@ export const mapOfOperators = {
|
|||||||
metrics: metricAggregateOperatorOptions,
|
metrics: metricAggregateOperatorOptions,
|
||||||
logs: logsAggregateOperatorOptions,
|
logs: logsAggregateOperatorOptions,
|
||||||
traces: tracesAggregateOperatorOptions,
|
traces: tracesAggregateOperatorOptions,
|
||||||
|
meter: meterAggregateOperatorOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const metricsOperatorsByType = {
|
export const metricsOperatorsByType = {
|
||||||
@ -193,6 +196,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
|
|||||||
groupBy: [],
|
groupBy: [],
|
||||||
legend: '',
|
legend: '',
|
||||||
reduceTo: 'avg',
|
reduceTo: 'avg',
|
||||||
|
source: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialQueryBuilderFormLogsValues: IBuilderQuery = {
|
const initialQueryBuilderFormLogsValues: IBuilderQuery = {
|
||||||
@ -209,6 +213,39 @@ const initialQueryBuilderFormTracesValues: IBuilderQuery = {
|
|||||||
dataSource: DataSource.TRACES,
|
dataSource: DataSource.TRACES,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const initialQueryBuilderFormMeterValues: IBuilderQuery = {
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
|
||||||
|
aggregateOperator: MeterAggregateOperator.COUNT,
|
||||||
|
aggregateAttribute: initialAutocompleteData,
|
||||||
|
timeAggregation: MeterAggregateOperator.RATE,
|
||||||
|
spaceAggregation: MeterAggregateOperator.SUM,
|
||||||
|
filter: { expression: '' },
|
||||||
|
aggregations: [
|
||||||
|
{
|
||||||
|
metricName: '',
|
||||||
|
temporality: '',
|
||||||
|
timeAggregation: MeterAggregateOperator.COUNT,
|
||||||
|
spaceAggregation: MeterAggregateOperator.SUM,
|
||||||
|
reduceTo: 'avg',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
functions: [],
|
||||||
|
filters: { items: [], op: 'AND' },
|
||||||
|
expression: createNewBuilderItemName({
|
||||||
|
existNames: [],
|
||||||
|
sourceNames: alphabet,
|
||||||
|
}),
|
||||||
|
disabled: false,
|
||||||
|
stepInterval: undefined,
|
||||||
|
having: [],
|
||||||
|
limit: null,
|
||||||
|
orderBy: [],
|
||||||
|
groupBy: [],
|
||||||
|
legend: '',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
};
|
||||||
|
|
||||||
export const initialQueryBuilderFormValuesMap: Record<
|
export const initialQueryBuilderFormValuesMap: Record<
|
||||||
DataSource,
|
DataSource,
|
||||||
IBuilderQuery
|
IBuilderQuery
|
||||||
@ -285,6 +322,19 @@ export const initialQueriesMap: Record<DataSource, Query> = {
|
|||||||
traces: initialQueryTracesWithType,
|
traces: initialQueryTracesWithType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const initialQueryMeterWithType: Query = {
|
||||||
|
...initialQueryWithType,
|
||||||
|
builder: {
|
||||||
|
...initialQueryWithType.builder,
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
...initialQueryBuilderFormValuesMap.metrics,
|
||||||
|
source: 'meter',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const operatorsByTypes: Record<LocalDataType, string[]> = {
|
export const operatorsByTypes: Record<LocalDataType, string[]> = {
|
||||||
string: Object.values(StringOperators),
|
string: Object.values(StringOperators),
|
||||||
number: Object.values(NumberOperators),
|
number: Object.values(NumberOperators),
|
||||||
|
|||||||
@ -125,6 +125,126 @@ export const metricAggregateOperatorOptions: SelectOption<string, string>[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const meterAggregateOperatorOptions: SelectOption<string, string>[] = [
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.COUNT,
|
||||||
|
label: 'Count',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.COUNT_DISTINCT,
|
||||||
|
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||||
|
label: 'Count Distinct',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.SUM,
|
||||||
|
label: 'Sum',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.AVG,
|
||||||
|
label: 'Avg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.MAX,
|
||||||
|
label: 'Max',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.MIN,
|
||||||
|
label: 'Min',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.P05,
|
||||||
|
label: 'P05',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.P10,
|
||||||
|
label: 'P10',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.P20,
|
||||||
|
label: 'P20',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.P25,
|
||||||
|
label: 'P25',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.P50,
|
||||||
|
label: 'P50',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.P75,
|
||||||
|
label: 'P75',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.P90,
|
||||||
|
label: 'P90',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.P95,
|
||||||
|
label: 'P95',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.P99,
|
||||||
|
label: 'P99',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.RATE,
|
||||||
|
label: 'Rate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.SUM_RATE,
|
||||||
|
label: 'Sum_rate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.AVG_RATE,
|
||||||
|
label: 'Avg_rate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.MAX_RATE,
|
||||||
|
label: 'Max_rate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.MIN_RATE,
|
||||||
|
label: 'Min_rate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.RATE_SUM,
|
||||||
|
label: 'Rate_sum',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.RATE_AVG,
|
||||||
|
label: 'Rate_avg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.RATE_MIN,
|
||||||
|
label: 'Rate_min',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.RATE_MAX,
|
||||||
|
label: 'Rate_max',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.HIST_QUANTILE_50,
|
||||||
|
label: 'Hist_quantile_50',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.HIST_QUANTILE_75,
|
||||||
|
label: 'Hist_quantile_75',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.HIST_QUANTILE_90,
|
||||||
|
label: 'Hist_quantile_90',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.HIST_QUANTILE_95,
|
||||||
|
label: 'Hist_quantile_95',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MetricAggregateOperator.HIST_QUANTILE_99,
|
||||||
|
label: 'Hist_quantile_99',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const tracesAggregateOperatorOptions: SelectOption<string, string>[] = [
|
export const tracesAggregateOperatorOptions: SelectOption<string, string>[] = [
|
||||||
{
|
{
|
||||||
value: TracesAggregatorOperator.COUNT,
|
value: TracesAggregatorOperator.COUNT,
|
||||||
|
|||||||
@ -77,6 +77,9 @@ const ROUTES = {
|
|||||||
API_MONITORING: '/api-monitoring/explorer',
|
API_MONITORING: '/api-monitoring/explorer',
|
||||||
METRICS_EXPLORER_BASE: '/metrics-explorer',
|
METRICS_EXPLORER_BASE: '/metrics-explorer',
|
||||||
WORKSPACE_ACCESS_RESTRICTED: '/workspace-access-restricted',
|
WORKSPACE_ACCESS_RESTRICTED: '/workspace-access-restricted',
|
||||||
|
METER_EXPLORER_BASE: '/meter-explorer',
|
||||||
|
METER_EXPLORER: '/meter-explorer',
|
||||||
|
METER_EXPLORER_VIEWS: '/meter-explorer/views',
|
||||||
HOME_PAGE: '/',
|
HOME_PAGE: '/',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ function ExplorerOptionWrapper({
|
|||||||
sourcepage,
|
sourcepage,
|
||||||
isOneChartPerQuery,
|
isOneChartPerQuery,
|
||||||
splitedQueries,
|
splitedQueries,
|
||||||
|
signalSource,
|
||||||
}: ExplorerOptionsWrapperProps): JSX.Element {
|
}: ExplorerOptionsWrapperProps): JSX.Element {
|
||||||
const [isExplorerOptionHidden, setIsExplorerOptionHidden] = useState(false);
|
const [isExplorerOptionHidden, setIsExplorerOptionHidden] = useState(false);
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ function ExplorerOptionWrapper({
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onExport={onExport}
|
onExport={onExport}
|
||||||
sourcepage={sourcepage}
|
sourcepage={sourcepage}
|
||||||
|
signalSource={signalSource}
|
||||||
isExplorerOptionHidden={isExplorerOptionHidden}
|
isExplorerOptionHidden={isExplorerOptionHidden}
|
||||||
setIsExplorerOptionHidden={setIsExplorerOptionHidden}
|
setIsExplorerOptionHidden={setIsExplorerOptionHidden}
|
||||||
isOneChartPerQuery={isOneChartPerQuery}
|
isOneChartPerQuery={isOneChartPerQuery}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
.explorer-options-container {
|
.explorer-options-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 24px;
|
bottom: 8px;
|
||||||
left: calc(50% + 240px);
|
left: calc(50% + 240px);
|
||||||
transform: translate(calc(-50% - 120px), 0);
|
transform: translate(calc(-50% - 120px), 0);
|
||||||
transition: left 0.2s linear;
|
transition: left 0.2s linear;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 8px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
.multi-alert-button,
|
.multi-alert-button,
|
||||||
@ -33,11 +33,12 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 10px 10px;
|
padding: 10px 12px;
|
||||||
border-radius: 50px;
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
background: rgba(22, 24, 29, 0.6);
|
background: rgba(22, 24, 29, 0.6);
|
||||||
|
border: 1px solid var(--bg-slate-500);
|
||||||
|
border-radius: 4px;
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
.action-icon {
|
.action-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -64,9 +65,9 @@
|
|||||||
|
|
||||||
.explorer-options {
|
.explorer-options {
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border: 1px solid var(--bg-slate-400);
|
border-radius: 4px;
|
||||||
border-radius: 50px;
|
border: 1px solid var(--bg-slate-500);
|
||||||
background: rgba(22, 24, 29, 0.6);
|
background: var(--bg-ink-400);
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
|
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|||||||
@ -93,6 +93,7 @@ function ExplorerOptions({
|
|||||||
onExport,
|
onExport,
|
||||||
query,
|
query,
|
||||||
sourcepage,
|
sourcepage,
|
||||||
|
signalSource,
|
||||||
isExplorerOptionHidden = false,
|
isExplorerOptionHidden = false,
|
||||||
setIsExplorerOptionHidden,
|
setIsExplorerOptionHidden,
|
||||||
isOneChartPerQuery = false,
|
isOneChartPerQuery = false,
|
||||||
@ -110,6 +111,7 @@ function ExplorerOptions({
|
|||||||
|
|
||||||
const isLogsExplorer = sourcepage === DataSource.LOGS;
|
const isLogsExplorer = sourcepage === DataSource.LOGS;
|
||||||
const isMetricsExplorer = sourcepage === DataSource.METRICS;
|
const isMetricsExplorer = sourcepage === DataSource.METRICS;
|
||||||
|
const isMeterExplorer = signalSource === 'meter';
|
||||||
|
|
||||||
const PRESERVED_VIEW_LOCAL_STORAGE_KEY = LOCALSTORAGE.LAST_USED_SAVED_VIEWS;
|
const PRESERVED_VIEW_LOCAL_STORAGE_KEY = LOCALSTORAGE.LAST_USED_SAVED_VIEWS;
|
||||||
|
|
||||||
@ -120,8 +122,11 @@ function ExplorerOptions({
|
|||||||
if (isMetricsExplorer) {
|
if (isMetricsExplorer) {
|
||||||
return PreservedViewsTypes.METRICS;
|
return PreservedViewsTypes.METRICS;
|
||||||
}
|
}
|
||||||
|
if (isMeterExplorer) {
|
||||||
|
return PreservedViewsTypes.METER;
|
||||||
|
}
|
||||||
return PreservedViewsTypes.TRACES;
|
return PreservedViewsTypes.TRACES;
|
||||||
}, [isLogsExplorer, isMetricsExplorer]);
|
}, [isLogsExplorer, isMetricsExplorer, isMeterExplorer]);
|
||||||
|
|
||||||
const onModalToggle = useCallback((value: boolean) => {
|
const onModalToggle = useCallback((value: boolean) => {
|
||||||
setIsExport(value);
|
setIsExport(value);
|
||||||
@ -150,6 +155,10 @@ function ExplorerOptions({
|
|||||||
[MetricsExplorerEventKeys.OneChartPerQueryEnabled]: isOneChartPerQuery,
|
[MetricsExplorerEventKeys.OneChartPerQueryEnabled]: isOneChartPerQuery,
|
||||||
panelType,
|
panelType,
|
||||||
});
|
});
|
||||||
|
} else if (isMeterExplorer) {
|
||||||
|
logEvent('Meter Explorer: Save view clicked', {
|
||||||
|
panelType,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setIsSaveModalOpen(!isSaveModalOpen);
|
setIsSaveModalOpen(!isSaveModalOpen);
|
||||||
};
|
};
|
||||||
@ -243,7 +252,7 @@ function ExplorerOptions({
|
|||||||
error,
|
error,
|
||||||
isRefetching,
|
isRefetching,
|
||||||
refetch: refetchAllView,
|
refetch: refetchAllView,
|
||||||
} = useGetAllViews(sourcepage);
|
} = useGetAllViews(isMeterExplorer ? 'meter' : sourcepage);
|
||||||
|
|
||||||
const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType);
|
const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType);
|
||||||
|
|
||||||
@ -316,7 +325,7 @@ function ExplorerOptions({
|
|||||||
compositeQuery,
|
compositeQuery,
|
||||||
viewKey,
|
viewKey,
|
||||||
extraData: updatedExtraData,
|
extraData: updatedExtraData,
|
||||||
sourcePage: sourcepage,
|
sourcePage: isMeterExplorer ? 'meter' : sourcepage,
|
||||||
viewName,
|
viewName,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -332,7 +341,7 @@ function ExplorerOptions({
|
|||||||
compositeQuery: mapCompositeQueryFromQuery(currentQuery, panelType),
|
compositeQuery: mapCompositeQueryFromQuery(currentQuery, panelType),
|
||||||
viewKey,
|
viewKey,
|
||||||
extraData: updatedExtraData,
|
extraData: updatedExtraData,
|
||||||
sourcePage: sourcepage,
|
sourcePage: isMeterExplorer ? 'meter' : sourcepage,
|
||||||
viewName,
|
viewName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -459,6 +468,11 @@ function ExplorerOptions({
|
|||||||
panelType,
|
panelType,
|
||||||
viewName: option?.value,
|
viewName: option?.value,
|
||||||
});
|
});
|
||||||
|
} else if (isMeterExplorer) {
|
||||||
|
logEvent('Meter Explorer: Select view', {
|
||||||
|
panelType,
|
||||||
|
viewName: option?.value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePreservedViewInLocalStorage(option);
|
updatePreservedViewInLocalStorage(option);
|
||||||
@ -505,6 +519,11 @@ function ExplorerOptions({
|
|||||||
: defaultLogsSelectedColumns,
|
: defaultLogsSelectedColumns,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (signalSource === 'meter') {
|
||||||
|
history.replace(ROUTES.METER_EXPLORER);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
history.replace(DATASOURCE_VS_ROUTES[sourcepage]);
|
history.replace(DATASOURCE_VS_ROUTES[sourcepage]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -549,7 +568,7 @@ function ExplorerOptions({
|
|||||||
redirectWithQueryBuilderData,
|
redirectWithQueryBuilderData,
|
||||||
refetchAllView,
|
refetchAllView,
|
||||||
saveViewAsync,
|
saveViewAsync,
|
||||||
sourcePage: sourcepage,
|
sourcePage: isMeterExplorer ? 'meter' : sourcepage,
|
||||||
viewName: newViewName,
|
viewName: newViewName,
|
||||||
setNewViewName,
|
setNewViewName,
|
||||||
});
|
});
|
||||||
@ -668,7 +687,7 @@ function ExplorerOptions({
|
|||||||
return `Query ${query.builder.queryData[0].queryName}`;
|
return `Query ${query.builder.queryData[0].queryName}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const alertButton = useMemo(() => {
|
const CreateAlertButton = useMemo(() => {
|
||||||
if (isOneChartPerQuery) {
|
if (isOneChartPerQuery) {
|
||||||
const selectLabel = (
|
const selectLabel = (
|
||||||
<Button
|
<Button
|
||||||
@ -721,7 +740,7 @@ function ExplorerOptions({
|
|||||||
splitedQueries,
|
splitedQueries,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const dashboardButton = useMemo(() => {
|
const AddToDashboardButton = useMemo(() => {
|
||||||
if (isOneChartPerQuery) {
|
if (isOneChartPerQuery) {
|
||||||
const selectLabel = (
|
const selectLabel = (
|
||||||
<Button
|
<Button
|
||||||
@ -829,7 +848,7 @@ function ExplorerOptions({
|
|||||||
style={{
|
style={{
|
||||||
background: extraData
|
background: extraData
|
||||||
? `linear-gradient(90deg, rgba(0,0,0,0) -5%, ${rgbaColor} 9%, rgba(0,0,0,0) 30%)`
|
? `linear-gradient(90deg, rgba(0,0,0,0) -5%, ${rgbaColor} 9%, rgba(0,0,0,0) 30%)`
|
||||||
: 'transparent',
|
: 'initial',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="view-options">
|
<div className="view-options">
|
||||||
@ -884,10 +903,13 @@ function ExplorerOptions({
|
|||||||
|
|
||||||
<hr className={isEditDeleteSupported ? '' : 'hidden'} />
|
<hr className={isEditDeleteSupported ? '' : 'hidden'} />
|
||||||
|
|
||||||
|
{signalSource !== 'meter' && (
|
||||||
<div className={cx('actions', isEditDeleteSupported ? '' : 'hidden')}>
|
<div className={cx('actions', isEditDeleteSupported ? '' : 'hidden')}>
|
||||||
{alertButton}
|
{CreateAlertButton}
|
||||||
{dashboardButton}
|
{AddToDashboardButton}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
{/* Hide the info icon for metrics explorer until we get the docs link */}
|
{/* Hide the info icon for metrics explorer until we get the docs link */}
|
||||||
{!isMetricsExplorer && (
|
{!isMetricsExplorer && (
|
||||||
@ -993,6 +1015,7 @@ export interface ExplorerOptionsProps {
|
|||||||
query: Query | null;
|
query: Query | null;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
sourcepage: DataSource;
|
sourcepage: DataSource;
|
||||||
|
signalSource?: string;
|
||||||
isExplorerOptionHidden?: boolean;
|
isExplorerOptionHidden?: boolean;
|
||||||
setIsExplorerOptionHidden?: Dispatch<SetStateAction<boolean>>;
|
setIsExplorerOptionHidden?: Dispatch<SetStateAction<boolean>>;
|
||||||
isOneChartPerQuery?: boolean;
|
isOneChartPerQuery?: boolean;
|
||||||
@ -1005,6 +1028,7 @@ ExplorerOptions.defaultProps = {
|
|||||||
setIsExplorerOptionHidden: undefined,
|
setIsExplorerOptionHidden: undefined,
|
||||||
isOneChartPerQuery: false,
|
isOneChartPerQuery: false,
|
||||||
splitedQueries: [],
|
splitedQueries: [],
|
||||||
|
signalSource: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ExplorerOptions;
|
export default ExplorerOptions;
|
||||||
|
|||||||
@ -2,4 +2,5 @@ export enum PreservedViewsTypes {
|
|||||||
LOGS = 'logs',
|
LOGS = 'logs',
|
||||||
TRACES = 'traces',
|
TRACES = 'traces',
|
||||||
METRICS = 'metrics',
|
METRICS = 'metrics',
|
||||||
|
METER = 'meter',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { PreservedViewsTypes } from './constants';
|
|||||||
export interface SaveNewViewHandlerProps {
|
export interface SaveNewViewHandlerProps {
|
||||||
viewName: string;
|
viewName: string;
|
||||||
compositeQuery: ICompositeMetricQuery;
|
compositeQuery: ICompositeMetricQuery;
|
||||||
sourcePage: DataSource;
|
sourcePage: DataSource | 'meter';
|
||||||
extraData: SaveViewProps['extraData'];
|
extraData: SaveViewProps['extraData'];
|
||||||
panelType: PANEL_TYPES | null;
|
panelType: PANEL_TYPES | null;
|
||||||
notifications: NotificationInstance;
|
notifications: NotificationInstance;
|
||||||
@ -32,7 +32,8 @@ export interface SaveNewViewHandlerProps {
|
|||||||
export type PreservedViewType =
|
export type PreservedViewType =
|
||||||
| PreservedViewsTypes.LOGS
|
| PreservedViewsTypes.LOGS
|
||||||
| PreservedViewsTypes.TRACES
|
| PreservedViewsTypes.TRACES
|
||||||
| PreservedViewsTypes.METRICS;
|
| PreservedViewsTypes.METRICS
|
||||||
|
| PreservedViewsTypes.METER;
|
||||||
|
|
||||||
export type PreservedViewsInLocalStorage = Partial<
|
export type PreservedViewsInLocalStorage = Partial<
|
||||||
Record<PreservedViewType, { key: string; value: string }>
|
Record<PreservedViewType, { key: string; value: string }>
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export const saveNewViewHandler = ({
|
|||||||
{
|
{
|
||||||
viewName,
|
viewName,
|
||||||
compositeQuery,
|
compositeQuery,
|
||||||
sourcePage,
|
sourcePage: sourcePage as DataSource,
|
||||||
extraData,
|
extraData,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -0,0 +1,195 @@
|
|||||||
|
.meter-explorer-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.meter-explorer-quick-filters-section {
|
||||||
|
width: 280px;
|
||||||
|
border-right: 1px solid var(--bg-slate-500);
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meter-explorer-content-section {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.explore-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 4px 0;
|
||||||
|
padding: 0 8px;
|
||||||
|
|
||||||
|
.explore-header-left-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explore-header-right-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-section {
|
||||||
|
max-height: 450px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.rc-virtual-list-holder {
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.explore-tabs {
|
||||||
|
margin: 15px 0;
|
||||||
|
.tab {
|
||||||
|
background-color: var(--bg-slate-500);
|
||||||
|
border-color: var(--bg-ink-200);
|
||||||
|
width: 180px;
|
||||||
|
padding: 16px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:first-of-type {
|
||||||
|
border-top-left-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:last-of-type {
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-view {
|
||||||
|
background: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.explore-content {
|
||||||
|
.ant-space {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-meter-search {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-series-view-panel {
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--bg-slate-500);
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
padding: 8px !important;
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-series-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(
|
||||||
|
auto-fit,
|
||||||
|
minmax(min(100%, calc(50% - 8px)), 1fr)
|
||||||
|
);
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.quick-filters-open {
|
||||||
|
.meter-explorer-content-section {
|
||||||
|
width: calc(100% - 280px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
padding-bottom: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meter-time-series-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.builder-units-filter {
|
||||||
|
padding: 0 8px;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
|
||||||
|
.builder-units-filter-label {
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightMode {
|
||||||
|
.meter-explorer-container {
|
||||||
|
.explore-tabs {
|
||||||
|
.tab {
|
||||||
|
background-color: var(--bg-vanilla-100);
|
||||||
|
border-color: var(--bg-vanilla-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-view {
|
||||||
|
background: var(--bg-vanilla-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboards-and-alerts-popover-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.dashboards-and-alerts-popover {
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboards-popover {
|
||||||
|
border: 1px solid var(--bg-sienna-500);
|
||||||
|
.ant-typography {
|
||||||
|
color: var(--bg-sienna-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alerts-popover {
|
||||||
|
border: 1px solid var(--bg-sakura-500);
|
||||||
|
.ant-typography {
|
||||||
|
color: var(--bg-sakura-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.no-data-text {
|
||||||
|
color: var(--text-vanilla-500);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
182
frontend/src/container/MeterExplorer/Explorer/Explorer.tsx
Normal file
182
frontend/src/container/MeterExplorer/Explorer/Explorer.tsx
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import './Explorer.styles.scss';
|
||||||
|
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
|
import { Button, Tooltip } from 'antd';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import { QueryBuilderV2 } from 'components/QueryBuilderV2/QueryBuilderV2';
|
||||||
|
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||||
|
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
|
||||||
|
import { initialQueryMeterWithType, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper';
|
||||||
|
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
|
||||||
|
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
||||||
|
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||||
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||||
|
import { Filter } from 'lucide-react';
|
||||||
|
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
import { MeterExplorerEventKeys, MeterExplorerEvents } from '../events';
|
||||||
|
import TimeSeries from './TimeSeries';
|
||||||
|
import { splitQueryIntoOneChartPerQuery } from './utils';
|
||||||
|
|
||||||
|
function Explorer(): JSX.Element {
|
||||||
|
const {
|
||||||
|
handleRunQuery,
|
||||||
|
stagedQuery,
|
||||||
|
updateAllQueriesOperators,
|
||||||
|
currentQuery,
|
||||||
|
} = useQueryBuilder();
|
||||||
|
const { safeNavigate } = useSafeNavigate();
|
||||||
|
|
||||||
|
const [showQuickFilters, setShowQuickFilters] = useState(true);
|
||||||
|
|
||||||
|
const defaultQuery = useMemo(
|
||||||
|
() =>
|
||||||
|
updateAllQueriesOperators(
|
||||||
|
initialQueryMeterWithType,
|
||||||
|
PANEL_TYPES.TIME_SERIES,
|
||||||
|
DataSource.METRICS,
|
||||||
|
'meter' as 'meter' | '',
|
||||||
|
),
|
||||||
|
[updateAllQueriesOperators],
|
||||||
|
);
|
||||||
|
|
||||||
|
const exportDefaultQuery = useMemo(
|
||||||
|
() =>
|
||||||
|
updateAllQueriesOperators(
|
||||||
|
currentQuery || initialQueryMeterWithType,
|
||||||
|
PANEL_TYPES.TIME_SERIES,
|
||||||
|
DataSource.METRICS,
|
||||||
|
'meter' as 'meter' | '',
|
||||||
|
),
|
||||||
|
[currentQuery, updateAllQueriesOperators],
|
||||||
|
);
|
||||||
|
|
||||||
|
useShareBuilderUrl({ defaultValue: defaultQuery });
|
||||||
|
|
||||||
|
const handleExport = useCallback(
|
||||||
|
(
|
||||||
|
dashboard: Dashboard | null,
|
||||||
|
_isNewDashboard?: boolean,
|
||||||
|
queryToExport?: Query,
|
||||||
|
): void => {
|
||||||
|
if (!dashboard) return;
|
||||||
|
|
||||||
|
const widgetId = uuid();
|
||||||
|
|
||||||
|
const dashboardEditView = generateExportToDashboardLink({
|
||||||
|
query: queryToExport || exportDefaultQuery,
|
||||||
|
panelType: PANEL_TYPES.TIME_SERIES,
|
||||||
|
dashboardId: dashboard.id,
|
||||||
|
widgetId,
|
||||||
|
});
|
||||||
|
|
||||||
|
safeNavigate(dashboardEditView);
|
||||||
|
},
|
||||||
|
[exportDefaultQuery, safeNavigate],
|
||||||
|
);
|
||||||
|
|
||||||
|
const splitedQueries = useMemo(
|
||||||
|
() =>
|
||||||
|
splitQueryIntoOneChartPerQuery(stagedQuery || initialQueryMeterWithType),
|
||||||
|
[stagedQuery],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
logEvent(MeterExplorerEvents.TabChanged, {
|
||||||
|
[MeterExplorerEventKeys.Tab]: 'explorer',
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const queryComponents = useMemo(
|
||||||
|
(): QueryBuilderProps['queryComponents'] => ({}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||||
|
<div
|
||||||
|
className={cx('meter-explorer-container', {
|
||||||
|
'quick-filters-open': showQuickFilters,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cx('meter-explorer-quick-filters-section', {
|
||||||
|
hidden: !showQuickFilters,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<QuickFilters
|
||||||
|
className="qf-meter-explorer"
|
||||||
|
source={QuickFiltersSource.METER_EXPLORER}
|
||||||
|
signal={SignalType.METER_EXPLORER}
|
||||||
|
showFilterCollapse
|
||||||
|
showQueryName={false}
|
||||||
|
handleFilterVisibilityChange={(): void => {
|
||||||
|
setShowQuickFilters(!showQuickFilters);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="meter-explorer-content-section">
|
||||||
|
<div className="meter-explorer-explore-content">
|
||||||
|
<div className="explore-header">
|
||||||
|
<div className="explore-header-left-actions">
|
||||||
|
{!showQuickFilters && (
|
||||||
|
<Tooltip title="Show Quick Filters" placement="right" arrow={false}>
|
||||||
|
<Button
|
||||||
|
className="periscope-btn outline"
|
||||||
|
icon={<Filter size={16} />}
|
||||||
|
onClick={(): void => setShowQuickFilters(!showQuickFilters)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="explore-header-right-actions">
|
||||||
|
<DateTimeSelector showAutoRefresh />
|
||||||
|
<RightToolbarActions
|
||||||
|
onStageRunQuery={(): void => handleRunQuery(true, true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<QueryBuilderV2
|
||||||
|
config={{
|
||||||
|
initialDataSource: DataSource.METRICS,
|
||||||
|
queryVariant: 'static',
|
||||||
|
signalSource: 'meter',
|
||||||
|
}}
|
||||||
|
panelType={PANEL_TYPES.TIME_SERIES}
|
||||||
|
queryComponents={queryComponents}
|
||||||
|
showFunctions={false}
|
||||||
|
version="v3"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="explore-content">
|
||||||
|
<TimeSeries />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ExplorerOptionWrapper
|
||||||
|
disabled={!stagedQuery}
|
||||||
|
query={exportDefaultQuery}
|
||||||
|
sourcepage={DataSource.METRICS}
|
||||||
|
signalSource="meter"
|
||||||
|
onExport={handleExport}
|
||||||
|
isOneChartPerQuery={false}
|
||||||
|
splitedQueries={splitedQueries}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Sentry.ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Explorer;
|
||||||
13
frontend/src/container/MeterExplorer/Explorer/NoData.tsx
Normal file
13
frontend/src/container/MeterExplorer/Explorer/NoData.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Typography } from 'antd';
|
||||||
|
import { ChartLine } from 'lucide-react';
|
||||||
|
|
||||||
|
export default function NoData(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div className="no-data-container">
|
||||||
|
<ChartLine size={48} />
|
||||||
|
<Typography.Text className="no-data-text">
|
||||||
|
No data found for the selected query
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import { Button } from 'antd';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { QueryBuilder } from 'container/QueryBuilder';
|
||||||
|
import { ButtonWrapper } from 'container/TracesExplorer/QuerySection/styles';
|
||||||
|
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { MeterExplorerEventKeys, MeterExplorerEvents } from '../events';
|
||||||
|
|
||||||
|
function QuerySection(): JSX.Element {
|
||||||
|
const { handleRunQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.TIME_SERIES);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="query-section">
|
||||||
|
<QueryBuilder
|
||||||
|
panelType={panelTypes}
|
||||||
|
config={{ initialDataSource: DataSource.METRICS, queryVariant: 'static' }}
|
||||||
|
version="v4"
|
||||||
|
actions={
|
||||||
|
<ButtonWrapper>
|
||||||
|
<Button
|
||||||
|
onClick={(): void => {
|
||||||
|
handleRunQuery();
|
||||||
|
logEvent(MeterExplorerEvents.QueryBuilderQueryChanged, {
|
||||||
|
[MeterExplorerEventKeys.Tab]: 'explorer',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
Run Query
|
||||||
|
</Button>
|
||||||
|
</ButtonWrapper>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QuerySection;
|
||||||
142
frontend/src/container/MeterExplorer/Explorer/TimeSeries.tsx
Normal file
142
frontend/src/container/MeterExplorer/Explorer/TimeSeries.tsx
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import { isAxiosError } from 'axios';
|
||||||
|
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||||
|
import { initialQueryMeterWithType, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
|
import { BuilderUnitsFilter } from 'container/QueryBuilder/filters/BuilderUnitsFilter';
|
||||||
|
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
||||||
|
import { convertDataValueToMs } from 'container/TimeSeriesView/utils';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||||
|
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { useQueries } from 'react-query';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { SuccessResponse } from 'types/api';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
|
function TimeSeries(): JSX.Element {
|
||||||
|
const { stagedQuery, currentQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector<
|
||||||
|
AppState,
|
||||||
|
GlobalReducer
|
||||||
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
|
const [yAxisUnit, setYAxisUnit] = useState<string>('');
|
||||||
|
|
||||||
|
const isValidToConvertToMs = useMemo(() => {
|
||||||
|
const isValid: boolean[] = [];
|
||||||
|
|
||||||
|
currentQuery.builder.queryData.forEach(
|
||||||
|
({ aggregateAttribute, aggregateOperator }) => {
|
||||||
|
const isExistDurationNanoAttribute =
|
||||||
|
aggregateAttribute?.key === 'durationNano' ||
|
||||||
|
aggregateAttribute?.key === 'duration_nano';
|
||||||
|
|
||||||
|
const isCountOperator =
|
||||||
|
aggregateOperator === 'count' || aggregateOperator === 'count_distinct';
|
||||||
|
|
||||||
|
isValid.push(!isCountOperator && isExistDurationNanoAttribute);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return isValid.every(Boolean);
|
||||||
|
}, [currentQuery]);
|
||||||
|
|
||||||
|
const queryPayloads = useMemo(
|
||||||
|
() => [stagedQuery || initialQueryMeterWithType],
|
||||||
|
[stagedQuery],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { showErrorModal } = useErrorModal();
|
||||||
|
|
||||||
|
const queries = useQueries(
|
||||||
|
queryPayloads.map((payload, index) => ({
|
||||||
|
queryKey: [
|
||||||
|
REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||||
|
payload,
|
||||||
|
ENTITY_VERSION_V5,
|
||||||
|
globalSelectedTime,
|
||||||
|
maxTime,
|
||||||
|
minTime,
|
||||||
|
index,
|
||||||
|
],
|
||||||
|
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
||||||
|
GetMetricQueryRange(
|
||||||
|
{
|
||||||
|
query: payload,
|
||||||
|
graphType: PANEL_TYPES.TIME_SERIES,
|
||||||
|
selectedTime: 'GLOBAL_TIME',
|
||||||
|
globalSelectedInterval: globalSelectedTime,
|
||||||
|
params: {
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ENTITY_VERSION_V5,
|
||||||
|
),
|
||||||
|
enabled: !!payload,
|
||||||
|
retry: (failureCount: number, error: Error): boolean => {
|
||||||
|
let status: number | undefined;
|
||||||
|
|
||||||
|
if (error instanceof APIError) {
|
||||||
|
status = error.getHttpStatusCode();
|
||||||
|
} else if (isAxiosError(error)) {
|
||||||
|
status = error.response?.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status && status >= 400 && status < 500) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return failureCount < 3;
|
||||||
|
},
|
||||||
|
onError: (error: APIError): void => {
|
||||||
|
showErrorModal(error);
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = useMemo(() => queries.map(({ data }) => data) ?? [], [queries]);
|
||||||
|
|
||||||
|
const responseData = useMemo(
|
||||||
|
() =>
|
||||||
|
data.map((datapoint) =>
|
||||||
|
isValidToConvertToMs ? convertDataValueToMs(datapoint) : datapoint,
|
||||||
|
),
|
||||||
|
[data, isValidToConvertToMs],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onUnitChangeHandler = (value: string): void => {
|
||||||
|
setYAxisUnit(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="meter-time-series-container">
|
||||||
|
<BuilderUnitsFilter onChange={onUnitChangeHandler} yAxisUnit={yAxisUnit} />
|
||||||
|
<div className="time-series-container">
|
||||||
|
{responseData.map((datapoint, index) => (
|
||||||
|
<div
|
||||||
|
className="time-series-view-panel"
|
||||||
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
<TimeSeriesView
|
||||||
|
isFilterApplied={false}
|
||||||
|
isError={queries[index].isError}
|
||||||
|
isLoading={queries[index].isLoading}
|
||||||
|
data={datapoint}
|
||||||
|
dataSource={DataSource.METRICS}
|
||||||
|
yAxisUnit={yAxisUnit}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TimeSeries;
|
||||||
3
frontend/src/container/MeterExplorer/Explorer/index.ts
Normal file
3
frontend/src/container/MeterExplorer/Explorer/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import Explorer from './Explorer';
|
||||||
|
|
||||||
|
export default Explorer;
|
||||||
37
frontend/src/container/MeterExplorer/Explorer/types.ts
Normal file
37
frontend/src/container/MeterExplorer/Explorer/types.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { RelatedMetric } from 'api/metricsExplorer/getRelatedMetrics';
|
||||||
|
import { UseQueryResult } from 'react-query';
|
||||||
|
import { SuccessResponse } from 'types/api';
|
||||||
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
|
||||||
|
export enum ExplorerTabs {
|
||||||
|
TIME_SERIES = 'time-series',
|
||||||
|
RELATED_METRICS = 'related-metrics',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeSeriesProps {
|
||||||
|
showOneChartPerQuery: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RelatedMetricsProps {
|
||||||
|
metricNames: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RelatedMetricsCardProps {
|
||||||
|
metric: RelatedMetricWithQueryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseGetRelatedMetricsGraphsProps {
|
||||||
|
selectedMetricName: string | null;
|
||||||
|
startMs: number;
|
||||||
|
endMs: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseGetRelatedMetricsGraphsReturn {
|
||||||
|
relatedMetrics: RelatedMetricWithQueryResult[];
|
||||||
|
isRelatedMetricsLoading: boolean;
|
||||||
|
isRelatedMetricsError: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RelatedMetricWithQueryResult extends RelatedMetric {
|
||||||
|
queryResult: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>;
|
||||||
|
}
|
||||||
37
frontend/src/container/MeterExplorer/Explorer/utils.tsx
Normal file
37
frontend/src/container/MeterExplorer/Explorer/utils.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
export const splitQueryIntoOneChartPerQuery = (query: Query): Query[] => {
|
||||||
|
const queries: Query[] = [];
|
||||||
|
|
||||||
|
query.builder.queryData.forEach((currentQuery) => {
|
||||||
|
const newQuery = {
|
||||||
|
...query,
|
||||||
|
id: uuid(),
|
||||||
|
builder: {
|
||||||
|
...query.builder,
|
||||||
|
queryData: [currentQuery],
|
||||||
|
queryFormulas: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
queries.push(newQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
query.builder.queryFormulas.forEach((currentFormula) => {
|
||||||
|
const newQuery = {
|
||||||
|
...query,
|
||||||
|
id: uuid(),
|
||||||
|
builder: {
|
||||||
|
...query.builder,
|
||||||
|
queryFormulas: [currentFormula],
|
||||||
|
queryData: query.builder.queryData.map((currentQuery) => ({
|
||||||
|
...currentQuery,
|
||||||
|
disabled: true,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
queries.push(newQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
return queries;
|
||||||
|
};
|
||||||
51
frontend/src/container/MeterExplorer/events.ts
Normal file
51
frontend/src/container/MeterExplorer/events.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* This file contains all analytics events for the Meter Explorer.
|
||||||
|
*/
|
||||||
|
export enum MeterExplorerEvents {
|
||||||
|
TabChanged = 'Meter Explorer: Tab visited',
|
||||||
|
ModalOpened = 'Meter Explorer: Modal opened',
|
||||||
|
MeterClicked = 'Meter Explorer: Meter clicked',
|
||||||
|
FilterApplied = 'Meter Explorer: Filter applied',
|
||||||
|
TreemapViewChanged = 'Meter Explorer: Treemap view changed',
|
||||||
|
PageNumberChanged = 'Meter Explorer: Page number changed',
|
||||||
|
PageSizeChanged = 'Meter Explorer: Page size changed',
|
||||||
|
OrderByApplied = 'Meter Explorer: Order by applied',
|
||||||
|
MetricMetadataUpdated = 'Meter Explorer: Metric metadata updated',
|
||||||
|
OpenInExplorerClicked = 'Meter Explorer: Open in explorer clicked',
|
||||||
|
InspectViewChanged = 'Meter Explorer: Inspect view changed',
|
||||||
|
InspectQueryChanged = 'Meter Explorer: Inspect query changed',
|
||||||
|
InspectPointClicked = 'Meter Explorer: Inspect point clicked',
|
||||||
|
QueryBuilderQueryChanged = 'Meter Explorer: QueryBuilder query changed',
|
||||||
|
YAxisUnitApplied = 'Meter Explorer: Y axis unit applied',
|
||||||
|
AddToAlertClicked = 'Meter Explorer: Add to alert clicked',
|
||||||
|
AddToDashboardClicked = 'Meter Explorer: Add to dashboard clicked',
|
||||||
|
SaveViewClicked = 'Meter Explorer: Save view clicked',
|
||||||
|
SearchApplied = 'Meter Explorer: Search applied',
|
||||||
|
ViewEdited = 'Meter Explorer: View edited',
|
||||||
|
ViewDeleted = 'Meter Explorer: View deleted',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MeterExplorerEventKeys {
|
||||||
|
Tab = 'tab',
|
||||||
|
Modal = 'modal',
|
||||||
|
View = 'view',
|
||||||
|
Interval = 'interval',
|
||||||
|
ViewType = 'viewType',
|
||||||
|
PageNumber = 'pageNumber',
|
||||||
|
PageSize = 'pageSize',
|
||||||
|
ColumnName = 'columnName',
|
||||||
|
Order = 'order',
|
||||||
|
AttributeKey = 'attributeKey',
|
||||||
|
AttributeValue = 'attributeValue',
|
||||||
|
MetricName = 'metricName',
|
||||||
|
InspectView = 'inspectView',
|
||||||
|
TimeAggregationOption = 'timeAggregationOption',
|
||||||
|
TimeAggregationInterval = 'timeAggregationInterval',
|
||||||
|
SpaceAggregationOption = 'spaceAggregationOption',
|
||||||
|
SpaceAggregationLabels = 'spaceAggregationLabels',
|
||||||
|
OneChartPerQueryEnabled = 'oneChartPerQueryEnabled',
|
||||||
|
YAxisUnit = 'yAxisUnit',
|
||||||
|
ViewName = 'viewName',
|
||||||
|
Filters = 'filters',
|
||||||
|
TimeRange = 'timeRange',
|
||||||
|
}
|
||||||
@ -189,7 +189,7 @@ function Explorer(): JSX.Element {
|
|||||||
query={exportDefaultQuery}
|
query={exportDefaultQuery}
|
||||||
sourcepage={DataSource.METRICS}
|
sourcepage={DataSource.METRICS}
|
||||||
onExport={handleExport}
|
onExport={handleExport}
|
||||||
isOneChartPerQuery={showOneChartPerQuery}
|
isOneChartPerQuery={false}
|
||||||
splitedQueries={splitedQueries}
|
splitedQueries={splitedQueries}
|
||||||
/>
|
/>
|
||||||
</Sentry.ErrorBoundary>
|
</Sentry.ErrorBoundary>
|
||||||
|
|||||||
@ -17,8 +17,9 @@ export type QueryBuilderConfig =
|
|||||||
| {
|
| {
|
||||||
queryVariant: 'static';
|
queryVariant: 'static';
|
||||||
initialDataSource: DataSource;
|
initialDataSource: DataSource;
|
||||||
|
signalSource?: string;
|
||||||
}
|
}
|
||||||
| { queryVariant: 'dropdown' };
|
| { queryVariant: 'dropdown'; signalSource?: string };
|
||||||
|
|
||||||
export type QueryBuilderProps = {
|
export type QueryBuilderProps = {
|
||||||
config?: QueryBuilderConfig;
|
config?: QueryBuilderConfig;
|
||||||
|
|||||||
@ -11,4 +11,5 @@ export type QueryProps = {
|
|||||||
version: string;
|
version: string;
|
||||||
showSpanScopeSelector?: boolean;
|
showSpanScopeSelector?: boolean;
|
||||||
showOnlyWhereClause?: boolean;
|
showOnlyWhereClause?: boolean;
|
||||||
|
signalSource?: string;
|
||||||
} & Pick<QueryBuilderProps, 'filterConfigs' | 'queryComponents'>;
|
} & Pick<QueryBuilderProps, 'filterConfigs' | 'queryComponents'>;
|
||||||
|
|||||||
@ -8,4 +8,5 @@ export type AgregatorFilterProps = Pick<AutoCompleteProps, 'disabled'> & {
|
|||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
onSelect?: (value: BaseAutocompleteData) => void;
|
onSelect?: (value: BaseAutocompleteData) => void;
|
||||||
index?: number;
|
index?: number;
|
||||||
|
signalSource?: 'meter' | '';
|
||||||
};
|
};
|
||||||
|
|||||||
@ -38,6 +38,7 @@ export const AggregatorFilter = memo(function AggregatorFilter({
|
|||||||
defaultValue,
|
defaultValue,
|
||||||
onSelect,
|
onSelect,
|
||||||
index,
|
index,
|
||||||
|
signalSource,
|
||||||
}: AgregatorFilterProps): JSX.Element {
|
}: AgregatorFilterProps): JSX.Element {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [optionsData, setOptionsData] = useState<ExtendedSelectOption[]>([]);
|
const [optionsData, setOptionsData] = useState<ExtendedSelectOption[]>([]);
|
||||||
@ -73,6 +74,7 @@ export const AggregatorFilter = memo(function AggregatorFilter({
|
|||||||
searchText: debouncedValue,
|
searchText: debouncedValue,
|
||||||
aggregateOperator: queryAggregation.timeAggregation,
|
aggregateOperator: queryAggregation.timeAggregation,
|
||||||
dataSource: query.dataSource,
|
dataSource: query.dataSource,
|
||||||
|
source: signalSource || '',
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
enabled:
|
enabled:
|
||||||
@ -152,10 +154,17 @@ export const AggregatorFilter = memo(function AggregatorFilter({
|
|||||||
setSearchText(text);
|
setSearchText(text);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const placeholder: string =
|
const getPlaceholder = useCallback(() => {
|
||||||
query.dataSource === DataSource.METRICS
|
if (signalSource === 'meter') {
|
||||||
? `Search metric name`
|
return 'Meter name';
|
||||||
: 'Aggregate attribute';
|
}
|
||||||
|
|
||||||
|
if (query.dataSource === DataSource.METRICS) {
|
||||||
|
return 'Metric name';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Aggregate attribute';
|
||||||
|
}, [signalSource, query.dataSource]);
|
||||||
|
|
||||||
const getAttributesData = useCallback(
|
const getAttributesData = useCallback(
|
||||||
(): BaseAutocompleteData[] =>
|
(): BaseAutocompleteData[] =>
|
||||||
@ -289,7 +298,7 @@ export const AggregatorFilter = memo(function AggregatorFilter({
|
|||||||
return (
|
return (
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
getPopupContainer={popupContainer}
|
getPopupContainer={popupContainer}
|
||||||
placeholder={placeholder}
|
placeholder={getPlaceholder()}
|
||||||
style={selectStyle}
|
style={selectStyle}
|
||||||
filterOption={false}
|
filterOption={false}
|
||||||
onSearch={handleSearchText}
|
onSearch={handleSearchText}
|
||||||
|
|||||||
@ -30,8 +30,10 @@ function BuilderUnitsFilter({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space>
|
<Space className="builder-units-filter">
|
||||||
<DefaultLabel>Y-axis unit</DefaultLabel>
|
<DefaultLabel className="builder-units-filter-label">
|
||||||
|
Y-axis unit
|
||||||
|
</DefaultLabel>
|
||||||
<Select
|
<Select
|
||||||
getPopupContainer={popupContainer}
|
getPopupContainer={popupContainer}
|
||||||
style={selectStyles}
|
style={selectStyles}
|
||||||
|
|||||||
@ -5,4 +5,5 @@ export type GroupByFilterProps = {
|
|||||||
query: IBuilderQuery;
|
query: IBuilderQuery;
|
||||||
onChange: (values: BaseAutocompleteData[]) => void;
|
onChange: (values: BaseAutocompleteData[]) => void;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
signalSource?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,9 +10,17 @@ import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAut
|
|||||||
// ** Helpers
|
// ** Helpers
|
||||||
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
||||||
import { isEqual, uniqWith } from 'lodash-es';
|
import { isEqual, uniqWith } from 'lodash-es';
|
||||||
import { memo, ReactNode, useCallback, useEffect, useState } from 'react';
|
import {
|
||||||
|
memo,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { useQueryClient } from 'react-query';
|
import { useQueryClient } from 'react-query';
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { SelectOption } from 'types/common/select';
|
import { SelectOption } from 'types/common/select';
|
||||||
import { popupContainer } from 'utils/selectPopupContainer';
|
import { popupContainer } from 'utils/selectPopupContainer';
|
||||||
|
|
||||||
@ -25,6 +33,7 @@ export const GroupByFilter = memo(function GroupByFilter({
|
|||||||
query,
|
query,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
|
signalSource,
|
||||||
}: GroupByFilterProps): JSX.Element {
|
}: GroupByFilterProps): JSX.Element {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
@ -38,10 +47,17 @@ export const GroupByFilter = memo(function GroupByFilter({
|
|||||||
|
|
||||||
const debouncedValue = useDebounce(searchText, DEBOUNCE_DELAY);
|
const debouncedValue = useDebounce(searchText, DEBOUNCE_DELAY);
|
||||||
|
|
||||||
|
const dataSource = useMemo(() => {
|
||||||
|
if (signalSource === 'meter') {
|
||||||
|
return 'meter' as DataSource;
|
||||||
|
}
|
||||||
|
return query.dataSource;
|
||||||
|
}, [signalSource, query.dataSource]);
|
||||||
|
|
||||||
const { isFetching } = useGetAggregateKeys(
|
const { isFetching } = useGetAggregateKeys(
|
||||||
{
|
{
|
||||||
aggregateAttribute: query.aggregateAttribute?.key || '',
|
aggregateAttribute: query.aggregateAttribute?.key || '',
|
||||||
dataSource: query.dataSource,
|
dataSource,
|
||||||
aggregateOperator: query.aggregateOperator || '',
|
aggregateOperator: query.aggregateOperator || '',
|
||||||
searchText: debouncedValue,
|
searchText: debouncedValue,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
Book,
|
Book,
|
||||||
Boxes,
|
Boxes,
|
||||||
BugIcon,
|
BugIcon,
|
||||||
|
ChartArea,
|
||||||
Cloudy,
|
Cloudy,
|
||||||
DraftingCompass,
|
DraftingCompass,
|
||||||
FileKey2,
|
FileKey2,
|
||||||
@ -113,7 +114,7 @@ const menuItems: SidebarItem[] = [
|
|||||||
key: ROUTES.METRICS_EXPLORER,
|
key: ROUTES.METRICS_EXPLORER,
|
||||||
label: 'Metrics',
|
label: 'Metrics',
|
||||||
icon: <BarChart2 size={16} />,
|
icon: <BarChart2 size={16} />,
|
||||||
isNew: true,
|
isNew: false,
|
||||||
itemKey: 'metrics',
|
itemKey: 'metrics',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -230,7 +231,7 @@ export const defaultMoreMenuItems: SidebarItem[] = [
|
|||||||
key: ROUTES.METRICS_EXPLORER,
|
key: ROUTES.METRICS_EXPLORER,
|
||||||
label: 'Metrics',
|
label: 'Metrics',
|
||||||
icon: <BarChart2 size={16} />,
|
icon: <BarChart2 size={16} />,
|
||||||
isNew: true,
|
isNew: false,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
itemKey: 'metrics',
|
itemKey: 'metrics',
|
||||||
},
|
},
|
||||||
@ -264,6 +265,15 @@ export const defaultMoreMenuItems: SidebarItem[] = [
|
|||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
itemKey: 'external-apis',
|
itemKey: 'external-apis',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: ROUTES.METER_EXPLORER,
|
||||||
|
label: 'Meter Explorer',
|
||||||
|
icon: <ChartArea size={16} />,
|
||||||
|
isNew: false,
|
||||||
|
isEnabled: false,
|
||||||
|
isBeta: true,
|
||||||
|
itemKey: 'meter-explorer',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: ROUTES.MESSAGING_QUEUES_OVERVIEW,
|
key: ROUTES.MESSAGING_QUEUES_OVERVIEW,
|
||||||
label: 'Messaging Queues',
|
label: 'Messaging Queues',
|
||||||
|
|||||||
@ -205,6 +205,7 @@ function TimeSeriesView({
|
|||||||
return (
|
return (
|
||||||
<div className="time-series-view">
|
<div className="time-series-view">
|
||||||
{isError && error && <ErrorInPlace error={error as APIError} />}
|
{isError && error && <ErrorInPlace error={error as APIError} />}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="graph-container"
|
className="graph-container"
|
||||||
style={{ height: '100%', width: '100%' }}
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
|||||||
@ -47,7 +47,7 @@ function TimeSeriesViewContainer({
|
|||||||
return isValid.every(Boolean);
|
return isValid.every(Boolean);
|
||||||
}, [currentQuery]);
|
}, [currentQuery]);
|
||||||
|
|
||||||
const { data, isLoading, isError, error } = useGetQueryRange(
|
const { data, isLoading, isFetching, isError, error } = useGetQueryRange(
|
||||||
{
|
{
|
||||||
query: stagedQuery || initialQueriesMap[dataSource],
|
query: stagedQuery || initialQueriesMap[dataSource],
|
||||||
graphType: panelType || PANEL_TYPES.TIME_SERIES,
|
graphType: panelType || PANEL_TYPES.TIME_SERIES,
|
||||||
@ -88,7 +88,7 @@ function TimeSeriesViewContainer({
|
|||||||
isFilterApplied={isFilterApplied}
|
isFilterApplied={isFilterApplied}
|
||||||
isError={isError}
|
isError={isError}
|
||||||
error={error as APIError}
|
error={error as APIError}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading || isFetching}
|
||||||
data={responseData}
|
data={responseData}
|
||||||
yAxisUnit={isValidToConvertToMs ? 'ms' : 'short'}
|
yAxisUnit={isValidToConvertToMs ? 'ms' : 'short'}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
|
|||||||
@ -233,6 +233,9 @@ export const routesToSkip = [
|
|||||||
ROUTES.ALL_ERROR,
|
ROUTES.ALL_ERROR,
|
||||||
ROUTES.UN_AUTHORIZED,
|
ROUTES.UN_AUTHORIZED,
|
||||||
ROUTES.NOT_FOUND,
|
ROUTES.NOT_FOUND,
|
||||||
|
ROUTES.METER_EXPLORER,
|
||||||
|
ROUTES.METER_EXPLORER_BASE,
|
||||||
|
ROUTES.METER_EXPLORER_VIEWS,
|
||||||
ROUTES.SOMETHING_WENT_WRONG,
|
ROUTES.SOMETHING_WENT_WRONG,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,34 @@
|
|||||||
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
|
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
|
||||||
import { AxiosError, AxiosResponse } from 'axios';
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
import { useQuery, UseQueryResult } from 'react-query';
|
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
|
||||||
|
import { ErrorResponse } from 'react-router-dom-v5-compat';
|
||||||
|
import { SuccessResponse } from 'types/api';
|
||||||
import { QueryKeyValueSuggestionsResponseProps } from 'types/api/querySuggestions/types';
|
import { QueryKeyValueSuggestionsResponseProps } from 'types/api/querySuggestions/types';
|
||||||
|
|
||||||
export const useGetQueryKeyValueSuggestions = ({
|
export const useGetQueryKeyValueSuggestions = ({
|
||||||
key,
|
key,
|
||||||
signal,
|
signal,
|
||||||
searchText,
|
searchText,
|
||||||
|
signalSource,
|
||||||
}: {
|
}: {
|
||||||
key: string;
|
key: string;
|
||||||
signal: 'traces' | 'logs' | 'metrics';
|
signal: 'traces' | 'logs' | 'metrics';
|
||||||
searchText?: string;
|
searchText?: string;
|
||||||
|
signalSource?: 'meter' | '';
|
||||||
|
options?: UseQueryOptions<
|
||||||
|
SuccessResponse<QueryKeyValueSuggestionsResponseProps> | ErrorResponse
|
||||||
|
>;
|
||||||
}): UseQueryResult<
|
}): UseQueryResult<
|
||||||
AxiosResponse<QueryKeyValueSuggestionsResponseProps>,
|
AxiosResponse<QueryKeyValueSuggestionsResponseProps>,
|
||||||
AxiosError
|
AxiosError
|
||||||
> =>
|
> =>
|
||||||
useQuery<AxiosResponse<QueryKeyValueSuggestionsResponseProps>, AxiosError>({
|
useQuery<AxiosResponse<QueryKeyValueSuggestionsResponseProps>, AxiosError>({
|
||||||
queryKey: ['queryKeyValueSuggestions', key, signal, searchText],
|
queryKey: ['queryKeyValueSuggestions', key, signal, searchText, signalSource],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
getValueSuggestions({ signal, key, searchText: searchText || '' }),
|
getValueSuggestions({
|
||||||
|
signal,
|
||||||
|
key,
|
||||||
|
searchText: searchText || '',
|
||||||
|
signalSource: signalSource as 'meter' | '',
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,9 +5,9 @@ import { AllViewsProps } from 'types/api/saveViews/types';
|
|||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
export const useGetAllViews = (
|
export const useGetAllViews = (
|
||||||
sourcepage: DataSource,
|
sourcepage: DataSource | 'meter',
|
||||||
): UseQueryResult<AxiosResponse<AllViewsProps>, AxiosError> =>
|
): UseQueryResult<AxiosResponse<AllViewsProps>, AxiosError> =>
|
||||||
useQuery<AxiosResponse<AllViewsProps>, AxiosError>({
|
useQuery<AxiosResponse<AllViewsProps>, AxiosError>({
|
||||||
queryKey: [{ sourcepage }],
|
queryKey: [{ sourcepage }],
|
||||||
queryFn: () => getAllViews(sourcepage),
|
queryFn: () => getAllViews(sourcepage as DataSource),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -490,6 +490,7 @@ export const defaultOutput = {
|
|||||||
pageSize: 0,
|
pageSize: 0,
|
||||||
queryName: 'A',
|
queryName: 'A',
|
||||||
reduceTo: 'avg',
|
reduceTo: 'avg',
|
||||||
|
source: '',
|
||||||
spaceAggregation: 'sum',
|
spaceAggregation: 'sum',
|
||||||
stepInterval: 240,
|
stepInterval: 240,
|
||||||
timeAggregation: 'rate',
|
timeAggregation: 'rate',
|
||||||
|
|||||||
16
frontend/src/pages/MeterExplorer/MeterExplorer.styles.scss
Normal file
16
frontend/src/pages/MeterExplorer/MeterExplorer.styles.scss
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.meter-explorer-page {
|
||||||
|
.ant-tabs-nav {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-tab {
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin: 0 8px 0 0 !important;
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
frontend/src/pages/MeterExplorer/MeterExplorerPage.tsx
Normal file
22
frontend/src/pages/MeterExplorer/MeterExplorerPage.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import './MeterExplorer.styles.scss';
|
||||||
|
|
||||||
|
import RouteTab from 'components/RouteTab';
|
||||||
|
import { TabRoutes } from 'components/RouteTab/types';
|
||||||
|
import history from 'lib/history';
|
||||||
|
import { useLocation } from 'react-use';
|
||||||
|
|
||||||
|
import { Explorer, Views } from './constants';
|
||||||
|
|
||||||
|
function MeterExplorerPage(): JSX.Element {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const routes: TabRoutes[] = [Explorer, Views];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="meter-explorer-page">
|
||||||
|
<RouteTab routes={routes} activeKey={pathname} history={history} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MeterExplorerPage;
|
||||||
32
frontend/src/pages/MeterExplorer/constants.tsx
Normal file
32
frontend/src/pages/MeterExplorer/constants.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { TabRoutes } from 'components/RouteTab/types';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
import ExplorerPage from 'container/MeterExplorer/Explorer';
|
||||||
|
import { Compass, TowerControl } from 'lucide-react';
|
||||||
|
import SaveView from 'pages/SaveView';
|
||||||
|
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
|
||||||
|
|
||||||
|
export const Explorer: TabRoutes = {
|
||||||
|
Component: (): JSX.Element => (
|
||||||
|
<PreferenceContextProvider>
|
||||||
|
<ExplorerPage />
|
||||||
|
</PreferenceContextProvider>
|
||||||
|
),
|
||||||
|
name: (
|
||||||
|
<div className="tab-item">
|
||||||
|
<Compass size={16} /> Explorer
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
route: ROUTES.METER_EXPLORER,
|
||||||
|
key: ROUTES.METER_EXPLORER,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Views: TabRoutes = {
|
||||||
|
Component: SaveView,
|
||||||
|
name: (
|
||||||
|
<div className="tab-item">
|
||||||
|
<TowerControl size={16} /> Views
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
route: ROUTES.METER_EXPLORER_VIEWS,
|
||||||
|
key: ROUTES.METER_EXPLORER_VIEWS,
|
||||||
|
};
|
||||||
3
frontend/src/pages/MeterExplorer/index.tsx
Normal file
3
frontend/src/pages/MeterExplorer/index.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import MeterExplorerPage from './MeterExplorerPage';
|
||||||
|
|
||||||
|
export default MeterExplorerPage;
|
||||||
@ -6,6 +6,7 @@ export const SOURCEPAGE_VS_ROUTES: {
|
|||||||
logs: ROUTES.LOGS_EXPLORER,
|
logs: ROUTES.LOGS_EXPLORER,
|
||||||
traces: ROUTES.TRACES_EXPLORER,
|
traces: ROUTES.TRACES_EXPLORER,
|
||||||
metrics: ROUTES.METRICS_EXPLORER_EXPLORER,
|
metrics: ROUTES.METRICS_EXPLORER_EXPLORER,
|
||||||
|
meter: ROUTES.METER_EXPLORER,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ROUTES_VS_SOURCEPAGE: {
|
export const ROUTES_VS_SOURCEPAGE: {
|
||||||
@ -14,4 +15,5 @@ export const ROUTES_VS_SOURCEPAGE: {
|
|||||||
[ROUTES.LOGS_SAVE_VIEWS]: 'logs',
|
[ROUTES.LOGS_SAVE_VIEWS]: 'logs',
|
||||||
[ROUTES.TRACES_SAVE_VIEWS]: 'traces',
|
[ROUTES.TRACES_SAVE_VIEWS]: 'traces',
|
||||||
[ROUTES.METRICS_EXPLORER_VIEWS]: 'metrics',
|
[ROUTES.METRICS_EXPLORER_VIEWS]: 'metrics',
|
||||||
|
[ROUTES.METER_EXPLORER_VIEWS]: 'meter',
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@ -17,6 +17,10 @@ import {
|
|||||||
} from 'components/ExplorerCard/utils';
|
} from 'components/ExplorerCard/utils';
|
||||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||||
import { getRandomColor } from 'container/ExplorerOptions/utils';
|
import { getRandomColor } from 'container/ExplorerOptions/utils';
|
||||||
|
import {
|
||||||
|
MeterExplorerEventKeys,
|
||||||
|
MeterExplorerEvents,
|
||||||
|
} from 'container/MeterExplorer/events';
|
||||||
import {
|
import {
|
||||||
MetricsExplorerEventKeys,
|
MetricsExplorerEventKeys,
|
||||||
MetricsExplorerEvents,
|
MetricsExplorerEvents,
|
||||||
@ -163,6 +167,10 @@ function SaveView(): JSX.Element {
|
|||||||
logEvent(MetricsExplorerEvents.TabChanged, {
|
logEvent(MetricsExplorerEvents.TabChanged, {
|
||||||
[MetricsExplorerEventKeys.Tab]: 'views',
|
[MetricsExplorerEventKeys.Tab]: 'views',
|
||||||
});
|
});
|
||||||
|
} else if (sourcepage === 'meter') {
|
||||||
|
logEvent(MeterExplorerEvents.TabChanged, {
|
||||||
|
[MeterExplorerEventKeys.Tab]: 'views',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
logEventCalledRef.current = true;
|
logEventCalledRef.current = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -241,12 +241,26 @@ export function QueryBuilderProvider({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const updateAllQueriesOperators = useCallback(
|
const updateAllQueriesOperators = useCallback(
|
||||||
(query: Query, panelType: PANEL_TYPES, dataSource: DataSource): Query => {
|
(
|
||||||
|
query: Query,
|
||||||
|
panelType: PANEL_TYPES,
|
||||||
|
dataSource: DataSource,
|
||||||
|
signalSource?: 'meter' | '',
|
||||||
|
): Query => {
|
||||||
const queryData = query.builder.queryData?.map((item) =>
|
const queryData = query.builder.queryData?.map((item) =>
|
||||||
getElementWithActualOperator(item, dataSource, panelType),
|
getElementWithActualOperator(item, dataSource, panelType),
|
||||||
);
|
);
|
||||||
|
|
||||||
return { ...query, builder: { ...query.builder, queryData } };
|
return {
|
||||||
|
...query,
|
||||||
|
builder: {
|
||||||
|
...query.builder,
|
||||||
|
queryData: queryData.map((item) => ({
|
||||||
|
...item,
|
||||||
|
source: signalSource,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
[getElementWithActualOperator],
|
[getElementWithActualOperator],
|
||||||
@ -854,6 +868,7 @@ export function QueryBuilderProvider({
|
|||||||
const handleRunQuery = useCallback(
|
const handleRunQuery = useCallback(
|
||||||
(shallUpdateStepInterval?: boolean, newQBQuery?: boolean) => {
|
(shallUpdateStepInterval?: boolean, newQBQuery?: boolean) => {
|
||||||
let currentQueryData = currentQuery;
|
let currentQueryData = currentQuery;
|
||||||
|
|
||||||
if (newQBQuery) {
|
if (newQBQuery) {
|
||||||
currentQueryData = {
|
currentQueryData = {
|
||||||
...currentQueryData,
|
...currentQueryData,
|
||||||
|
|||||||
@ -4,4 +4,5 @@ export interface IGetAggregateAttributePayload {
|
|||||||
aggregateOperator: string;
|
aggregateOperator: string;
|
||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
searchText: string;
|
searchText: string;
|
||||||
|
source?: 'meter' | '';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,6 +87,7 @@ export type IBuilderQuery = {
|
|||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
selectColumns?: BaseAutocompleteData[] | TelemetryFieldKey[];
|
selectColumns?: BaseAutocompleteData[] | TelemetryFieldKey[];
|
||||||
|
source?: 'meter' | '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IClickHouseQuery {
|
export interface IClickHouseQuery {
|
||||||
|
|||||||
@ -28,6 +28,7 @@ export interface QueryKeyRequestProps {
|
|||||||
fieldContext?: 'resource' | 'scope' | 'attribute' | 'span';
|
fieldContext?: 'resource' | 'scope' | 'attribute' | 'span';
|
||||||
fieldDataType?: QUERY_BUILDER_KEY_TYPES;
|
fieldDataType?: QUERY_BUILDER_KEY_TYPES;
|
||||||
metricName?: string;
|
metricName?: string;
|
||||||
|
signalSource?: 'meter' | '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryKeyValueSuggestionsProps {
|
export interface QueryKeyValueSuggestionsProps {
|
||||||
@ -44,4 +45,7 @@ export interface QueryKeyValueRequestProps {
|
|||||||
signal: 'traces' | 'logs' | 'metrics';
|
signal: 'traces' | 'logs' | 'metrics';
|
||||||
key: string;
|
key: string;
|
||||||
searchText: string;
|
searchText: string;
|
||||||
|
signalSource?: 'meter' | '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SignalType = 'traces' | 'logs' | 'metrics';
|
||||||
|
|||||||
@ -239,10 +239,17 @@ export interface MetricBuilderQuery extends BaseBuilderQuery {
|
|||||||
aggregations?: MetricAggregation[];
|
aggregations?: MetricAggregation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MeterBuilderQuery extends BaseBuilderQuery {
|
||||||
|
signal: 'metrics';
|
||||||
|
source: 'meter';
|
||||||
|
aggregations?: MetricAggregation[];
|
||||||
|
}
|
||||||
|
|
||||||
export type BuilderQuery =
|
export type BuilderQuery =
|
||||||
| TraceBuilderQuery
|
| TraceBuilderQuery
|
||||||
| LogBuilderQuery
|
| LogBuilderQuery
|
||||||
| MetricBuilderQuery;
|
| MetricBuilderQuery
|
||||||
|
| MeterBuilderQuery;
|
||||||
|
|
||||||
export interface QueryBuilderFormula {
|
export interface QueryBuilderFormula {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@ -105,6 +105,42 @@ export enum MetricAggregateOperator {
|
|||||||
LATEST = 'latest',
|
LATEST = 'latest',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum MeterAggregateOperator {
|
||||||
|
EMPTY = '', // used as time aggregator for histograms
|
||||||
|
NOOP = 'noop',
|
||||||
|
COUNT = 'count',
|
||||||
|
COUNT_DISTINCT = 'count_distinct',
|
||||||
|
SUM = 'sum',
|
||||||
|
AVG = 'avg',
|
||||||
|
MAX = 'max',
|
||||||
|
MIN = 'min',
|
||||||
|
P05 = 'p05',
|
||||||
|
P10 = 'p10',
|
||||||
|
P20 = 'p20',
|
||||||
|
P25 = 'p25',
|
||||||
|
P50 = 'p50',
|
||||||
|
P75 = 'p75',
|
||||||
|
P90 = 'p90',
|
||||||
|
P95 = 'p95',
|
||||||
|
P99 = 'p99',
|
||||||
|
RATE = 'rate',
|
||||||
|
SUM_RATE = 'sum_rate',
|
||||||
|
AVG_RATE = 'avg_rate',
|
||||||
|
MAX_RATE = 'max_rate',
|
||||||
|
MIN_RATE = 'min_rate',
|
||||||
|
RATE_SUM = 'rate_sum',
|
||||||
|
RATE_AVG = 'rate_avg',
|
||||||
|
RATE_MIN = 'rate_min',
|
||||||
|
RATE_MAX = 'rate_max',
|
||||||
|
HIST_QUANTILE_50 = 'hist_quantile_50',
|
||||||
|
HIST_QUANTILE_75 = 'hist_quantile_75',
|
||||||
|
HIST_QUANTILE_90 = 'hist_quantile_90',
|
||||||
|
HIST_QUANTILE_95 = 'hist_quantile_95',
|
||||||
|
HIST_QUANTILE_99 = 'hist_quantile_99',
|
||||||
|
INCREASE = 'increase',
|
||||||
|
LATEST = 'latest',
|
||||||
|
}
|
||||||
|
|
||||||
export enum TracesAggregatorOperator {
|
export enum TracesAggregatorOperator {
|
||||||
NOOP = 'noop',
|
NOOP = 'noop',
|
||||||
COUNT = 'count',
|
COUNT = 'count',
|
||||||
@ -237,6 +273,7 @@ export type QueryBuilderContextType = {
|
|||||||
queryData: Query,
|
queryData: Query,
|
||||||
panelType: PANEL_TYPES,
|
panelType: PANEL_TYPES,
|
||||||
dataSource: DataSource,
|
dataSource: DataSource,
|
||||||
|
signalSource?: 'meter' | '',
|
||||||
) => Query;
|
) => Query;
|
||||||
updateQueriesData: <T extends keyof QueryBuilderData>(
|
updateQueriesData: <T extends keyof QueryBuilderData>(
|
||||||
query: Query,
|
query: Query,
|
||||||
|
|||||||
@ -123,4 +123,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
|||||||
INFRASTRUCTURE_MONITORING_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
INFRASTRUCTURE_MONITORING_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
API_MONITORING_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
API_MONITORING_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
MESSAGING_QUEUES_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
MESSAGING_QUEUES_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
|
METER_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
|
METER_EXPLORER_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
|
METER_EXPLORER_VIEWS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user