mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 23:47:12 +00:00
Merge branch 'demo/trace-operators' of github.com:SigNoz/signoz into demo/trace_operators_backend
This commit is contained in:
commit
a2ab97a347
@ -257,6 +257,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
|||||||
s.config.APIServer.Timeout.Max,
|
s.config.APIServer.Timeout.Max,
|
||||||
).Wrap)
|
).Wrap)
|
||||||
r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap)
|
r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap)
|
||||||
|
r.Use(middleware.NewComment().Wrap)
|
||||||
|
|
||||||
apiHandler.RegisterRoutes(r, am)
|
apiHandler.RegisterRoutes(r, am)
|
||||||
apiHandler.RegisterLogsRoutes(r, am)
|
apiHandler.RegisterLogsRoutes(r, am)
|
||||||
|
|||||||
@ -5,7 +5,10 @@ import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
|||||||
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
|
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
|
||||||
import { isEmpty } from 'lodash-es';
|
import { isEmpty } from 'lodash-es';
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import {
|
||||||
|
IBuilderQuery,
|
||||||
|
IBuilderTraceOperator,
|
||||||
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import {
|
import {
|
||||||
BaseBuilderQuery,
|
BaseBuilderQuery,
|
||||||
FieldContext,
|
FieldContext,
|
||||||
@ -276,6 +279,103 @@ export function convertBuilderQueriesToV5(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createTraceOperatorBaseSpec(
|
||||||
|
queryData: IBuilderTraceOperator,
|
||||||
|
requestType: RequestType,
|
||||||
|
panelType?: PANEL_TYPES,
|
||||||
|
): BaseBuilderQuery {
|
||||||
|
const nonEmptySelectColumns = (queryData.selectColumns as (
|
||||||
|
| BaseAutocompleteData
|
||||||
|
| TelemetryFieldKey
|
||||||
|
)[])?.filter((c) => ('key' in c ? c?.key : c?.name));
|
||||||
|
|
||||||
|
return {
|
||||||
|
stepInterval: queryData?.stepInterval || undefined,
|
||||||
|
groupBy:
|
||||||
|
queryData.groupBy?.length > 0
|
||||||
|
? queryData.groupBy.map(
|
||||||
|
(item: any): GroupByKey => ({
|
||||||
|
name: item.key,
|
||||||
|
fieldDataType: item?.dataType,
|
||||||
|
fieldContext: item?.type,
|
||||||
|
description: item?.description,
|
||||||
|
unit: item?.unit,
|
||||||
|
signal: item?.signal,
|
||||||
|
materialized: item?.materialized,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
limit:
|
||||||
|
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
|
||||||
|
? queryData.limit || queryData.pageSize || undefined
|
||||||
|
: queryData.limit || undefined,
|
||||||
|
offset:
|
||||||
|
requestType === 'raw' || requestType === 'trace'
|
||||||
|
? queryData.offset
|
||||||
|
: undefined,
|
||||||
|
order:
|
||||||
|
queryData.orderBy?.length > 0
|
||||||
|
? queryData.orderBy.map(
|
||||||
|
(order: any): OrderBy => ({
|
||||||
|
key: {
|
||||||
|
name: order.columnName,
|
||||||
|
},
|
||||||
|
direction: order.order,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
legend: isEmpty(queryData.legend) ? undefined : queryData.legend,
|
||||||
|
having: isEmpty(queryData.having) ? undefined : (queryData?.having as Having),
|
||||||
|
selectFields: isEmpty(nonEmptySelectColumns)
|
||||||
|
? undefined
|
||||||
|
: nonEmptySelectColumns?.map(
|
||||||
|
(column: any): TelemetryFieldKey => ({
|
||||||
|
name: column.name ?? column.key,
|
||||||
|
fieldDataType:
|
||||||
|
column?.fieldDataType ?? (column?.dataType as FieldDataType),
|
||||||
|
fieldContext: column?.fieldContext ?? (column?.type as FieldContext),
|
||||||
|
signal: column?.signal ?? undefined,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertTraceOperatorToV5(
|
||||||
|
traceOperator: Record<string, IBuilderTraceOperator>,
|
||||||
|
requestType: RequestType,
|
||||||
|
panelType?: PANEL_TYPES,
|
||||||
|
): QueryEnvelope[] {
|
||||||
|
return Object.entries(traceOperator).map(
|
||||||
|
([queryName, traceOperatorData]): QueryEnvelope => {
|
||||||
|
const baseSpec = createTraceOperatorBaseSpec(
|
||||||
|
traceOperatorData,
|
||||||
|
requestType,
|
||||||
|
panelType,
|
||||||
|
);
|
||||||
|
let spec: QueryEnvelope['spec'];
|
||||||
|
|
||||||
|
// Skip aggregation for raw request type
|
||||||
|
const aggregations =
|
||||||
|
requestType === 'raw'
|
||||||
|
? undefined
|
||||||
|
: createAggregation(traceOperatorData, panelType);
|
||||||
|
|
||||||
|
spec = {
|
||||||
|
name: queryName,
|
||||||
|
returnSpansFrom: traceOperatorData.returnSpansFrom || '',
|
||||||
|
...baseSpec,
|
||||||
|
expression: traceOperatorData.expression || '',
|
||||||
|
aggregations: aggregations as TraceAggregation[],
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'builder_trace_operator' as QueryType,
|
||||||
|
spec,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts PromQL queries to V5 format
|
* Converts PromQL queries to V5 format
|
||||||
*/
|
*/
|
||||||
@ -357,14 +457,27 @@ export const prepareQueryRangePayloadV5 = ({
|
|||||||
|
|
||||||
switch (query.queryType) {
|
switch (query.queryType) {
|
||||||
case EQueryType.QUERY_BUILDER: {
|
case EQueryType.QUERY_BUILDER: {
|
||||||
const { queryData: data, queryFormulas } = query.builder;
|
const { queryData: data, queryFormulas, queryTraceOperator } = query.builder;
|
||||||
const currentQueryData = mapQueryDataToApi(data, 'queryName', tableParams);
|
const currentQueryData = mapQueryDataToApi(data, 'queryName', tableParams);
|
||||||
const currentFormulas = mapQueryDataToApi(queryFormulas, 'queryName');
|
const currentFormulas = mapQueryDataToApi(queryFormulas, 'queryName');
|
||||||
|
|
||||||
|
const filteredTraceOperator =
|
||||||
|
queryTraceOperator && queryTraceOperator.length > 0
|
||||||
|
? queryTraceOperator.filter((traceOperator) =>
|
||||||
|
Boolean(traceOperator.expression.trim()),
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const currentTraceOperator = mapQueryDataToApi(
|
||||||
|
filteredTraceOperator,
|
||||||
|
'queryName',
|
||||||
|
);
|
||||||
|
|
||||||
// Combine legend maps
|
// Combine legend maps
|
||||||
legendMap = {
|
legendMap = {
|
||||||
...currentQueryData.newLegendMap,
|
...currentQueryData.newLegendMap,
|
||||||
...currentFormulas.newLegendMap,
|
...currentFormulas.newLegendMap,
|
||||||
|
...currentTraceOperator.newLegendMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convert builder queries
|
// Convert builder queries
|
||||||
@ -397,8 +510,36 @@ export const prepareQueryRangePayloadV5 = ({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const traceOperatorQueries = convertTraceOperatorToV5(
|
||||||
|
currentTraceOperator.data,
|
||||||
|
requestType,
|
||||||
|
graphType,
|
||||||
|
);
|
||||||
|
|
||||||
|
// const traceOperatorQueries = Object.entries(currentTraceOperator.data).map(
|
||||||
|
// ([queryName, traceOperatorData]): QueryEnvelope => ({
|
||||||
|
// type: 'builder_trace_operator' as const,
|
||||||
|
// spec: {
|
||||||
|
// name: queryName,
|
||||||
|
// expression: traceOperatorData.expression || '',
|
||||||
|
// legend: isEmpty(traceOperatorData.legend)
|
||||||
|
// ? undefined
|
||||||
|
// : traceOperatorData.legend,
|
||||||
|
// limit: 10,
|
||||||
|
// order: traceOperatorData.orderBy?.map(
|
||||||
|
// // eslint-disable-next-line sonarjs/no-identical-functions
|
||||||
|
// (order: any): OrderBy => ({
|
||||||
|
// key: {
|
||||||
|
// name: order.columnName,
|
||||||
|
// },
|
||||||
|
// direction: order.order,
|
||||||
|
// }),
|
||||||
|
// ),
|
||||||
|
// },
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
// Combine both types
|
// Combine both types
|
||||||
queries = [...builderQueries, ...formulaQueries];
|
queries = [...builderQueries, ...formulaQueries, ...traceOperatorQueries];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EQueryType.PROM: {
|
case EQueryType.PROM: {
|
||||||
|
|||||||
@ -22,6 +22,10 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
.qb-trace-view-selector-container {
|
||||||
|
padding: 12px 8px 8px 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.qb-content-section {
|
.qb-content-section {
|
||||||
@ -179,7 +183,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
margin-left: 32px;
|
margin-left: 26px;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
|
|
||||||
@ -195,8 +199,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.formula-container {
|
.formula-container {
|
||||||
margin-left: 82px;
|
padding: 8px;
|
||||||
padding: 4px 0px;
|
margin-left: 74px;
|
||||||
|
|
||||||
.ant-col {
|
.ant-col {
|
||||||
&::before {
|
&::before {
|
||||||
@ -331,6 +335,12 @@
|
|||||||
);
|
);
|
||||||
left: 15px;
|
left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.has-trace-operator {
|
||||||
|
&::before {
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.formula-name {
|
.formula-name {
|
||||||
@ -347,7 +357,7 @@
|
|||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
height: 65px;
|
height: 128px;
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@ -5,11 +5,13 @@ import { Formula } from 'container/QueryBuilder/components/Formula';
|
|||||||
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { memo, useEffect, useMemo, useRef } from 'react';
|
import { memo, useEffect, useMemo, useRef } from 'react';
|
||||||
|
import { IBuilderTraceOperator } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { QueryBuilderV2Provider } from './QueryBuilderV2Context';
|
import { QueryBuilderV2Provider } from './QueryBuilderV2Context';
|
||||||
import QueryFooter from './QueryV2/QueryFooter/QueryFooter';
|
import QueryFooter from './QueryV2/QueryFooter/QueryFooter';
|
||||||
import { QueryV2 } from './QueryV2/QueryV2';
|
import { QueryV2 } from './QueryV2/QueryV2';
|
||||||
|
import TraceOperator from './QueryV2/TraceOperator/TraceOperator';
|
||||||
|
|
||||||
export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
||||||
config,
|
config,
|
||||||
@ -18,6 +20,7 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
|||||||
queryComponents,
|
queryComponents,
|
||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
showOnlyWhereClause = false,
|
showOnlyWhereClause = false,
|
||||||
|
showTraceOperator = false,
|
||||||
version,
|
version,
|
||||||
}: QueryBuilderProps): JSX.Element {
|
}: QueryBuilderProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
@ -25,6 +28,7 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
|||||||
addNewBuilderQuery,
|
addNewBuilderQuery,
|
||||||
addNewFormula,
|
addNewFormula,
|
||||||
handleSetConfig,
|
handleSetConfig,
|
||||||
|
addTraceOperator,
|
||||||
panelType,
|
panelType,
|
||||||
initialDataSource,
|
initialDataSource,
|
||||||
} = useQueryBuilder();
|
} = useQueryBuilder();
|
||||||
@ -54,6 +58,14 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
|||||||
newPanelType,
|
newPanelType,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const isMultiQueryAllowed = useMemo(
|
||||||
|
() =>
|
||||||
|
!showOnlyWhereClause ||
|
||||||
|
!isListViewPanel ||
|
||||||
|
(currentDataSource === DataSource.TRACES && showTraceOperator),
|
||||||
|
[showOnlyWhereClause, currentDataSource, showTraceOperator, isListViewPanel],
|
||||||
|
);
|
||||||
|
|
||||||
const listViewLogFilterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => {
|
const listViewLogFilterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => {
|
||||||
const config: QueryBuilderProps['filterConfigs'] = {
|
const config: QueryBuilderProps['filterConfigs'] = {
|
||||||
stepInterval: { isHidden: true, isDisabled: true },
|
stepInterval: { isHidden: true, isDisabled: true },
|
||||||
@ -97,11 +109,45 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
|||||||
listViewTracesFilterConfigs,
|
listViewTracesFilterConfigs,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const traceOperator = useMemo((): IBuilderTraceOperator | undefined => {
|
||||||
|
if (
|
||||||
|
currentQuery.builder.queryTraceOperator &&
|
||||||
|
currentQuery.builder.queryTraceOperator.length > 0
|
||||||
|
) {
|
||||||
|
return currentQuery.builder.queryTraceOperator[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}, [currentQuery.builder.queryTraceOperator]);
|
||||||
|
|
||||||
|
const shouldShowTraceOperator = useMemo(
|
||||||
|
() =>
|
||||||
|
showTraceOperator &&
|
||||||
|
currentDataSource === DataSource.TRACES &&
|
||||||
|
Boolean(traceOperator),
|
||||||
|
[currentDataSource, showTraceOperator, traceOperator],
|
||||||
|
);
|
||||||
|
|
||||||
|
const shouldShowFooter = useMemo(
|
||||||
|
() =>
|
||||||
|
(!showOnlyWhereClause && !isListViewPanel) ||
|
||||||
|
(currentDataSource === DataSource.TRACES && showTraceOperator),
|
||||||
|
[isListViewPanel, showTraceOperator, showOnlyWhereClause, currentDataSource],
|
||||||
|
);
|
||||||
|
|
||||||
|
const showFormula = useMemo(() => {
|
||||||
|
if (currentDataSource === DataSource.TRACES) {
|
||||||
|
return !isListViewPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, [isListViewPanel, currentDataSource]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryBuilderV2Provider>
|
<QueryBuilderV2Provider>
|
||||||
<div className="query-builder-v2">
|
<div className="query-builder-v2">
|
||||||
<div className="qb-content-container">
|
<div className="qb-content-container">
|
||||||
{isListViewPanel && (
|
{!isMultiQueryAllowed ? (
|
||||||
<QueryV2
|
<QueryV2
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
key={currentQuery.builder.queryData[0].queryName}
|
key={currentQuery.builder.queryData[0].queryName}
|
||||||
@ -109,15 +155,15 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
|||||||
query={currentQuery.builder.queryData[0]}
|
query={currentQuery.builder.queryData[0]}
|
||||||
filterConfigs={queryFilterConfigs}
|
filterConfigs={queryFilterConfigs}
|
||||||
queryComponents={queryComponents}
|
queryComponents={queryComponents}
|
||||||
|
isMultiQueryAllowed={isMultiQueryAllowed}
|
||||||
|
showTraceOperator={shouldShowTraceOperator}
|
||||||
version={version}
|
version={version}
|
||||||
isAvailableToDisable={false}
|
isAvailableToDisable={false}
|
||||||
queryVariant={config?.queryVariant || 'dropdown'}
|
queryVariant={config?.queryVariant || 'dropdown'}
|
||||||
showOnlyWhereClause={showOnlyWhereClause}
|
showOnlyWhereClause={showOnlyWhereClause}
|
||||||
isListViewPanel={isListViewPanel}
|
isListViewPanel={isListViewPanel}
|
||||||
/>
|
/>
|
||||||
)}
|
) : (
|
||||||
|
|
||||||
{!isListViewPanel &&
|
|
||||||
currentQuery.builder.queryData.map((query, index) => (
|
currentQuery.builder.queryData.map((query, index) => (
|
||||||
<QueryV2
|
<QueryV2
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
@ -127,13 +173,16 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
|||||||
filterConfigs={queryFilterConfigs}
|
filterConfigs={queryFilterConfigs}
|
||||||
queryComponents={queryComponents}
|
queryComponents={queryComponents}
|
||||||
version={version}
|
version={version}
|
||||||
|
isMultiQueryAllowed={isMultiQueryAllowed}
|
||||||
isAvailableToDisable={false}
|
isAvailableToDisable={false}
|
||||||
|
showTraceOperator={shouldShowTraceOperator}
|
||||||
queryVariant={config?.queryVariant || 'dropdown'}
|
queryVariant={config?.queryVariant || 'dropdown'}
|
||||||
showOnlyWhereClause={showOnlyWhereClause}
|
showOnlyWhereClause={showOnlyWhereClause}
|
||||||
isListViewPanel={isListViewPanel}
|
isListViewPanel={isListViewPanel}
|
||||||
signalSource={config?.signalSource || ''}
|
signalSource={config?.signalSource || ''}
|
||||||
/>
|
/>
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
|
|
||||||
{!showOnlyWhereClause && currentQuery.builder.queryFormulas.length > 0 && (
|
{!showOnlyWhereClause && currentQuery.builder.queryFormulas.length > 0 && (
|
||||||
<div className="qb-formulas-container">
|
<div className="qb-formulas-container">
|
||||||
@ -158,15 +207,25 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!showOnlyWhereClause && !isListViewPanel && (
|
{shouldShowFooter && (
|
||||||
<QueryFooter
|
<QueryFooter
|
||||||
|
showAddFormula={showFormula}
|
||||||
addNewBuilderQuery={addNewBuilderQuery}
|
addNewBuilderQuery={addNewBuilderQuery}
|
||||||
addNewFormula={addNewFormula}
|
addNewFormula={addNewFormula}
|
||||||
|
addTraceOperator={addTraceOperator}
|
||||||
|
showAddTraceOperator={showTraceOperator && !traceOperator}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shouldShowTraceOperator && (
|
||||||
|
<TraceOperator
|
||||||
|
isListViewPanel={isListViewPanel}
|
||||||
|
traceOperator={traceOperator as IBuilderTraceOperator}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!showOnlyWhereClause && !isListViewPanel && (
|
{isMultiQueryAllowed && (
|
||||||
<div className="query-names-section">
|
<div className="query-names-section">
|
||||||
{currentQuery.builder.queryData.map((query) => (
|
{currentQuery.builder.queryData.map((query) => (
|
||||||
<div key={query.queryName} className="query-name">
|
<div key={query.queryName} className="query-name">
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
|
.query-add-ons {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.add-ons-list {
|
.add-ons-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
.add-ons-tabs {
|
.add-ons-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -144,6 +144,8 @@ function QueryAddOns({
|
|||||||
showReduceTo,
|
showReduceTo,
|
||||||
panelType,
|
panelType,
|
||||||
index,
|
index,
|
||||||
|
isForTraceOperator = false,
|
||||||
|
children,
|
||||||
}: {
|
}: {
|
||||||
query: IBuilderQuery;
|
query: IBuilderQuery;
|
||||||
version: string;
|
version: string;
|
||||||
@ -151,6 +153,8 @@ function QueryAddOns({
|
|||||||
showReduceTo: boolean;
|
showReduceTo: boolean;
|
||||||
panelType: PANEL_TYPES | null;
|
panelType: PANEL_TYPES | null;
|
||||||
index: number;
|
index: number;
|
||||||
|
isForTraceOperator?: boolean;
|
||||||
|
children?: React.ReactNode;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const [addOns, setAddOns] = useState<AddOn[]>(ADD_ONS);
|
const [addOns, setAddOns] = useState<AddOn[]>(ADD_ONS);
|
||||||
|
|
||||||
@ -160,6 +164,7 @@ function QueryAddOns({
|
|||||||
index,
|
index,
|
||||||
query,
|
query,
|
||||||
entityVersion: '',
|
entityVersion: '',
|
||||||
|
isForTraceOperator,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { handleSetQueryData } = useQueryBuilder();
|
const { handleSetQueryData } = useQueryBuilder();
|
||||||
@ -486,6 +491,7 @@ function QueryAddOns({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,7 +4,10 @@ import { Tooltip } from 'antd';
|
|||||||
import InputWithLabel from 'components/InputWithLabel/InputWithLabel';
|
import InputWithLabel from 'components/InputWithLabel/InputWithLabel';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import {
|
||||||
|
IBuilderQuery,
|
||||||
|
IBuilderTraceOperator,
|
||||||
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import QueryAggregationSelect from './QueryAggregationSelect';
|
import QueryAggregationSelect from './QueryAggregationSelect';
|
||||||
@ -20,7 +23,7 @@ function QueryAggregationOptions({
|
|||||||
panelType?: string;
|
panelType?: string;
|
||||||
onAggregationIntervalChange: (value: number) => void;
|
onAggregationIntervalChange: (value: number) => void;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
queryData: IBuilderQuery;
|
queryData: IBuilderQuery | IBuilderTraceOperator;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const showAggregationInterval = useMemo(() => {
|
const showAggregationInterval = useMemo(() => {
|
||||||
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
|
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
|
||||||
|
|||||||
@ -4,9 +4,15 @@ import { Plus, Sigma } from 'lucide-react';
|
|||||||
export default function QueryFooter({
|
export default function QueryFooter({
|
||||||
addNewBuilderQuery,
|
addNewBuilderQuery,
|
||||||
addNewFormula,
|
addNewFormula,
|
||||||
|
addTraceOperator,
|
||||||
|
showAddFormula = true,
|
||||||
|
showAddTraceOperator = false,
|
||||||
}: {
|
}: {
|
||||||
addNewBuilderQuery: () => void;
|
addNewBuilderQuery: () => void;
|
||||||
addNewFormula: () => void;
|
addNewFormula: () => void;
|
||||||
|
addTraceOperator?: () => void;
|
||||||
|
showAddTraceOperator: boolean;
|
||||||
|
showAddFormula?: boolean;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="qb-footer">
|
<div className="qb-footer">
|
||||||
@ -22,32 +28,62 @@ export default function QueryFooter({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="qb-add-formula">
|
{showAddFormula && (
|
||||||
<Tooltip
|
<div className="qb-add-formula">
|
||||||
title={
|
<Tooltip
|
||||||
<div style={{ textAlign: 'center' }}>
|
title={
|
||||||
Add New Formula
|
<div style={{ textAlign: 'center' }}>
|
||||||
<Typography.Link
|
Add New Formula
|
||||||
href="https://signoz.io/docs/userguide/query-builder-v5/#multi-query-analysis-advanced-comparisons"
|
<Typography.Link
|
||||||
target="_blank"
|
href="https://signoz.io/docs/userguide/query-builder-v5/#multi-query-analysis-advanced-comparisons"
|
||||||
style={{ textDecoration: 'underline' }}
|
target="_blank"
|
||||||
>
|
style={{ textDecoration: 'underline' }}
|
||||||
{' '}
|
>
|
||||||
<br />
|
{' '}
|
||||||
Learn more
|
<br />
|
||||||
</Typography.Link>
|
Learn more
|
||||||
</div>
|
</Typography.Link>
|
||||||
}
|
</div>
|
||||||
>
|
}
|
||||||
<Button
|
|
||||||
className="add-formula-button periscope-btn secondary"
|
|
||||||
icon={<Sigma size={16} />}
|
|
||||||
onClick={addNewFormula}
|
|
||||||
>
|
>
|
||||||
Add Formula
|
<Button
|
||||||
</Button>
|
className="add-formula-button periscope-btn secondary"
|
||||||
</Tooltip>
|
icon={<Sigma size={16} />}
|
||||||
</div>
|
onClick={addNewFormula}
|
||||||
|
>
|
||||||
|
Add Formula
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showAddTraceOperator && (
|
||||||
|
<div className="qb-add-formula">
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
Add Trace Matching
|
||||||
|
<Typography.Link
|
||||||
|
href="https://signoz.io/docs/userguide/query-builder-v5/#multi-query-analysis-advanced-comparisons"
|
||||||
|
target="_blank"
|
||||||
|
style={{ textDecoration: 'underline' }}
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
<br />
|
||||||
|
Learn more
|
||||||
|
</Typography.Link>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="add-formula-button periscope-btn secondary"
|
||||||
|
icon={<Sigma size={16} />}
|
||||||
|
onClick={() => addTraceOperator?.()}
|
||||||
|
>
|
||||||
|
Add Trace Matching
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
'Helvetica Neue', sans-serif;
|
'Helvetica Neue', sans-serif;
|
||||||
|
|
||||||
.query-where-clause-editor-container {
|
.query-where-clause-editor-container {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
|
|||||||
@ -26,9 +26,11 @@ export const QueryV2 = memo(function QueryV2({
|
|||||||
query,
|
query,
|
||||||
filterConfigs,
|
filterConfigs,
|
||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
|
showTraceOperator = false,
|
||||||
version,
|
version,
|
||||||
showOnlyWhereClause = false,
|
showOnlyWhereClause = false,
|
||||||
signalSource = '',
|
signalSource = '',
|
||||||
|
isMultiQueryAllowed = false,
|
||||||
}: QueryProps & { ref: React.RefObject<HTMLDivElement> }): JSX.Element {
|
}: QueryProps & { ref: React.RefObject<HTMLDivElement> }): JSX.Element {
|
||||||
const { cloneQuery, panelType } = useQueryBuilder();
|
const { cloneQuery, panelType } = useQueryBuilder();
|
||||||
|
|
||||||
@ -108,11 +110,15 @@ export const QueryV2 = memo(function QueryV2({
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<div className="qb-content-section">
|
<div className="qb-content-section">
|
||||||
{!showOnlyWhereClause && (
|
{isMultiQueryAllowed && (
|
||||||
<div className="qb-header-container">
|
<div className="qb-header-container">
|
||||||
<div className="query-actions-container">
|
<div className="query-actions-container">
|
||||||
<div className="query-actions-left-container">
|
<div className="query-actions-left-container">
|
||||||
<QBEntityOptions
|
<QBEntityOptions
|
||||||
|
hasTraceOperator={
|
||||||
|
showTraceOperator ||
|
||||||
|
(isListViewPanel && dataSource === DataSource.TRACES)
|
||||||
|
}
|
||||||
isMetricsDataSource={dataSource === DataSource.METRICS}
|
isMetricsDataSource={dataSource === DataSource.METRICS}
|
||||||
showFunctions={
|
showFunctions={
|
||||||
(version && version === ENTITY_VERSION_V4) ||
|
(version && version === ENTITY_VERSION_V4) ||
|
||||||
@ -139,7 +145,30 @@ export const QueryV2 = memo(function QueryV2({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isListViewPanel && (
|
{!isCollapsed &&
|
||||||
|
(showTraceOperator ||
|
||||||
|
(isListViewPanel && dataSource === DataSource.TRACES)) && (
|
||||||
|
<div className="qb-search-filter-container" style={{ flex: 1 }}>
|
||||||
|
<div className="query-search-container">
|
||||||
|
<QuerySearch
|
||||||
|
key={`query-search-${query.queryName}-${query.dataSource}`}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
queryData={query}
|
||||||
|
dataSource={dataSource}
|
||||||
|
signalSource={signalSource}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showSpanScopeSelector && (
|
||||||
|
<div className="traces-search-filter-container">
|
||||||
|
<div className="traces-search-filter-in">in</div>
|
||||||
|
<SpanScopeSelector query={query} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isMultiQueryAllowed && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
className="query-actions-dropdown"
|
className="query-actions-dropdown"
|
||||||
menu={{
|
menu={{
|
||||||
@ -181,28 +210,32 @@ export const QueryV2 = memo(function QueryV2({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="qb-search-filter-container">
|
{!showTraceOperator &&
|
||||||
<div className="query-search-container">
|
!(isListViewPanel && dataSource === DataSource.TRACES) && (
|
||||||
<QuerySearch
|
<div className="qb-search-filter-container">
|
||||||
key={`query-search-${query.queryName}-${query.dataSource}`}
|
<div className="query-search-container">
|
||||||
onChange={handleSearchChange}
|
<QuerySearch
|
||||||
queryData={query}
|
key={`query-search-${query.queryName}-${query.dataSource}`}
|
||||||
dataSource={dataSource}
|
onChange={handleSearchChange}
|
||||||
signalSource={signalSource}
|
queryData={query}
|
||||||
/>
|
dataSource={dataSource}
|
||||||
</div>
|
signalSource={signalSource}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{showSpanScopeSelector && (
|
{showSpanScopeSelector && (
|
||||||
<div className="traces-search-filter-container">
|
<div className="traces-search-filter-container">
|
||||||
<div className="traces-search-filter-in">in</div>
|
<div className="traces-search-filter-in">in</div>
|
||||||
<SpanScopeSelector query={query} />
|
<SpanScopeSelector query={query} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!showOnlyWhereClause &&
|
{!showOnlyWhereClause &&
|
||||||
!isListViewPanel &&
|
!isListViewPanel &&
|
||||||
|
!showTraceOperator &&
|
||||||
dataSource !== DataSource.METRICS && (
|
dataSource !== DataSource.METRICS && (
|
||||||
<QueryAggregation
|
<QueryAggregation
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
@ -225,7 +258,7 @@ export const QueryV2 = memo(function QueryV2({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!showOnlyWhereClause && (
|
{!showOnlyWhereClause && !isListViewPanel && !showTraceOperator && (
|
||||||
<QueryAddOns
|
<QueryAddOns
|
||||||
index={index}
|
index={index}
|
||||||
query={query}
|
query={query}
|
||||||
|
|||||||
@ -0,0 +1,180 @@
|
|||||||
|
.qb-trace-operator {
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&.non-list-view {
|
||||||
|
padding-left: 40px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
left: 12px;
|
||||||
|
height: calc(100% - 48px);
|
||||||
|
width: 1px;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
#1d212d,
|
||||||
|
#1d212d 4px,
|
||||||
|
transparent 4px,
|
||||||
|
transparent 8px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-span-source-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
&-query {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
&-query-name {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
padding: 2px;
|
||||||
|
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid rgba(242, 71, 105, 0.2);
|
||||||
|
background: rgba(242, 71, 105, 0.1);
|
||||||
|
color: var(--Sakura-400, #f56c87);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-arrow {
|
||||||
|
position: relative;
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
left: -26px;
|
||||||
|
height: 1px;
|
||||||
|
width: 20px;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
to right,
|
||||||
|
#1d212d,
|
||||||
|
#1d212d 4px,
|
||||||
|
transparent 4px,
|
||||||
|
transparent 8px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: -10px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
height: 4px;
|
||||||
|
width: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--bg-slate-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-aggregation-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-add-ons-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-add-ons-input {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
background: var(--bg-ink-300);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -16px;
|
||||||
|
top: 50%;
|
||||||
|
height: 1px;
|
||||||
|
width: 16px;
|
||||||
|
background-color: var(--bg-slate-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
padding: 0px 8px;
|
||||||
|
border-right: 1px solid var(--bg-slate-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightMode {
|
||||||
|
.qb-trace-operator {
|
||||||
|
&-arrow {
|
||||||
|
&::before {
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
to right,
|
||||||
|
var(--bg-vanilla-300),
|
||||||
|
var(--bg-vanilla-300) 4px,
|
||||||
|
transparent 4px,
|
||||||
|
transparent 8px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
background-color: var(--bg-vanilla-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.non-list-view {
|
||||||
|
&::before {
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
var(--bg-vanilla-300),
|
||||||
|
var(--bg-vanilla-300) 4px,
|
||||||
|
transparent 4px,
|
||||||
|
transparent 8px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-add-ons-input {
|
||||||
|
border: 1px solid var(--bg-vanilla-300) !important;
|
||||||
|
background: var(--bg-vanilla-100) !important;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: var(--bg-ink-500) !important;
|
||||||
|
border-right: 1px solid var(--bg-vanilla-300) !important;
|
||||||
|
background: var(--bg-vanilla-100) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,157 @@
|
|||||||
|
/* eslint-disable react/require-default-props */
|
||||||
|
/* eslint-disable sonarjs/no-duplicate-string */
|
||||||
|
|
||||||
|
import './TraceOperator.styles.scss';
|
||||||
|
|
||||||
|
import { Button, Select, Tooltip, Typography } from 'antd';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import InputWithLabel from 'components/InputWithLabel/InputWithLabel';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||||
|
import { Trash2 } from 'lucide-react';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
IBuilderQuery,
|
||||||
|
IBuilderTraceOperator,
|
||||||
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import QueryAddOns from '../QueryAddOns/QueryAddOns';
|
||||||
|
import QueryAggregation from '../QueryAggregation/QueryAggregation';
|
||||||
|
|
||||||
|
export default function TraceOperator({
|
||||||
|
traceOperator,
|
||||||
|
isListViewPanel = false,
|
||||||
|
}: {
|
||||||
|
traceOperator: IBuilderTraceOperator;
|
||||||
|
isListViewPanel?: boolean;
|
||||||
|
}): JSX.Element {
|
||||||
|
const { panelType, currentQuery, removeTraceOperator } = useQueryBuilder();
|
||||||
|
const { handleChangeQueryData } = useQueryOperations({
|
||||||
|
index: 0,
|
||||||
|
query: traceOperator,
|
||||||
|
entityVersion: '',
|
||||||
|
isForTraceOperator: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleTraceOperatorChange = useCallback(
|
||||||
|
(traceOperatorExpression: string) => {
|
||||||
|
handleChangeQueryData('expression', traceOperatorExpression);
|
||||||
|
},
|
||||||
|
[handleChangeQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeAggregateEvery = useCallback(
|
||||||
|
(value: IBuilderQuery['stepInterval']) => {
|
||||||
|
handleChangeQueryData('stepInterval', value);
|
||||||
|
},
|
||||||
|
[handleChangeQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeAggregation = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
handleChangeQueryData('aggregations', [
|
||||||
|
{
|
||||||
|
expression: value,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
[handleChangeQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeSpanSource = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
handleChangeQueryData('returnSpansFrom', value);
|
||||||
|
},
|
||||||
|
[handleChangeQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultSpanSource = useMemo(
|
||||||
|
() =>
|
||||||
|
traceOperator.returnSpansFrom ||
|
||||||
|
currentQuery.builder.queryData[0].queryName ||
|
||||||
|
'',
|
||||||
|
[currentQuery.builder.queryData, traceOperator?.returnSpansFrom],
|
||||||
|
);
|
||||||
|
|
||||||
|
const spanSourceOptions = useMemo(
|
||||||
|
() =>
|
||||||
|
currentQuery.builder.queryData.map((query) => ({
|
||||||
|
value: query.queryName,
|
||||||
|
label: (
|
||||||
|
<div className="qb-trace-operator-span-source-label">
|
||||||
|
<span className="qb-trace-operator-span-source-label-query">Query</span>
|
||||||
|
<p className="qb-trace-operator-span-source-label-query-name">
|
||||||
|
{query.queryName}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
[currentQuery.builder.queryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cx('qb-trace-operator', !isListViewPanel && 'non-list-view')}>
|
||||||
|
<div className="qb-trace-operator-container">
|
||||||
|
<InputWithLabel
|
||||||
|
className={cx(
|
||||||
|
'qb-trace-operator-input',
|
||||||
|
!isListViewPanel && 'qb-trace-operator-arrow',
|
||||||
|
)}
|
||||||
|
initialValue={traceOperator?.expression || ''}
|
||||||
|
label="TRACES MATCHING"
|
||||||
|
placeholder="Add condition..."
|
||||||
|
type="text"
|
||||||
|
onChange={handleTraceOperatorChange}
|
||||||
|
/>
|
||||||
|
{!isListViewPanel && (
|
||||||
|
<div className="qb-trace-operator-aggregation-container">
|
||||||
|
<div className={cx(!isListViewPanel && 'qb-trace-operator-arrow')}>
|
||||||
|
<QueryAggregation
|
||||||
|
dataSource={DataSource.TRACES}
|
||||||
|
key={`query-search-${traceOperator.queryName}`}
|
||||||
|
panelType={panelType || undefined}
|
||||||
|
onAggregationIntervalChange={handleChangeAggregateEvery}
|
||||||
|
onChange={handleChangeAggregation}
|
||||||
|
queryData={traceOperator}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
'qb-trace-operator-add-ons-container',
|
||||||
|
!isListViewPanel && 'qb-trace-operator-arrow',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<QueryAddOns
|
||||||
|
index={0}
|
||||||
|
query={traceOperator}
|
||||||
|
version="v3"
|
||||||
|
isForTraceOperator
|
||||||
|
isListViewPanel={false}
|
||||||
|
showReduceTo={false}
|
||||||
|
panelType={panelType}
|
||||||
|
>
|
||||||
|
<div className="qb-trace-operator-add-ons-input">
|
||||||
|
<Typography.Text className="label">Using spans from</Typography.Text>
|
||||||
|
<Select
|
||||||
|
bordered={false}
|
||||||
|
defaultValue={defaultSpanSource}
|
||||||
|
style={{ minWidth: 120 }}
|
||||||
|
onChange={handleChangeSpanSource}
|
||||||
|
options={spanSourceOptions}
|
||||||
|
listItemHeight={24}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QueryAddOns>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Tooltip title="Remove Trace Operator" placement="topLeft">
|
||||||
|
<Button className="periscope-btn ghost" onClick={removeTraceOperator}>
|
||||||
|
<Trash2 size={14} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -17,6 +17,19 @@
|
|||||||
font-weight: var(--font-weight-normal);
|
font-weight: var(--font-weight-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.view-title-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
border: 1px solid var(--bg-slate-400);
|
border: 1px solid var(--bg-slate-400);
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { RadioChangeEvent } from 'antd/es/radio';
|
|||||||
interface Option {
|
interface Option {
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SignozRadioGroupProps {
|
interface SignozRadioGroupProps {
|
||||||
@ -37,7 +38,10 @@ function SignozRadioGroup({
|
|||||||
value={option.value}
|
value={option.value}
|
||||||
className={value === option.value ? 'selected_view tab' : 'tab'}
|
className={value === option.value ? 'selected_view tab' : 'tab'}
|
||||||
>
|
>
|
||||||
{option.label}
|
<div className="view-title-container">
|
||||||
|
{option.icon && <div className="icon-container">{option.icon}</div>}
|
||||||
|
{option.label}
|
||||||
|
</div>
|
||||||
</Radio.Button>
|
</Radio.Button>
|
||||||
))}
|
))}
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
HavingForm,
|
HavingForm,
|
||||||
IBuilderFormula,
|
IBuilderFormula,
|
||||||
IBuilderQuery,
|
IBuilderQuery,
|
||||||
|
IBuilderTraceOperator,
|
||||||
IClickHouseQuery,
|
IClickHouseQuery,
|
||||||
IPromQLQuery,
|
IPromQLQuery,
|
||||||
Query,
|
Query,
|
||||||
@ -50,6 +51,8 @@ import {
|
|||||||
export const MAX_FORMULAS = 20;
|
export const MAX_FORMULAS = 20;
|
||||||
export const MAX_QUERIES = 26;
|
export const MAX_QUERIES = 26;
|
||||||
|
|
||||||
|
export const TRACE_OPERATOR_QUERY_NAME = 'T1';
|
||||||
|
|
||||||
export const idDivider = '--';
|
export const idDivider = '--';
|
||||||
export const selectValueDivider = '__';
|
export const selectValueDivider = '__';
|
||||||
|
|
||||||
@ -265,6 +268,11 @@ export const initialFormulaBuilderFormValues: IBuilderFormula = {
|
|||||||
legend: '',
|
legend: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const initialQueryBuilderFormTraceOperatorValues: IBuilderTraceOperator = {
|
||||||
|
...initialQueryBuilderFormTracesValues,
|
||||||
|
queryName: TRACE_OPERATOR_QUERY_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
export const initialQueryPromQLData: IPromQLQuery = {
|
export const initialQueryPromQLData: IPromQLQuery = {
|
||||||
name: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
|
name: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
|
||||||
query: '',
|
query: '',
|
||||||
@ -282,6 +290,7 @@ export const initialClickHouseData: IClickHouseQuery = {
|
|||||||
export const initialQueryBuilderData: QueryBuilderData = {
|
export const initialQueryBuilderData: QueryBuilderData = {
|
||||||
queryData: [initialQueryBuilderFormValues],
|
queryData: [initialQueryBuilderFormValues],
|
||||||
queryFormulas: [],
|
queryFormulas: [],
|
||||||
|
queryTraceOperator: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialSingleQueryMap: Record<
|
export const initialSingleQueryMap: Record<
|
||||||
|
|||||||
@ -54,6 +54,7 @@ function QuerySection({
|
|||||||
queryVariant: 'static',
|
queryVariant: 'static',
|
||||||
initialDataSource: ALERTS_DATA_SOURCE_MAP[alertType],
|
initialDataSource: ALERTS_DATA_SOURCE_MAP[alertType],
|
||||||
}}
|
}}
|
||||||
|
showTraceOperator={alertType === AlertTypes.TRACES_BASED_ALERT}
|
||||||
showFunctions={
|
showFunctions={
|
||||||
(alertType === AlertTypes.METRICS_BASED_ALERT &&
|
(alertType === AlertTypes.METRICS_BASED_ALERT &&
|
||||||
alertDef.version === ENTITY_VERSION_V4) ||
|
alertDef.version === ENTITY_VERSION_V4) ||
|
||||||
|
|||||||
@ -150,6 +150,7 @@ function FormAlertRules({
|
|||||||
|
|
||||||
const queryOptions = useMemo(() => {
|
const queryOptions = useMemo(() => {
|
||||||
const queryConfig: Record<EQueryType, () => SelectProps['options']> = {
|
const queryConfig: Record<EQueryType, () => SelectProps['options']> = {
|
||||||
|
// TODO: Filter out queries who are used in trace operator
|
||||||
[EQueryType.QUERY_BUILDER]: () => [
|
[EQueryType.QUERY_BUILDER]: () => [
|
||||||
...(getSelectedQueryOptions(currentQuery.builder.queryData) || []),
|
...(getSelectedQueryOptions(currentQuery.builder.queryData) || []),
|
||||||
...(getSelectedQueryOptions(currentQuery.builder.queryFormulas) || []),
|
...(getSelectedQueryOptions(currentQuery.builder.queryFormulas) || []),
|
||||||
|
|||||||
@ -30,5 +30,14 @@ export type QueryBuilderProps = {
|
|||||||
isListViewPanel?: boolean;
|
isListViewPanel?: boolean;
|
||||||
showFunctions?: boolean;
|
showFunctions?: boolean;
|
||||||
showOnlyWhereClause?: boolean;
|
showOnlyWhereClause?: boolean;
|
||||||
|
showOnlyTraceOperator?: boolean;
|
||||||
|
showTraceViewSelector?: boolean;
|
||||||
|
showTraceOperator?: boolean;
|
||||||
version: string;
|
version: string;
|
||||||
|
onChangeTraceView?: (view: TraceView) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum TraceView {
|
||||||
|
SPANS = 'spans',
|
||||||
|
TRACES = 'traces',
|
||||||
|
}
|
||||||
|
|||||||
@ -39,6 +39,7 @@ interface QBEntityOptionsProps {
|
|||||||
showCloneOption?: boolean;
|
showCloneOption?: boolean;
|
||||||
isListViewPanel?: boolean;
|
isListViewPanel?: boolean;
|
||||||
index?: number;
|
index?: number;
|
||||||
|
hasTraceOperator?: boolean;
|
||||||
queryVariant?: 'dropdown' | 'static';
|
queryVariant?: 'dropdown' | 'static';
|
||||||
onChangeDataSource?: (value: DataSource) => void;
|
onChangeDataSource?: (value: DataSource) => void;
|
||||||
}
|
}
|
||||||
@ -61,6 +62,7 @@ export default function QBEntityOptions({
|
|||||||
onCloneQuery,
|
onCloneQuery,
|
||||||
index,
|
index,
|
||||||
queryVariant,
|
queryVariant,
|
||||||
|
hasTraceOperator = false,
|
||||||
onChangeDataSource,
|
onChangeDataSource,
|
||||||
}: QBEntityOptionsProps): JSX.Element {
|
}: QBEntityOptionsProps): JSX.Element {
|
||||||
const handleCloneEntity = (): void => {
|
const handleCloneEntity = (): void => {
|
||||||
@ -97,7 +99,7 @@ export default function QBEntityOptions({
|
|||||||
value="query-builder"
|
value="query-builder"
|
||||||
className="periscope-btn visibility-toggle"
|
className="periscope-btn visibility-toggle"
|
||||||
onClick={onToggleVisibility}
|
onClick={onToggleVisibility}
|
||||||
disabled={isListViewPanel}
|
disabled={isListViewPanel && query?.dataSource !== DataSource.TRACES}
|
||||||
>
|
>
|
||||||
{entityData.disabled ? <EyeOff size={16} /> : <Eye size={16} />}
|
{entityData.disabled ? <EyeOff size={16} /> : <Eye size={16} />}
|
||||||
</Button>
|
</Button>
|
||||||
@ -115,6 +117,7 @@ export default function QBEntityOptions({
|
|||||||
className={cx(
|
className={cx(
|
||||||
'periscope-btn',
|
'periscope-btn',
|
||||||
entityType === 'query' ? 'query-name' : 'formula-name',
|
entityType === 'query' ? 'query-name' : 'formula-name',
|
||||||
|
hasTraceOperator && 'has-trace-operator',
|
||||||
isLogsExplorerPage && lastUsedQuery === index ? 'sync-btn' : '',
|
isLogsExplorerPage && lastUsedQuery === index ? 'sync-btn' : '',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -11,5 +11,7 @@ export type QueryProps = {
|
|||||||
version: string;
|
version: string;
|
||||||
showSpanScopeSelector?: boolean;
|
showSpanScopeSelector?: boolean;
|
||||||
showOnlyWhereClause?: boolean;
|
showOnlyWhereClause?: boolean;
|
||||||
|
showTraceOperator?: boolean;
|
||||||
signalSource?: string;
|
signalSource?: string;
|
||||||
|
isMultiQueryAllowed?: boolean;
|
||||||
} & Pick<QueryBuilderProps, 'filterConfigs' | 'queryComponents'>;
|
} & Pick<QueryBuilderProps, 'filterConfigs' | 'queryComponents'>;
|
||||||
|
|||||||
@ -37,11 +37,15 @@ function QuerySection(): JSX.Element {
|
|||||||
};
|
};
|
||||||
}, [panelTypes, renderOrderBy]);
|
}, [panelTypes, renderOrderBy]);
|
||||||
|
|
||||||
|
const isListViewPanel = useMemo(
|
||||||
|
() => panelTypes === PANEL_TYPES.LIST || panelTypes === PANEL_TYPES.TRACE,
|
||||||
|
[panelTypes],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryBuilderV2
|
<QueryBuilderV2
|
||||||
isListViewPanel={
|
isListViewPanel={isListViewPanel}
|
||||||
panelTypes === PANEL_TYPES.LIST || panelTypes === PANEL_TYPES.TRACE
|
showTraceOperator
|
||||||
}
|
|
||||||
config={{ initialDataSource: DataSource.TRACES, queryVariant: 'static' }}
|
config={{ initialDataSource: DataSource.TRACES, queryVariant: 'static' }}
|
||||||
queryComponents={queryComponents}
|
queryComponents={queryComponents}
|
||||||
panelType={panelTypes}
|
panelType={panelTypes}
|
||||||
|
|||||||
@ -54,9 +54,11 @@ export const useQueryOperations: UseQueryOperations = ({
|
|||||||
formula,
|
formula,
|
||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
entityVersion,
|
entityVersion,
|
||||||
|
isForTraceOperator = false,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
handleSetQueryData,
|
handleSetQueryData,
|
||||||
|
handleSetTraceOperatorData,
|
||||||
handleSetFormulaData,
|
handleSetFormulaData,
|
||||||
removeQueryBuilderEntityByIndex,
|
removeQueryBuilderEntityByIndex,
|
||||||
panelType,
|
panelType,
|
||||||
@ -400,9 +402,19 @@ export const useQueryOperations: UseQueryOperations = ({
|
|||||||
: value,
|
: value,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSetQueryData(index, newQuery);
|
if (isForTraceOperator) {
|
||||||
|
handleSetTraceOperatorData(index, newQuery);
|
||||||
|
} else {
|
||||||
|
handleSetQueryData(index, newQuery);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[query, index, handleSetQueryData],
|
[
|
||||||
|
query,
|
||||||
|
index,
|
||||||
|
handleSetQueryData,
|
||||||
|
handleSetTraceOperatorData,
|
||||||
|
isForTraceOperator,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChangeFormulaData: HandleChangeFormulaData = useCallback(
|
const handleChangeFormulaData: HandleChangeFormulaData = useCallback(
|
||||||
|
|||||||
@ -53,7 +53,6 @@ function TracesExplorer(): JSX.Element {
|
|||||||
handleRunQuery,
|
handleRunQuery,
|
||||||
stagedQuery,
|
stagedQuery,
|
||||||
handleSetConfig,
|
handleSetConfig,
|
||||||
updateQueriesData,
|
|
||||||
} = useQueryBuilder();
|
} = useQueryBuilder();
|
||||||
|
|
||||||
const { options } = useOptionsMenu({
|
const { options } = useOptionsMenu({
|
||||||
@ -112,48 +111,14 @@ function TracesExplorer(): JSX.Element {
|
|||||||
handleSetConfig(PANEL_TYPES.LIST, DataSource.TRACES);
|
handleSetConfig(PANEL_TYPES.LIST, DataSource.TRACES);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view === ExplorerViews.LIST) {
|
// TODO: remove formula when switching to List view
|
||||||
if (
|
|
||||||
selectedView !== ExplorerViews.LIST &&
|
|
||||||
currentQuery?.builder?.queryData?.[0]
|
|
||||||
) {
|
|
||||||
const filterToRetain = currentQuery.builder.queryData[0].filter;
|
|
||||||
|
|
||||||
const newDefaultQuery = updateAllQueriesOperators(
|
|
||||||
initialQueriesMap.traces,
|
|
||||||
PANEL_TYPES.LIST,
|
|
||||||
DataSource.TRACES,
|
|
||||||
);
|
|
||||||
|
|
||||||
const newListQuery = updateQueriesData(
|
|
||||||
newDefaultQuery,
|
|
||||||
'queryData',
|
|
||||||
(item, index) => {
|
|
||||||
if (index === 0) {
|
|
||||||
return { ...item, filter: filterToRetain };
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
setDefaultQuery(newListQuery);
|
|
||||||
}
|
|
||||||
setShouldReset(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelectedView(view);
|
setSelectedView(view);
|
||||||
handleExplorerTabChange(
|
handleExplorerTabChange(
|
||||||
view === ExplorerViews.TIMESERIES ? PANEL_TYPES.TIME_SERIES : view,
|
view === ExplorerViews.TIMESERIES ? PANEL_TYPES.TIME_SERIES : view,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[
|
[handleSetConfig, handleExplorerTabChange, selectedView, setSelectedView],
|
||||||
handleSetConfig,
|
|
||||||
handleExplorerTabChange,
|
|
||||||
selectedView,
|
|
||||||
currentQuery,
|
|
||||||
updateAllQueriesOperators,
|
|
||||||
updateQueriesData,
|
|
||||||
setSelectedView,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const listQuery = useMemo(() => {
|
const listQuery = useMemo(() => {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
initialClickHouseData,
|
initialClickHouseData,
|
||||||
initialFormulaBuilderFormValues,
|
initialFormulaBuilderFormValues,
|
||||||
initialQueriesMap,
|
initialQueriesMap,
|
||||||
|
initialQueryBuilderFormTraceOperatorValues,
|
||||||
initialQueryBuilderFormValuesMap,
|
initialQueryBuilderFormValuesMap,
|
||||||
initialQueryPromQLData,
|
initialQueryPromQLData,
|
||||||
initialQueryState,
|
initialQueryState,
|
||||||
@ -14,6 +15,7 @@ import {
|
|||||||
MAX_FORMULAS,
|
MAX_FORMULAS,
|
||||||
MAX_QUERIES,
|
MAX_QUERIES,
|
||||||
PANEL_TYPES,
|
PANEL_TYPES,
|
||||||
|
TRACE_OPERATOR_QUERY_NAME,
|
||||||
} from 'constants/queryBuilder';
|
} from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import {
|
import {
|
||||||
@ -47,6 +49,7 @@ import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteRe
|
|||||||
import {
|
import {
|
||||||
IBuilderFormula,
|
IBuilderFormula,
|
||||||
IBuilderQuery,
|
IBuilderQuery,
|
||||||
|
IBuilderTraceOperator,
|
||||||
IClickHouseQuery,
|
IClickHouseQuery,
|
||||||
IPromQLQuery,
|
IPromQLQuery,
|
||||||
Query,
|
Query,
|
||||||
@ -75,14 +78,18 @@ export const QueryBuilderContext = createContext<QueryBuilderContextType>({
|
|||||||
panelType: PANEL_TYPES.TIME_SERIES,
|
panelType: PANEL_TYPES.TIME_SERIES,
|
||||||
isEnabledQuery: false,
|
isEnabledQuery: false,
|
||||||
handleSetQueryData: () => {},
|
handleSetQueryData: () => {},
|
||||||
|
handleSetTraceOperatorData: () => {},
|
||||||
handleSetFormulaData: () => {},
|
handleSetFormulaData: () => {},
|
||||||
handleSetQueryItemData: () => {},
|
handleSetQueryItemData: () => {},
|
||||||
handleSetConfig: () => {},
|
handleSetConfig: () => {},
|
||||||
removeQueryBuilderEntityByIndex: () => {},
|
removeQueryBuilderEntityByIndex: () => {},
|
||||||
|
removeAllQueryBuilderEntities: () => {},
|
||||||
removeQueryTypeItemByIndex: () => {},
|
removeQueryTypeItemByIndex: () => {},
|
||||||
addNewBuilderQuery: () => {},
|
addNewBuilderQuery: () => {},
|
||||||
cloneQuery: () => {},
|
cloneQuery: () => {},
|
||||||
addNewFormula: () => {},
|
addNewFormula: () => {},
|
||||||
|
addTraceOperator: () => {},
|
||||||
|
removeTraceOperator: () => {},
|
||||||
addNewQueryItem: () => {},
|
addNewQueryItem: () => {},
|
||||||
redirectWithQueryBuilderData: () => {},
|
redirectWithQueryBuilderData: () => {},
|
||||||
handleRunQuery: () => {},
|
handleRunQuery: () => {},
|
||||||
@ -173,6 +180,10 @@ export function QueryBuilderProvider({
|
|||||||
...initialFormulaBuilderFormValues,
|
...initialFormulaBuilderFormValues,
|
||||||
...item,
|
...item,
|
||||||
})),
|
})),
|
||||||
|
queryTraceOperator: query.builder.queryTraceOperator?.map((item) => ({
|
||||||
|
...initialQueryBuilderFormTraceOperatorValues,
|
||||||
|
...item,
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupedQueryData = builder.queryData.map((item) => {
|
const setupedQueryData = builder.queryData.map((item) => {
|
||||||
@ -385,8 +396,11 @@ export function QueryBuilderProvider({
|
|||||||
const removeQueryBuilderEntityByIndex = useCallback(
|
const removeQueryBuilderEntityByIndex = useCallback(
|
||||||
(type: keyof QueryBuilderData, index: number) => {
|
(type: keyof QueryBuilderData, index: number) => {
|
||||||
setCurrentQuery((prevState) => {
|
setCurrentQuery((prevState) => {
|
||||||
const currentArray: (IBuilderQuery | IBuilderFormula)[] =
|
const currentArray: (
|
||||||
prevState.builder[type];
|
| IBuilderQuery
|
||||||
|
| IBuilderFormula
|
||||||
|
| IBuilderTraceOperator
|
||||||
|
)[] = prevState.builder[type];
|
||||||
|
|
||||||
const filteredArray = currentArray.filter((_, i) => index !== i);
|
const filteredArray = currentArray.filter((_, i) => index !== i);
|
||||||
|
|
||||||
@ -400,8 +414,11 @@ export function QueryBuilderProvider({
|
|||||||
});
|
});
|
||||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||||
setSupersetQuery((prevState) => {
|
setSupersetQuery((prevState) => {
|
||||||
const currentArray: (IBuilderQuery | IBuilderFormula)[] =
|
const currentArray: (
|
||||||
prevState.builder[type];
|
| IBuilderQuery
|
||||||
|
| IBuilderFormula
|
||||||
|
| IBuilderTraceOperator
|
||||||
|
)[] = prevState.builder[type];
|
||||||
|
|
||||||
const filteredArray = currentArray.filter((_, i) => index !== i);
|
const filteredArray = currentArray.filter((_, i) => index !== i);
|
||||||
|
|
||||||
@ -417,6 +434,20 @@ export function QueryBuilderProvider({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const removeAllQueryBuilderEntities = useCallback(
|
||||||
|
(type: keyof QueryBuilderData) => {
|
||||||
|
setCurrentQuery((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
builder: { ...prevState.builder, [type]: [] },
|
||||||
|
}));
|
||||||
|
setSupersetQuery((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
builder: { ...prevState.builder, [type]: [] },
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[setCurrentQuery, setSupersetQuery],
|
||||||
|
);
|
||||||
|
|
||||||
const removeQueryTypeItemByIndex = useCallback(
|
const removeQueryTypeItemByIndex = useCallback(
|
||||||
(type: EQueryType.PROM | EQueryType.CLICKHOUSE, index: number) => {
|
(type: EQueryType.PROM | EQueryType.CLICKHOUSE, index: number) => {
|
||||||
setCurrentQuery((prevState) => {
|
setCurrentQuery((prevState) => {
|
||||||
@ -639,6 +670,68 @@ export function QueryBuilderProvider({
|
|||||||
});
|
});
|
||||||
}, [createNewBuilderFormula]);
|
}, [createNewBuilderFormula]);
|
||||||
|
|
||||||
|
const addTraceOperator = useCallback((expression = '') => {
|
||||||
|
const trimmed = (expression || '').trim();
|
||||||
|
|
||||||
|
setCurrentQuery((prevState) => {
|
||||||
|
const existing = prevState.builder.queryTraceOperator?.[0] || null;
|
||||||
|
const updated: IBuilderTraceOperator = existing
|
||||||
|
? { ...existing, expression: trimmed }
|
||||||
|
: {
|
||||||
|
...initialQueryBuilderFormTraceOperatorValues,
|
||||||
|
queryName: TRACE_OPERATOR_QUERY_NAME,
|
||||||
|
expression: trimmed,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
builder: {
|
||||||
|
...prevState.builder,
|
||||||
|
// enforce single trace operator and replace only expression
|
||||||
|
queryTraceOperator: [updated],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||||
|
setSupersetQuery((prevState) => {
|
||||||
|
const existing = prevState.builder.queryTraceOperator?.[0] || null;
|
||||||
|
const updated: IBuilderTraceOperator = existing
|
||||||
|
? { ...existing, expression: trimmed }
|
||||||
|
: {
|
||||||
|
...initialQueryBuilderFormTraceOperatorValues,
|
||||||
|
queryName: TRACE_OPERATOR_QUERY_NAME,
|
||||||
|
expression: trimmed,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
builder: {
|
||||||
|
...prevState.builder,
|
||||||
|
// enforce single trace operator and replace only expression
|
||||||
|
queryTraceOperator: [updated],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const removeTraceOperator = useCallback(() => {
|
||||||
|
setCurrentQuery((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
builder: {
|
||||||
|
...prevState.builder,
|
||||||
|
queryTraceOperator: [],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||||
|
setSupersetQuery((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
builder: {
|
||||||
|
...prevState.builder,
|
||||||
|
queryTraceOperator: [],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const updateQueryBuilderData: <T>(
|
const updateQueryBuilderData: <T>(
|
||||||
arr: T[],
|
arr: T[],
|
||||||
index: number,
|
index: number,
|
||||||
@ -745,6 +838,44 @@ export function QueryBuilderProvider({
|
|||||||
},
|
},
|
||||||
[updateQueryBuilderData, updateSuperSetQueryBuilderData],
|
[updateQueryBuilderData, updateSuperSetQueryBuilderData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleSetTraceOperatorData = useCallback(
|
||||||
|
(index: number, traceOperatorData: IBuilderTraceOperator): void => {
|
||||||
|
setCurrentQuery((prevState) => {
|
||||||
|
const updatedTraceOperatorBuilderData = updateQueryBuilderData(
|
||||||
|
prevState.builder.queryTraceOperator,
|
||||||
|
index,
|
||||||
|
traceOperatorData,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
builder: {
|
||||||
|
...prevState.builder,
|
||||||
|
queryTraceOperator: updatedTraceOperatorBuilderData,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||||
|
setSupersetQuery((prevState) => {
|
||||||
|
const updatedTraceOperatorBuilderData = updateQueryBuilderData(
|
||||||
|
prevState.builder.queryTraceOperator,
|
||||||
|
index,
|
||||||
|
traceOperatorData,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
builder: {
|
||||||
|
...prevState.builder,
|
||||||
|
queryTraceOperator: updatedTraceOperatorBuilderData,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[updateQueryBuilderData],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSetFormulaData = useCallback(
|
const handleSetFormulaData = useCallback(
|
||||||
(index: number, formulaData: IBuilderFormula): void => {
|
(index: number, formulaData: IBuilderFormula): void => {
|
||||||
setCurrentQuery((prevState) => {
|
setCurrentQuery((prevState) => {
|
||||||
@ -1045,14 +1176,18 @@ export function QueryBuilderProvider({
|
|||||||
panelType,
|
panelType,
|
||||||
isEnabledQuery,
|
isEnabledQuery,
|
||||||
handleSetQueryData,
|
handleSetQueryData,
|
||||||
|
handleSetTraceOperatorData,
|
||||||
handleSetFormulaData,
|
handleSetFormulaData,
|
||||||
handleSetQueryItemData,
|
handleSetQueryItemData,
|
||||||
handleSetConfig,
|
handleSetConfig,
|
||||||
removeQueryBuilderEntityByIndex,
|
removeQueryBuilderEntityByIndex,
|
||||||
removeQueryTypeItemByIndex,
|
removeQueryTypeItemByIndex,
|
||||||
|
removeAllQueryBuilderEntities,
|
||||||
cloneQuery,
|
cloneQuery,
|
||||||
addNewBuilderQuery,
|
addNewBuilderQuery,
|
||||||
addNewFormula,
|
addNewFormula,
|
||||||
|
addTraceOperator,
|
||||||
|
removeTraceOperator,
|
||||||
addNewQueryItem,
|
addNewQueryItem,
|
||||||
redirectWithQueryBuilderData,
|
redirectWithQueryBuilderData,
|
||||||
handleRunQuery,
|
handleRunQuery,
|
||||||
@ -1073,14 +1208,18 @@ export function QueryBuilderProvider({
|
|||||||
panelType,
|
panelType,
|
||||||
isEnabledQuery,
|
isEnabledQuery,
|
||||||
handleSetQueryData,
|
handleSetQueryData,
|
||||||
|
handleSetTraceOperatorData,
|
||||||
handleSetFormulaData,
|
handleSetFormulaData,
|
||||||
handleSetQueryItemData,
|
handleSetQueryItemData,
|
||||||
handleSetConfig,
|
handleSetConfig,
|
||||||
removeQueryBuilderEntityByIndex,
|
removeQueryBuilderEntityByIndex,
|
||||||
removeQueryTypeItemByIndex,
|
removeQueryTypeItemByIndex,
|
||||||
|
removeAllQueryBuilderEntities,
|
||||||
cloneQuery,
|
cloneQuery,
|
||||||
addNewBuilderQuery,
|
addNewBuilderQuery,
|
||||||
addNewFormula,
|
addNewFormula,
|
||||||
|
addTraceOperator,
|
||||||
|
removeTraceOperator,
|
||||||
addNewQueryItem,
|
addNewQueryItem,
|
||||||
redirectWithQueryBuilderData,
|
redirectWithQueryBuilderData,
|
||||||
handleRunQuery,
|
handleRunQuery,
|
||||||
|
|||||||
@ -29,6 +29,10 @@ export interface IBuilderFormula {
|
|||||||
orderBy?: OrderByPayload[];
|
orderBy?: OrderByPayload[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IBuilderTraceOperator = IBuilderQuery & {
|
||||||
|
returnSpansFrom?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export interface TagFilterItem {
|
export interface TagFilterItem {
|
||||||
id: string;
|
id: string;
|
||||||
key?: BaseAutocompleteData;
|
key?: BaseAutocompleteData;
|
||||||
@ -124,6 +128,7 @@ export type BuilderQueryDataResourse = Record<
|
|||||||
export type MapData =
|
export type MapData =
|
||||||
| IBuilderQuery
|
| IBuilderQuery
|
||||||
| IBuilderFormula
|
| IBuilderFormula
|
||||||
|
| IBuilderTraceOperator
|
||||||
| IClickHouseQuery
|
| IClickHouseQuery
|
||||||
| IPromQLQuery;
|
| IPromQLQuery;
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export type RequestType =
|
|||||||
|
|
||||||
export type QueryType =
|
export type QueryType =
|
||||||
| 'builder_query'
|
| 'builder_query'
|
||||||
|
| 'builder_trace_operator'
|
||||||
| 'builder_formula'
|
| 'builder_formula'
|
||||||
| 'builder_sub_query'
|
| 'builder_sub_query'
|
||||||
| 'builder_join'
|
| 'builder_join'
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteRe
|
|||||||
import {
|
import {
|
||||||
IBuilderFormula,
|
IBuilderFormula,
|
||||||
IBuilderQuery,
|
IBuilderQuery,
|
||||||
|
IBuilderTraceOperator,
|
||||||
} from 'types/api/queryBuilder/queryBuilderData';
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import {
|
import {
|
||||||
BaseBuilderQuery,
|
BaseBuilderQuery,
|
||||||
@ -18,6 +19,7 @@ import { SelectOption } from './select';
|
|||||||
|
|
||||||
type UseQueryOperationsParams = Pick<QueryProps, 'index' | 'query'> &
|
type UseQueryOperationsParams = Pick<QueryProps, 'index' | 'query'> &
|
||||||
Pick<QueryBuilderProps, 'filterConfigs'> & {
|
Pick<QueryBuilderProps, 'filterConfigs'> & {
|
||||||
|
isForTraceOperator?: boolean;
|
||||||
formula?: IBuilderFormula;
|
formula?: IBuilderFormula;
|
||||||
isListViewPanel?: boolean;
|
isListViewPanel?: boolean;
|
||||||
entityVersion: string;
|
entityVersion: string;
|
||||||
@ -32,6 +34,14 @@ export type HandleChangeQueryData<T = IBuilderQuery> = <
|
|||||||
value: Value,
|
value: Value,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
|
export type HandleChangeTraceOperatorData<T = IBuilderTraceOperator> = <
|
||||||
|
Key extends keyof T,
|
||||||
|
Value extends T[Key]
|
||||||
|
>(
|
||||||
|
key: Key,
|
||||||
|
value: Value,
|
||||||
|
) => void;
|
||||||
|
|
||||||
// Legacy version for backward compatibility
|
// Legacy version for backward compatibility
|
||||||
export type HandleChangeQueryDataLegacy = HandleChangeQueryData<IBuilderQuery>;
|
export type HandleChangeQueryDataLegacy = HandleChangeQueryData<IBuilderQuery>;
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { Dispatch, SetStateAction } from 'react';
|
|||||||
import {
|
import {
|
||||||
IBuilderFormula,
|
IBuilderFormula,
|
||||||
IBuilderQuery,
|
IBuilderQuery,
|
||||||
|
IBuilderTraceOperator,
|
||||||
IClickHouseQuery,
|
IClickHouseQuery,
|
||||||
IPromQLQuery,
|
IPromQLQuery,
|
||||||
Query,
|
Query,
|
||||||
@ -222,6 +223,7 @@ export type ReduceOperators = 'last' | 'sum' | 'avg' | 'max' | 'min';
|
|||||||
export type QueryBuilderData = {
|
export type QueryBuilderData = {
|
||||||
queryData: IBuilderQuery[];
|
queryData: IBuilderQuery[];
|
||||||
queryFormulas: IBuilderFormula[];
|
queryFormulas: IBuilderFormula[];
|
||||||
|
queryTraceOperator: IBuilderTraceOperator[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QueryBuilderContextType = {
|
export type QueryBuilderContextType = {
|
||||||
@ -235,6 +237,10 @@ export type QueryBuilderContextType = {
|
|||||||
panelType: PANEL_TYPES | null;
|
panelType: PANEL_TYPES | null;
|
||||||
isEnabledQuery: boolean;
|
isEnabledQuery: boolean;
|
||||||
handleSetQueryData: (index: number, queryData: IBuilderQuery) => void;
|
handleSetQueryData: (index: number, queryData: IBuilderQuery) => void;
|
||||||
|
handleSetTraceOperatorData: (
|
||||||
|
index: number,
|
||||||
|
traceOperatorData: IBuilderTraceOperator,
|
||||||
|
) => void;
|
||||||
handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void;
|
handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void;
|
||||||
handleSetQueryItemData: (
|
handleSetQueryItemData: (
|
||||||
index: number,
|
index: number,
|
||||||
@ -249,12 +255,15 @@ export type QueryBuilderContextType = {
|
|||||||
type: keyof QueryBuilderData,
|
type: keyof QueryBuilderData,
|
||||||
index: number,
|
index: number,
|
||||||
) => void;
|
) => void;
|
||||||
|
removeAllQueryBuilderEntities: (type: keyof QueryBuilderData) => void;
|
||||||
removeQueryTypeItemByIndex: (
|
removeQueryTypeItemByIndex: (
|
||||||
type: EQueryType.PROM | EQueryType.CLICKHOUSE,
|
type: EQueryType.PROM | EQueryType.CLICKHOUSE,
|
||||||
index: number,
|
index: number,
|
||||||
) => void;
|
) => void;
|
||||||
addNewBuilderQuery: () => void;
|
addNewBuilderQuery: () => void;
|
||||||
addNewFormula: () => void;
|
addNewFormula: () => void;
|
||||||
|
removeTraceOperator: () => void;
|
||||||
|
addTraceOperator: (expression?: string) => void;
|
||||||
cloneQuery: (type: string, query: IBuilderQuery) => void;
|
cloneQuery: (type: string, query: IBuilderQuery) => void;
|
||||||
addNewQueryItem: (type: EQueryType.PROM | EQueryType.CLICKHOUSE) => void;
|
addNewQueryItem: (type: EQueryType.PROM | EQueryType.CLICKHOUSE) => void;
|
||||||
redirectWithQueryBuilderData: (
|
redirectWithQueryBuilderData: (
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -97,7 +98,12 @@ func (a *APIKey) Wrap(next http.Handler) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r = r.WithContext(ctx)
|
comment := ctxtypes.CommentFromContext(ctx)
|
||||||
|
comment.Set("auth_type", "api_key")
|
||||||
|
comment.Set("user_id", claims.UserID)
|
||||||
|
comment.Set("org_id", claims.OrgID)
|
||||||
|
|
||||||
|
r = r.WithContext(ctxtypes.NewContextWithComment(ctx, comment))
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/sharder"
|
"github.com/SigNoz/signoz/pkg/sharder"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -50,7 +51,12 @@ func (a *Auth) Wrap(next http.Handler) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r = r.WithContext(ctx)
|
comment := ctxtypes.CommentFromContext(ctx)
|
||||||
|
comment.Set("auth_type", "jwt")
|
||||||
|
comment.Set("user_id", claims.UserID)
|
||||||
|
comment.Set("org_id", claims.OrgID)
|
||||||
|
|
||||||
|
r = r.WithContext(ctxtypes.NewContextWithComment(ctx, comment))
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
|||||||
24
pkg/http/middleware/comment.go
Normal file
24
pkg/http/middleware/comment.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Comment struct{}
|
||||||
|
|
||||||
|
func NewComment() *Comment {
|
||||||
|
return &Comment{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (middleware *Comment) Wrap(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
|
comment := ctxtypes.CommentFromContext(req.Context())
|
||||||
|
comment.Merge(ctxtypes.CommentFromHTTPRequest(req))
|
||||||
|
|
||||||
|
req = req.WithContext(ctxtypes.NewContextWithComment(req.Context(), comment))
|
||||||
|
next.ServeHTTP(rw, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -2,16 +2,11 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||||
)
|
)
|
||||||
@ -55,9 +50,6 @@ func (middleware *Logging) Wrap(next http.Handler) http.Handler {
|
|||||||
string(semconv.HTTPRouteKey), path,
|
string(semconv.HTTPRouteKey), path,
|
||||||
}
|
}
|
||||||
|
|
||||||
logCommentKVs := middleware.getLogCommentKVs(req)
|
|
||||||
req = req.WithContext(context.WithValue(req.Context(), common.LogCommentKey, logCommentKVs))
|
|
||||||
|
|
||||||
badResponseBuffer := new(bytes.Buffer)
|
badResponseBuffer := new(bytes.Buffer)
|
||||||
writer := newBadResponseLoggingWriter(rw, badResponseBuffer)
|
writer := newBadResponseLoggingWriter(rw, badResponseBuffer)
|
||||||
next.ServeHTTP(writer, req)
|
next.ServeHTTP(writer, req)
|
||||||
@ -85,67 +77,3 @@ func (middleware *Logging) Wrap(next http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (middleware *Logging) getLogCommentKVs(r *http.Request) map[string]string {
|
|
||||||
referrer := r.Header.Get("Referer")
|
|
||||||
|
|
||||||
var path, dashboardID, alertID, page, client, viewName, tab string
|
|
||||||
|
|
||||||
if referrer != "" {
|
|
||||||
referrerURL, _ := url.Parse(referrer)
|
|
||||||
client = "browser"
|
|
||||||
path = referrerURL.Path
|
|
||||||
|
|
||||||
if strings.Contains(path, "/dashboard") {
|
|
||||||
// Split the path into segments
|
|
||||||
pathSegments := strings.Split(referrerURL.Path, "/")
|
|
||||||
// The dashboard ID should be the segment after "/dashboard/"
|
|
||||||
// Loop through pathSegments to find "dashboard" and then take the next segment as the ID
|
|
||||||
for i, segment := range pathSegments {
|
|
||||||
if segment == "dashboard" && i < len(pathSegments)-1 {
|
|
||||||
// Return the next segment, which should be the dashboard ID
|
|
||||||
dashboardID = pathSegments[i+1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
page = "dashboards"
|
|
||||||
} else if strings.Contains(path, "/alerts") {
|
|
||||||
urlParams := referrerURL.Query()
|
|
||||||
alertID = urlParams.Get("ruleId")
|
|
||||||
page = "alerts"
|
|
||||||
} else if strings.Contains(path, "logs") && strings.Contains(path, "explorer") {
|
|
||||||
page = "logs-explorer"
|
|
||||||
viewName = referrerURL.Query().Get("viewName")
|
|
||||||
} else if strings.Contains(path, "/trace") || strings.Contains(path, "traces-explorer") {
|
|
||||||
page = "traces-explorer"
|
|
||||||
viewName = referrerURL.Query().Get("viewName")
|
|
||||||
} else if strings.Contains(path, "/services") {
|
|
||||||
page = "services"
|
|
||||||
tab = referrerURL.Query().Get("tab")
|
|
||||||
if tab == "" {
|
|
||||||
tab = "OVER_METRICS"
|
|
||||||
}
|
|
||||||
} else if strings.Contains(path, "/metrics") {
|
|
||||||
page = "metrics-explorer"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
client = "api"
|
|
||||||
}
|
|
||||||
|
|
||||||
var email string
|
|
||||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
|
||||||
if err == nil {
|
|
||||||
email = claims.Email
|
|
||||||
}
|
|
||||||
|
|
||||||
kvs := map[string]string{
|
|
||||||
"path": path,
|
|
||||||
"dashboardID": dashboardID,
|
|
||||||
"alertID": alertID,
|
|
||||||
"source": page,
|
|
||||||
"client": client,
|
|
||||||
"viewName": viewName,
|
|
||||||
"servicesTab": tab,
|
|
||||||
"email": email,
|
|
||||||
}
|
|
||||||
return kvs
|
|
||||||
}
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/analytics"
|
"github.com/SigNoz/signoz/pkg/analytics"
|
||||||
@ -12,6 +11,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/factory"
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
"github.com/SigNoz/signoz/pkg/http/render"
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"github.com/SigNoz/signoz/pkg/variables"
|
"github.com/SigNoz/signoz/pkg/variables"
|
||||||
@ -166,49 +166,9 @@ func (a *API) logEvent(ctx context.Context, referrer string, event *qbtypes.QBEv
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
properties["referrer"] = referrer
|
comments := ctxtypes.CommentFromContext(ctx).Map()
|
||||||
|
for key, value := range comments {
|
||||||
logsExplorerMatched, _ := regexp.MatchString(`/logs/logs-explorer(?:\?.*)?$`, referrer)
|
properties[key] = value
|
||||||
traceExplorerMatched, _ := regexp.MatchString(`/traces-explorer(?:\?.*)?$`, referrer)
|
|
||||||
metricsExplorerMatched, _ := regexp.MatchString(`/metrics-explorer/explorer(?:\?.*)?$`, referrer)
|
|
||||||
dashboardMatched, _ := regexp.MatchString(`/dashboard/[a-zA-Z0-9\-]+/(new|edit)(?:\?.*)?$`, referrer)
|
|
||||||
alertMatched, _ := regexp.MatchString(`/alerts/(new|edit)(?:\?.*)?$`, referrer)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case dashboardMatched:
|
|
||||||
properties["module_name"] = "dashboard"
|
|
||||||
case alertMatched:
|
|
||||||
properties["module_name"] = "rule"
|
|
||||||
case metricsExplorerMatched:
|
|
||||||
properties["module_name"] = "metrics-explorer"
|
|
||||||
case logsExplorerMatched:
|
|
||||||
properties["module_name"] = "logs-explorer"
|
|
||||||
case traceExplorerMatched:
|
|
||||||
properties["module_name"] = "traces-explorer"
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if dashboardMatched {
|
|
||||||
if dashboardIDRegex, err := regexp.Compile(`/dashboard/([a-f0-9\-]+)/`); err == nil {
|
|
||||||
if matches := dashboardIDRegex.FindStringSubmatch(referrer); len(matches) > 1 {
|
|
||||||
properties["dashboard_id"] = matches[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if widgetIDRegex, err := regexp.Compile(`widgetId=([a-f0-9\-]+)`); err == nil {
|
|
||||||
if matches := widgetIDRegex.FindStringSubmatch(referrer); len(matches) > 1 {
|
|
||||||
properties["widget_id"] = matches[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if alertMatched {
|
|
||||||
if alertIDRegex, err := regexp.Compile(`ruleId=(\d+)`); err == nil {
|
|
||||||
if matches := alertIDRegex.FindStringSubmatch(referrer); len(matches) > 1 {
|
|
||||||
properties["rule_id"] = matches[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !event.HasData {
|
if !event.HasData {
|
||||||
|
|||||||
@ -3640,28 +3640,8 @@ func readRowsForTimeSeriesResult(rows driver.Rows, vars []interface{}, columnNam
|
|||||||
return seriesList, getPersonalisedError(rows.Err())
|
return seriesList, getPersonalisedError(rows.Err())
|
||||||
}
|
}
|
||||||
|
|
||||||
func logCommentKVs(ctx context.Context) map[string]string {
|
|
||||||
kv := ctx.Value(common.LogCommentKey)
|
|
||||||
if kv == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
logCommentKVs, ok := kv.(map[string]string)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return logCommentKVs
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTimeSeriesResultV3 runs the query and returns list of time series
|
// GetTimeSeriesResultV3 runs the query and returns list of time series
|
||||||
func (r *ClickHouseReader) GetTimeSeriesResultV3(ctx context.Context, query string) ([]*v3.Series, error) {
|
func (r *ClickHouseReader) GetTimeSeriesResultV3(ctx context.Context, query string) ([]*v3.Series, error) {
|
||||||
|
|
||||||
ctxArgs := map[string]interface{}{"query": query}
|
|
||||||
for k, v := range logCommentKVs(ctx) {
|
|
||||||
ctxArgs[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
defer utils.Elapsed("GetTimeSeriesResultV3", ctxArgs)()
|
|
||||||
|
|
||||||
// Hook up query progress reporting if requested.
|
// Hook up query progress reporting if requested.
|
||||||
queryId := ctx.Value("queryId")
|
queryId := ctx.Value("queryId")
|
||||||
if queryId != nil {
|
if queryId != nil {
|
||||||
@ -3725,20 +3705,12 @@ func (r *ClickHouseReader) GetTimeSeriesResultV3(ctx context.Context, query stri
|
|||||||
|
|
||||||
// GetListResultV3 runs the query and returns list of rows
|
// GetListResultV3 runs the query and returns list of rows
|
||||||
func (r *ClickHouseReader) GetListResultV3(ctx context.Context, query string) ([]*v3.Row, error) {
|
func (r *ClickHouseReader) GetListResultV3(ctx context.Context, query string) ([]*v3.Row, error) {
|
||||||
|
|
||||||
ctxArgs := map[string]interface{}{"query": query}
|
|
||||||
for k, v := range logCommentKVs(ctx) {
|
|
||||||
ctxArgs[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
defer utils.Elapsed("GetListResultV3", ctxArgs)()
|
|
||||||
|
|
||||||
rows, err := r.db.Query(ctx, query)
|
rows, err := r.db.Query(ctx, query)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("error while reading time series result", zap.Error(err))
|
zap.L().Error("error while reading time series result", zap.Error(err))
|
||||||
return nil, errors.New(err.Error())
|
return nil, errors.New(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@ -220,6 +220,7 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
|
|||||||
).Wrap)
|
).Wrap)
|
||||||
r.Use(middleware.NewAPIKey(s.signoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.signoz.Instrumentation.Logger(), s.signoz.Sharder).Wrap)
|
r.Use(middleware.NewAPIKey(s.signoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.signoz.Instrumentation.Logger(), s.signoz.Sharder).Wrap)
|
||||||
r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap)
|
r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap)
|
||||||
|
r.Use(middleware.NewComment().Wrap)
|
||||||
|
|
||||||
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger())
|
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger())
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
type LogCommentContextKeyType string
|
|
||||||
|
|
||||||
const LogCommentKey LogCommentContextKeyType = "logComment"
|
|
||||||
@ -7,7 +7,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||||
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
opentracing "github.com/opentracing/opentracing-go"
|
opentracing "github.com/opentracing/opentracing-go"
|
||||||
@ -369,12 +369,10 @@ func (g *PromRuleTask) Eval(ctx context.Context, ts time.Time) {
|
|||||||
rule.SetEvaluationTimestamp(t)
|
rule.SetEvaluationTimestamp(t)
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
kvs := map[string]string{
|
comment := ctxtypes.CommentFromContext(ctx)
|
||||||
"alertID": rule.ID(),
|
comment.Set("rule_id", rule.ID())
|
||||||
"source": "alerts",
|
comment.Set("auth_type", "internal")
|
||||||
"client": "query-service",
|
ctx = ctxtypes.NewContextWithComment(ctx, comment)
|
||||||
}
|
|
||||||
ctx = context.WithValue(ctx, common.LogCommentKey, kvs)
|
|
||||||
|
|
||||||
_, err := rule.Eval(ctx, ts)
|
_, err := rule.Eval(ctx, ts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/utils/labels"
|
"github.com/SigNoz/signoz/pkg/query-service/utils/labels"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||||
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
opentracing "github.com/opentracing/opentracing-go"
|
opentracing "github.com/opentracing/opentracing-go"
|
||||||
@ -352,12 +352,10 @@ func (g *RuleTask) Eval(ctx context.Context, ts time.Time) {
|
|||||||
rule.SetEvaluationTimestamp(t)
|
rule.SetEvaluationTimestamp(t)
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
kvs := map[string]string{
|
comment := ctxtypes.CommentFromContext(ctx)
|
||||||
"alertID": rule.ID(),
|
comment.Set("rule_id", rule.ID())
|
||||||
"source": "alerts",
|
comment.Set("auth_type", "internal")
|
||||||
"client": "query-service",
|
ctx = ctxtypes.NewContextWithComment(ctx, comment)
|
||||||
}
|
|
||||||
ctx = context.WithValue(ctx, common.LogCommentKey, kvs)
|
|
||||||
|
|
||||||
_, err := rule.Eval(ctx, ts)
|
_, err := rule.Eval(ctx, ts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -2,13 +2,12 @@ package telemetrystorehook
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ClickHouse/clickhouse-go/v2"
|
"github.com/ClickHouse/clickhouse-go/v2"
|
||||||
"github.com/SigNoz/signoz/pkg/factory"
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
|
||||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type provider struct {
|
type provider struct {
|
||||||
@ -32,11 +31,7 @@ func NewSettings(ctx context.Context, providerSettings factory.ProviderSettings,
|
|||||||
func (h *provider) BeforeQuery(ctx context.Context, _ *telemetrystore.QueryEvent) context.Context {
|
func (h *provider) BeforeQuery(ctx context.Context, _ *telemetrystore.QueryEvent) context.Context {
|
||||||
settings := clickhouse.Settings{}
|
settings := clickhouse.Settings{}
|
||||||
|
|
||||||
// Apply default settings
|
settings["log_comment"] = ctxtypes.CommentFromContext(ctx).String()
|
||||||
logComment := h.getLogComment(ctx)
|
|
||||||
if logComment != "" {
|
|
||||||
settings["log_comment"] = logComment
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Value("enforce_max_result_rows") != nil {
|
if ctx.Value("enforce_max_result_rows") != nil {
|
||||||
settings["max_result_rows"] = h.settings.MaxResultRows
|
settings["max_result_rows"] = h.settings.MaxResultRows
|
||||||
@ -91,22 +86,4 @@ func (h *provider) BeforeQuery(ctx context.Context, _ *telemetrystore.QueryEvent
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *provider) AfterQuery(ctx context.Context, event *telemetrystore.QueryEvent) {
|
func (h *provider) AfterQuery(ctx context.Context, event *telemetrystore.QueryEvent) {}
|
||||||
}
|
|
||||||
|
|
||||||
func (h *provider) getLogComment(ctx context.Context) string {
|
|
||||||
// Get the key-value pairs from context for log comment
|
|
||||||
kv := ctx.Value(common.LogCommentKey)
|
|
||||||
if kv == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
logCommentKVs, ok := kv.(map[string]string)
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
logComment, _ := json.Marshal(logCommentKVs)
|
|
||||||
|
|
||||||
return string(logComment)
|
|
||||||
}
|
|
||||||
|
|||||||
163
pkg/types/ctxtypes/comment.go
Normal file
163
pkg/types/ctxtypes/comment.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package ctxtypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logsExplorerRegex = regexp.MustCompile(`/logs/logs-explorer(?:\?.*)?$`)
|
||||||
|
traceExplorerRegex = regexp.MustCompile(`/traces-explorer(?:\?.*)?$`)
|
||||||
|
metricsExplorerRegex = regexp.MustCompile(`/metrics-explorer/explorer(?:\?.*)?$`)
|
||||||
|
dashboardRegex = regexp.MustCompile(`/dashboard/[a-zA-Z0-9\-]+/(new|edit)(?:\?.*)?$`)
|
||||||
|
dashboardIDRegex = regexp.MustCompile(`/dashboard/([a-f0-9\-]+)/`)
|
||||||
|
widgetIDRegex = regexp.MustCompile(`widgetId=([a-f0-9\-]+)`)
|
||||||
|
ruleRegex = regexp.MustCompile(`/alerts/(new|edit)(?:\?.*)?$`)
|
||||||
|
ruleIDRegex = regexp.MustCompile(`ruleId=(\d+)`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type commentCtxKey struct{}
|
||||||
|
|
||||||
|
type Comment struct {
|
||||||
|
vals map[string]string
|
||||||
|
mtx sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContextWithComment(ctx context.Context, comment *Comment) context.Context {
|
||||||
|
return context.WithValue(ctx, commentCtxKey{}, comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CommentFromContext(ctx context.Context) *Comment {
|
||||||
|
comment, ok := ctx.Value(commentCtxKey{}).(*Comment)
|
||||||
|
if !ok {
|
||||||
|
return NewComment()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a deep copy of the comment to prevent mutations from affecting the original
|
||||||
|
copy := NewComment()
|
||||||
|
copy.Merge(comment.Map())
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
func CommentFromHTTPRequest(req *http.Request) map[string]string {
|
||||||
|
comments := map[string]string{}
|
||||||
|
|
||||||
|
referrer := req.Header.Get("Referer")
|
||||||
|
if referrer == "" {
|
||||||
|
return comments
|
||||||
|
}
|
||||||
|
|
||||||
|
referrerURL, err := url.Parse(referrer)
|
||||||
|
if err != nil {
|
||||||
|
return comments
|
||||||
|
}
|
||||||
|
|
||||||
|
logsExplorerMatched := logsExplorerRegex.MatchString(referrer)
|
||||||
|
traceExplorerMatched := traceExplorerRegex.MatchString(referrer)
|
||||||
|
metricsExplorerMatched := metricsExplorerRegex.MatchString(referrer)
|
||||||
|
dashboardMatched := dashboardRegex.MatchString(referrer)
|
||||||
|
ruleMatched := ruleRegex.MatchString(referrer)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case dashboardMatched:
|
||||||
|
comments["module_name"] = "dashboard"
|
||||||
|
case ruleMatched:
|
||||||
|
comments["module_name"] = "rule"
|
||||||
|
case metricsExplorerMatched:
|
||||||
|
comments["module_name"] = "metrics-explorer"
|
||||||
|
case logsExplorerMatched:
|
||||||
|
comments["module_name"] = "logs-explorer"
|
||||||
|
case traceExplorerMatched:
|
||||||
|
comments["module_name"] = "traces-explorer"
|
||||||
|
default:
|
||||||
|
return comments
|
||||||
|
}
|
||||||
|
|
||||||
|
if dashboardMatched {
|
||||||
|
if matches := dashboardIDRegex.FindStringSubmatch(referrer); len(matches) > 1 {
|
||||||
|
comments["dashboard_id"] = matches[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches := widgetIDRegex.FindStringSubmatch(referrer); len(matches) > 1 {
|
||||||
|
comments["widget_id"] = matches[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ruleMatched {
|
||||||
|
if matches := ruleIDRegex.FindStringSubmatch(referrer); len(matches) > 1 {
|
||||||
|
comments["rule_id"] = matches[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
comments["http_path"] = referrerURL.Path
|
||||||
|
|
||||||
|
return comments
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewComment creates a new Comment with an empty map. It is safe to use concurrently.
|
||||||
|
func NewComment() *Comment {
|
||||||
|
return &Comment{vals: map[string]string{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (comment *Comment) Set(key, value string) {
|
||||||
|
comment.mtx.Lock()
|
||||||
|
defer comment.mtx.Unlock()
|
||||||
|
|
||||||
|
comment.vals[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (comment *Comment) Merge(vals map[string]string) {
|
||||||
|
comment.mtx.Lock()
|
||||||
|
defer comment.mtx.Unlock()
|
||||||
|
|
||||||
|
// If vals is nil, do nothing. Comment should not panic.
|
||||||
|
if vals == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range vals {
|
||||||
|
comment.vals[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (comment *Comment) Map() map[string]string {
|
||||||
|
comment.mtx.RLock()
|
||||||
|
defer comment.mtx.RUnlock()
|
||||||
|
|
||||||
|
copyOfVals := make(map[string]string)
|
||||||
|
for key, value := range comment.vals {
|
||||||
|
copyOfVals[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyOfVals
|
||||||
|
}
|
||||||
|
|
||||||
|
func (comment *Comment) String() string {
|
||||||
|
comment.mtx.RLock()
|
||||||
|
defer comment.mtx.RUnlock()
|
||||||
|
|
||||||
|
commentJSON, err := json.Marshal(comment.vals)
|
||||||
|
if err != nil {
|
||||||
|
return "{}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(commentJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (comment *Comment) Equal(other *Comment) bool {
|
||||||
|
if len(comment.vals) != len(other.vals) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range comment.vals {
|
||||||
|
if val, ok := other.vals[key]; !ok || val != value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
123
pkg/types/ctxtypes/comment_test.go
Normal file
123
pkg/types/ctxtypes/comment_test.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package ctxtypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommentFromHTTPRequest(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
req *http.Request
|
||||||
|
expected map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "EmptyReferer",
|
||||||
|
req: &http.Request{Header: http.Header{"Referer": {""}}},
|
||||||
|
expected: map[string]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ControlCharacterInReferer",
|
||||||
|
req: &http.Request{Header: http.Header{"Referer": {"https://signoz.io/logs/logs-explorer\x00"}}},
|
||||||
|
expected: map[string]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "LogsExplorer",
|
||||||
|
req: &http.Request{Header: http.Header{"Referer": {"https://signoz.io/logs/logs-explorer"}}},
|
||||||
|
expected: map[string]string{"http_path": "/logs/logs-explorer", "module_name": "logs-explorer"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TracesExplorer",
|
||||||
|
req: &http.Request{Header: http.Header{"Referer": {"https://signoz.io/traces-explorer"}}},
|
||||||
|
expected: map[string]string{"http_path": "/traces-explorer", "module_name": "traces-explorer"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MetricsExplorer",
|
||||||
|
req: &http.Request{Header: http.Header{"Referer": {"https://signoz.io/metrics-explorer/explorer"}}},
|
||||||
|
expected: map[string]string{"http_path": "/metrics-explorer/explorer", "module_name": "metrics-explorer"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DashboardWithID",
|
||||||
|
req: &http.Request{Header: http.Header{"Referer": {"https://signoz.io/dashboard/123/new"}}},
|
||||||
|
expected: map[string]string{"http_path": "/dashboard/123/new", "module_name": "dashboard", "dashboard_id": "123"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Rule",
|
||||||
|
req: &http.Request{Header: http.Header{"Referer": {"https://signoz.io/alerts/new"}}},
|
||||||
|
expected: map[string]string{"http_path": "/alerts/new", "module_name": "rule"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RuleWithID",
|
||||||
|
req: &http.Request{Header: http.Header{"Referer": {"https://signoz.io/alerts/edit?ruleId=123"}}},
|
||||||
|
expected: map[string]string{"http_path": "/alerts/edit", "module_name": "rule", "rule_id": "123"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actual := CommentFromHTTPRequest(tc.req)
|
||||||
|
|
||||||
|
assert.True(t, (&Comment{vals: tc.expected}).Equal(&Comment{vals: actual}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommentFromContext(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
comment1 := CommentFromContext(ctx)
|
||||||
|
assert.True(t, NewComment().Equal(comment1))
|
||||||
|
|
||||||
|
comment1.Set("k1", "v1")
|
||||||
|
ctx = NewContextWithComment(ctx, comment1)
|
||||||
|
actual1 := CommentFromContext(ctx)
|
||||||
|
assert.True(t, comment1.Equal(actual1))
|
||||||
|
|
||||||
|
// Get the comment from the context, mutate it, but this time do not set it back in the context
|
||||||
|
comment2 := CommentFromContext(ctx)
|
||||||
|
comment2.Set("k2", "v2")
|
||||||
|
|
||||||
|
actual2 := CommentFromContext(ctx)
|
||||||
|
// Since comment2 was not set back in the context, it should not affect the original comment1
|
||||||
|
assert.True(t, comment1.Equal(actual2))
|
||||||
|
assert.False(t, comment2.Equal(actual2))
|
||||||
|
assert.False(t, comment1.Equal(comment2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommentFromContextConcurrent(t *testing.T) {
|
||||||
|
comment := NewComment()
|
||||||
|
comment.Set("k1", "v1")
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = NewContextWithComment(ctx, comment)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
ctxs := make([]context.Context, 10)
|
||||||
|
var mtx sync.Mutex
|
||||||
|
wg.Add(10)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
comment := CommentFromContext(ctx)
|
||||||
|
comment.Set("k2", fmt.Sprintf("v%d", i))
|
||||||
|
newCtx := NewContextWithComment(ctx, comment)
|
||||||
|
mtx.Lock()
|
||||||
|
ctxs[i] = newCtx
|
||||||
|
mtx.Unlock()
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
for i, ctx := range ctxs {
|
||||||
|
comment := CommentFromContext(ctx)
|
||||||
|
assert.Equal(t, len(comment.vals), 2)
|
||||||
|
assert.Equal(t, comment.vals["k1"], "v1")
|
||||||
|
assert.Equal(t, comment.vals["k2"], fmt.Sprintf("v%d", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user