Compare commits

..

1 Commits

Author SHA1 Message Date
Ishan Uniyal
aa004c9da9 feat: handled case for checkbox query reload 2025-12-29 07:12:39 +05:30
28 changed files with 104 additions and 1326 deletions

View File

@@ -278,13 +278,3 @@ tokenizer:
token:
# The maximum number of tokens a user can have. This limits the number of concurrent sessions a user can have.
max_per_user: 5
##################### Flagger #####################
flagger:
# Config are the overrides for the feature flags which come directly from the config file.
config:
boolean:
string:
float:
integer:
object:

View File

@@ -1726,51 +1726,6 @@ paths:
summary: Update user preference
tags:
- preferences
/api/v2/features:
get:
deprecated: false
description: This endpoint returns the supported features and their details
operationId: GetFeatures
responses:
"200":
content:
application/json:
schema:
properties:
data:
items:
$ref: '#/components/schemas/FeaturetypesGettableFeature'
type: array
status:
type: string
type: object
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: Get features
tags:
- features
/api/v2/orgs/me:
get:
deprecated: false
@@ -2218,24 +2173,6 @@ components:
message:
type: string
type: object
FeaturetypesGettableFeature:
properties:
defaultVariant:
type: string
description:
type: string
kind:
type: string
name:
type: string
resolvedValue: {}
stage:
type: string
variants:
additionalProperties: {}
nullable: true
type: object
type: object
PreferencetypesPreference:
properties:
allowedScopes:

View File

@@ -421,11 +421,16 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
...currentFilter,
value: currentFilter.value.filter((val) => val !== value),
};
if (newFilter.value.length === 0) {
query.filters.items = query.filters.items.filter(
(item) => !isEqual(item.key?.key, filter.attributeKey.key),
);
if (query.filter?.expression) {
query.filter.expression = removeKeysFromExpression(
query.filter.expression,
[filter.attributeKey.key],
);
}
} else {
query.filters.items = query.filters.items.map((item) => {
if (isEqual(item.key?.key, filter.attributeKey.key)) {
@@ -435,6 +440,16 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
});
}
} else {
const newFilter = {
...currentFilter,
value: currentFilter.value === value ? null : currentFilter.value,
};
if (newFilter.value === null && query.filter?.expression) {
query.filter.expression = removeKeysFromExpression(
query.filter.expression,
[filter.attributeKey.key],
);
}
query.filters.items = query.filters.items.filter(
(item) => !isEqual(item.key?.key, filter.attributeKey.key),
);

View File

@@ -132,20 +132,11 @@ function UplotPanelWrapper({
[selectedGraph, widget?.panelTypes, widget?.stackedBarChart],
);
const chartData = useMemo(
() =>
getUPlotChartData(
queryResponse?.data?.payload,
widget.fillSpans,
stackedBarChart,
hiddenGraph,
),
[
queryResponse?.data?.payload,
widget.fillSpans,
stackedBarChart,
hiddenGraph,
],
const chartData = getUPlotChartData(
queryResponse?.data?.payload,
widget.fillSpans,
stackedBarChart,
hiddenGraph,
);
useEffect(() => {
@@ -302,7 +293,7 @@ function UplotPanelWrapper({
)}
{isFullViewMode && setGraphVisibility && !stackedBarChart && (
<GraphManager
data={chartData}
data={getUPlotChartData(queryResponse?.data?.payload, widget.fillSpans)}
name={widget.id}
options={options}
yAxisUnit={widget.yAxisUnit}

View File

@@ -1,6 +1,8 @@
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import { AxiosError, AxiosResponse } from 'axios';
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
import { ErrorResponse } from 'react-router-dom-v5-compat';
import { SuccessResponse } from 'types/api';
import { QueryKeyValueSuggestionsResponseProps } from 'types/api/querySuggestions/types';
export const useGetQueryKeyValueSuggestions = ({
@@ -9,15 +11,13 @@ export const useGetQueryKeyValueSuggestions = ({
searchText,
signalSource,
metricName,
options,
}: {
key: string;
signal: 'traces' | 'logs' | 'metrics';
searchText?: string;
signalSource?: 'meter' | '';
options?: UseQueryOptions<
AxiosResponse<QueryKeyValueSuggestionsResponseProps>,
AxiosError
SuccessResponse<QueryKeyValueSuggestionsResponseProps> | ErrorResponse
>;
metricName?: string;
}): UseQueryResult<
@@ -41,5 +41,4 @@ export const useGetQueryKeyValueSuggestions = ({
signalSource: signalSource as 'meter' | '',
metricName: metricName || '',
}),
...options,
});

View File

@@ -1,6 +1,6 @@
import { themeColors } from 'constants/theme';
import getLabelName from 'lib/getLabelName';
import { isUndefined } from 'lodash-es';
import { cloneDeep, isUndefined } from 'lodash-es';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { QueryData } from 'types/api/widgets/getQuery';
@@ -8,7 +8,7 @@ import { normalizePlotValue } from './dataUtils';
import { generateColor } from './generateColor';
function getXAxisTimestamps(seriesList: QueryData[]): number[] {
const timestamps = new Set<number>();
const timestamps = new Set();
seriesList.forEach((series: { values?: [number, string][] }) => {
if (series?.values) {
@@ -18,71 +18,54 @@ function getXAxisTimestamps(seriesList: QueryData[]): number[] {
}
});
const timestampsArr = Array.from(timestamps);
timestampsArr.sort((a, b) => a - b);
return timestampsArr;
const timestampsArr: number[] | unknown[] = Array.from(timestamps) || [];
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return timestampsArr.sort((a, b) => a - b);
}
// eslint-disable-next-line sonarjs/cognitive-complexity
function fillMissingXAxisTimestamps(
timestampArr: number[],
data: Array<{ values?: [number, string][] }>,
): (number | null)[][] {
function fillMissingXAxisTimestamps(timestampArr: number[], data: any[]): any {
// Generate a set of all timestamps in the range
const allTimestampsSet = new Set(timestampArr);
const result: (number | null)[][] = [];
const processedData = cloneDeep(data);
// Process each series entry
for (let i = 0; i < data.length; i++) {
const entry = data[i];
if (!entry?.values) {
result.push([]);
} else {
// Build Set of existing timestamps directly (avoid intermediate array)
const existingTimestamps = new Set<number>();
const valuesMap = new Map<number, number | null>();
// Fill missing timestamps with null values
processedData.forEach((entry: { values: (number | null)[][] }) => {
const existingTimestamps = new Set(
(entry?.values ?? []).map((value) => value[0]),
);
for (let j = 0; j < entry.values.length; j++) {
const [timestamp, value] = entry.values[j];
existingTimestamps.add(timestamp);
valuesMap.set(timestamp, normalizePlotValue(value));
}
const missingTimestamps = Array.from(allTimestampsSet).filter(
(timestamp) => !existingTimestamps.has(timestamp),
);
// Find missing timestamps by iterating Set directly (avoid Array.from + filter)
const missingTimestamps: number[] = [];
const allTimestampsArray = Array.from(allTimestampsSet);
for (let k = 0; k < allTimestampsArray.length; k++) {
const timestamp = allTimestampsArray[k];
if (!existingTimestamps.has(timestamp)) {
missingTimestamps.push(timestamp);
}
}
missingTimestamps.forEach((timestamp) => {
const value = null;
// Add missing timestamps to map
for (let j = 0; j < missingTimestamps.length; j++) {
valuesMap.set(missingTimestamps[j], null);
}
entry?.values?.push([timestamp, value]);
});
// Build sorted array of values
const sortedTimestamps = Array.from(valuesMap.keys()).sort((a, b) => a - b);
const yValues = sortedTimestamps.map((timestamp) => {
const value = valuesMap.get(timestamp);
return value !== undefined ? value : null;
});
result.push(yValues);
}
}
entry?.values?.forEach((v) => {
// eslint-disable-next-line no-param-reassign
v[1] = normalizePlotValue(v[1]);
});
return result;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
entry?.values?.sort((a, b) => a[0] - b[0]);
});
return processedData.map((entry: { values: [number, string][] }) =>
entry?.values?.map((value) => value[1]),
);
}
function getStackedSeries(val: (number | null)[][]): (number | null)[][] {
const series = val ? val.map((row: (number | null)[]) => [...row]) : [];
function getStackedSeries(val: any): any {
const series = cloneDeep(val) || [];
for (let i = series.length - 2; i >= 0; i--) {
for (let j = 0; j < series[i].length; j++) {
series[i][j] = (series[i][j] || 0) + (series[i + 1][j] || 0);
series[i][j] += series[i + 1][j];
}
}
@@ -127,7 +110,6 @@ const processAnomalyDetectionData = (
queryIndex < anomalyDetectionData.length;
queryIndex++
) {
const queryData = anomalyDetectionData[queryIndex];
const {
series,
predictedSeries,
@@ -135,7 +117,7 @@ const processAnomalyDetectionData = (
lowerBoundSeries,
queryName,
legend,
} = queryData;
} = anomalyDetectionData[queryIndex];
for (let index = 0; index < series?.length; index++) {
const label = getLabelName(
@@ -147,30 +129,14 @@ const processAnomalyDetectionData = (
const objKey =
anomalyDetectionData.length > 1 ? `${queryName}-${label}` : label;
// Single iteration instead of 5 separate map operations
const { values: seriesValues } = series[index];
const { values: predictedValues } = predictedSeries[index];
const { values: upperBoundValues } = upperBoundSeries[index];
const { values: lowerBoundValues } = lowerBoundSeries[index];
// eslint-disable-next-line prefer-destructuring
const length = seriesValues.length;
const timestamps: number[] = new Array(length);
const values: number[] = new Array(length);
const predicted: number[] = new Array(length);
const upperBound: number[] = new Array(length);
const lowerBound: number[] = new Array(length);
for (let i = 0; i < length; i++) {
timestamps[i] = seriesValues[i].timestamp / 1000;
values[i] = seriesValues[i].value;
predicted[i] = predictedValues[i].value;
upperBound[i] = upperBoundValues[i].value;
lowerBound[i] = lowerBoundValues[i].value;
}
processedData[objKey] = {
data: [timestamps, values, predicted, upperBound, lowerBound],
data: [
series[index].values.map((v: { timestamp: number }) => v.timestamp / 1000),
series[index].values.map((v: { value: number }) => v.value),
predictedSeries[index].values.map((v: { value: number }) => v.value),
upperBoundSeries[index].values.map((v: { value: number }) => v.value),
lowerBoundSeries[index].values.map((v: { value: number }) => v.value),
],
color: generateColor(
objKey,
isDarkMode ? themeColors.chartcolors : themeColors.lightModeColor,
@@ -186,7 +152,14 @@ const processAnomalyDetectionData = (
export const getUplotChartDataForAnomalyDetection = (
apiResponse: MetricRangePayloadProps,
isDarkMode: boolean,
): Record<string, { [x: string]: any; data: number[][]; color: string }> => {
): Record<
string,
{
[x: string]: any;
data: number[][];
color: string;
}
> => {
const anomalyDetectionData = apiResponse?.data?.newResult?.data?.result;
return processAnomalyDetectionData(anomalyDetectionData, isDarkMode);
};

16
go.mod
View File

@@ -74,12 +74,12 @@ require (
go.opentelemetry.io/otel/trace v1.38.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.46.0
golang.org/x/crypto v0.41.0
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
golang.org/x/net v0.47.0
golang.org/x/net v0.43.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.19.0
golang.org/x/text v0.32.0
golang.org/x/sync v0.17.0
golang.org/x/text v0.28.0
google.golang.org/protobuf v1.36.9
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
@@ -103,7 +103,6 @@ require (
go.opentelemetry.io/collector/config/configretry v1.34.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/tools/godoc v0.1.0-deprecated // indirect
modernc.org/libc v1.66.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
@@ -224,7 +223,6 @@ require (
github.com/oklog/run v1.1.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/oklog/ulid/v2 v2.1.1 // indirect
github.com/open-feature/go-sdk v1.17.0
github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.128.0 // indirect
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.128.0 // indirect
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.128.0 // indirect
@@ -338,10 +336,10 @@ require (
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/mock v0.6.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.30.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.39.0 // indirect
golang.org/x/tools v0.36.0 // indirect
gonum.org/v1/gonum v0.16.0 // indirect
google.golang.org/api v0.236.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect

36
go.sum
View File

@@ -762,8 +762,6 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/open-feature/go-sdk v1.17.0 h1:/OUBBw5d9D61JaNZZxb2Nnr5/EJrEpjtKCTY3rspJQk=
github.com/open-feature/go-sdk v1.17.0/go.mod h1:lPxPSu1UnZ4E3dCxZi5gV3et2ACi8O8P+zsTGVsDZUw=
github.com/open-telemetry/opamp-go v0.19.0 h1:8LvQKDwqi+BU3Yy159SU31e2XB0vgnk+PN45pnKilPs=
github.com/open-telemetry/opamp-go v0.19.0/go.mod h1:9/1G6T5dnJz4cJtoYSr6AX18kHdOxnxxETJPZSHyEUg=
github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.128.0 h1:T5IE0l1qcIg6dkHui4hHe+qj3VzuMwpnhrUyubyCwO0=
@@ -1284,8 +1282,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1323,8 +1321,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1373,8 +1371,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1409,8 +1407,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1497,12 +1495,12 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1513,8 +1511,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1577,10 +1575,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk=
golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -1,31 +0,0 @@
package signozapiserver
import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/gorilla/mux"
)
func (provider *provider) addFlaggerRoutes(router *mux.Router) error {
if err := router.Handle("/api/v2/features", handler.New(provider.authZ.ViewAccess(provider.flaggerHandler.GetFeatures), handler.OpenAPIDef{
ID: "GetFeatures",
Tags: []string{"features"},
Summary: "Get features",
Description: "This endpoint returns the supported features and their details",
Request: nil,
RequestContentType: "",
Response: make([]*featuretypes.GettableFeature, 0),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
return nil
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/SigNoz/signoz/pkg/apiserver"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/http/middleware"
@@ -33,7 +32,6 @@ type provider struct {
preferenceHandler preference.Handler
globalHandler global.Handler
promoteHandler promote.Handler
flaggerHandler flagger.Handler
}
func NewFactory(
@@ -46,10 +44,9 @@ func NewFactory(
preferenceHandler preference.Handler,
globalHandler global.Handler,
promoteHandler promote.Handler,
flaggerHandler flagger.Handler,
) factory.ProviderFactory[apiserver.APIServer, apiserver.Config] {
return factory.NewProviderFactory(factory.MustNewName("signoz"), func(ctx context.Context, providerSettings factory.ProviderSettings, config apiserver.Config) (apiserver.APIServer, error) {
return newProvider(ctx, providerSettings, config, orgGetter, authz, orgHandler, userHandler, sessionHandler, authDomainHandler, preferenceHandler, globalHandler, promoteHandler, flaggerHandler)
return newProvider(ctx, providerSettings, config, orgGetter, authz, orgHandler, userHandler, sessionHandler, authDomainHandler, preferenceHandler, globalHandler, promoteHandler)
})
}
@@ -66,7 +63,6 @@ func newProvider(
preferenceHandler preference.Handler,
globalHandler global.Handler,
promoteHandler promote.Handler,
flaggerHandler flagger.Handler,
) (apiserver.APIServer, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/apiserver/signozapiserver")
router := mux.NewRouter().UseEncodedPath()
@@ -82,7 +78,6 @@ func newProvider(
preferenceHandler: preferenceHandler,
globalHandler: globalHandler,
promoteHandler: promoteHandler,
flaggerHandler: flaggerHandler,
}
provider.authZ = middleware.NewAuthZ(settings.Logger(), orgGetter, authz)
@@ -127,10 +122,6 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
return err
}
if err := provider.addFlaggerRoutes(router); err != nil {
return err
}
return nil
}

View File

@@ -1,32 +0,0 @@
package flagger
import "github.com/SigNoz/signoz/pkg/factory"
type Config struct {
Config ConfigProvider `mapstructure:"config"`
}
type ConfigProvider struct {
Boolean map[string]bool `mapstructure:"boolean"`
String map[string]string `mapstructure:"string"`
Float map[string]float64 `mapstructure:"float"`
Integer map[string]int64 `mapstructure:"integer"`
Object map[string]any `mapstructure:"object"`
}
func NewConfigFactory() factory.ConfigFactory {
return factory.NewConfigFactory(
factory.MustNewName("flagger"), newConfig,
)
}
// newConfig creates a new config with the default values.
func newConfig() factory.Config {
return &Config{
Config: ConfigProvider{},
}
}
func (c Config) Validate() error {
return nil
}

View File

@@ -1,320 +0,0 @@
package configflagger
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/open-feature/go-sdk/openfeature"
)
type provider struct {
config flagger.Config
settings factory.ScopedProviderSettings
// This is the default registry that will be containing all the supported features along with there all possible variants
registry featuretypes.Registry
// These are the feature variants that are configured in the config file and will be used as overrides
featureVariants map[featuretypes.Name]*featuretypes.FeatureVariant
}
func NewFactory(registry featuretypes.Registry) factory.ProviderFactory[flagger.FlaggerProvider, flagger.Config] {
return factory.NewProviderFactory(factory.MustNewName("config"), func(ctx context.Context, ps factory.ProviderSettings, c flagger.Config) (flagger.FlaggerProvider, error) {
return New(ctx, ps, c, registry)
})
}
func New(ctx context.Context, ps factory.ProviderSettings, c flagger.Config, registry featuretypes.Registry) (flagger.FlaggerProvider, error) {
settings := factory.NewScopedProviderSettings(ps, "github.com/SigNoz/signoz/pkg/flagger/configflagger")
featureVariants := make(map[featuretypes.Name]*featuretypes.FeatureVariant)
for name, value := range c.Config.Boolean {
feature, _, err := registry.GetByString(name)
if err != nil {
return nil, err
}
variant, err := featuretypes.VariantByValue(feature, value)
if err != nil {
return nil, err
}
featureVariants[feature.Name] = variant
}
for name, value := range c.Config.String {
feature, _, err := registry.GetByString(name)
if err != nil {
return nil, err
}
variant, err := featuretypes.VariantByValue(feature, value)
if err != nil {
return nil, err
}
featureVariants[feature.Name] = variant
}
for name, value := range c.Config.Float {
feature, _, err := registry.GetByString(name)
if err != nil {
return nil, err
}
variant, err := featuretypes.VariantByValue(feature, value)
if err != nil {
return nil, err
}
featureVariants[feature.Name] = variant
}
for name, value := range c.Config.Integer {
feature, _, err := registry.GetByString(name)
if err != nil {
return nil, err
}
variant, err := featuretypes.VariantByValue(feature, value)
if err != nil {
return nil, err
}
featureVariants[feature.Name] = variant
}
for name, value := range c.Config.Object {
feature, _, err := registry.GetByString(name)
if err != nil {
return nil, err
}
variant, err := featuretypes.VariantByValue(feature, value)
if err != nil {
return nil, err
}
featureVariants[feature.Name] = variant
}
return &provider{
config: c,
settings: settings,
registry: registry,
featureVariants: featureVariants,
}, nil
}
func (provider *provider) Metadata() openfeature.Metadata {
return openfeature.Metadata{
Name: "config",
}
}
func (p *provider) BooleanEvaluation(ctx context.Context, flag string, defaultValue bool, evalCtx openfeature.FlattenedContext) openfeature.BoolResolutionDetail {
// check if the feature is present in the default registry
feature, detail, err := p.registry.GetByString(flag)
if err != nil {
return openfeature.BoolResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
// get the default value from the feature from default registry
value, detail, err := featuretypes.VariantValue[bool](feature, feature.DefaultVariant)
if err != nil {
return openfeature.BoolResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
// check if the feature is present in the featureVariants map
variant, ok := p.featureVariants[feature.Name]
if ok {
// return early as we have found the value in the featureVariants map
return openfeature.BoolResolutionDetail{
Value: variant.Value.(bool),
ProviderResolutionDetail: detail,
}
}
// return the value from the default registry we found earlier
return openfeature.BoolResolutionDetail{
Value: value,
ProviderResolutionDetail: detail,
}
}
func (p *provider) FloatEvaluation(ctx context.Context, flag string, defaultValue float64, evalCtx openfeature.FlattenedContext) openfeature.FloatResolutionDetail {
// check if the feature is present in the default registry
feature, detail, err := p.registry.GetByString(flag)
if err != nil {
return openfeature.FloatResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
// get the default value from the feature from default registry
value, detail, err := featuretypes.VariantValue[float64](feature, feature.DefaultVariant)
if err != nil {
return openfeature.FloatResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
// check if the feature is present in the featureVariants map
variant, ok := p.featureVariants[feature.Name]
if ok {
// return early as we have found the value in the featureVariants map
return openfeature.FloatResolutionDetail{
Value: variant.Value.(float64),
ProviderResolutionDetail: detail,
}
}
// return the value from the default registry we found earlier
return openfeature.FloatResolutionDetail{
Value: value,
ProviderResolutionDetail: detail,
}
}
func (p *provider) StringEvaluation(ctx context.Context, flag string, defaultValue string, evalCtx openfeature.FlattenedContext) openfeature.StringResolutionDetail {
// check if the feature is present in the default registry
feature, detail, err := p.registry.GetByString(flag)
if err != nil {
return openfeature.StringResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
// get the default value from the feature from default registry
value, detail, err := featuretypes.VariantValue[string](feature, feature.DefaultVariant)
if err != nil {
return openfeature.StringResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
// check if the feature is present in the featureVariants map
variant, ok := p.featureVariants[feature.Name]
if ok {
// return early as we have found the value in the featureVariants map
return openfeature.StringResolutionDetail{
Value: variant.Value.(string),
ProviderResolutionDetail: detail,
}
}
// return the value from the default registry we found earlier
return openfeature.StringResolutionDetail{
Value: value,
ProviderResolutionDetail: detail,
}
}
func (p *provider) IntEvaluation(ctx context.Context, flag string, defaultValue int64, evalCtx openfeature.FlattenedContext) openfeature.IntResolutionDetail {
// check if the feature is present in the default registry
feature, detail, err := p.registry.GetByString(flag)
if err != nil {
return openfeature.IntResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
// get the default value from the feature from default registry
value, detail, err := featuretypes.VariantValue[int64](feature, feature.DefaultVariant)
if err != nil {
return openfeature.IntResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
// check if the feature is present in the featureVariants map
variant, ok := p.featureVariants[feature.Name]
if ok {
// return early as we have found the value in the featureVariants map
return openfeature.IntResolutionDetail{
Value: variant.Value.(int64),
ProviderResolutionDetail: detail,
}
}
// return the value from the default registry we found earlier
return openfeature.IntResolutionDetail{
Value: value,
ProviderResolutionDetail: detail,
}
}
func (p *provider) ObjectEvaluation(ctx context.Context, flag string, defaultValue any, evalCtx openfeature.FlattenedContext) openfeature.InterfaceResolutionDetail {
// check if the feature is present in the default registry
feature, detail, err := p.registry.GetByString(flag)
if err != nil {
return openfeature.InterfaceResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
// get the default value from the feature from default registry
value, detail, err := featuretypes.VariantValue[any](feature, feature.DefaultVariant)
if err != nil {
return openfeature.InterfaceResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
// check if the feature is present in the featureVariants map
variant, ok := p.featureVariants[feature.Name]
if ok {
// return early as we have found the value in the featureVariants map
return openfeature.InterfaceResolutionDetail{
Value: variant.Value,
ProviderResolutionDetail: detail,
}
}
// return the value from the default registry we found earlier
return openfeature.InterfaceResolutionDetail{
Value: value,
ProviderResolutionDetail: detail,
}
}
func (provider *provider) Hooks() []openfeature.Hook {
return []openfeature.Hook{}
}
func (p *provider) List(ctx context.Context) ([]*featuretypes.GettableFeature, error) {
result := make([]*featuretypes.GettableFeature, 0, len(p.featureVariants))
for featureName, variant := range p.featureVariants {
feature, _, err := p.registry.Get(featureName)
if err != nil {
return nil, err
}
result = append(result, &featuretypes.GettableFeature{
Name: feature.Name.String(),
Kind: feature.Kind.StringValue(),
Stage: feature.Stage.StringValue(),
Description: feature.Description,
DefaultVariant: feature.DefaultVariant.String(),
Variants: nil,
ResolvedValue: variant.Value,
})
}
return result, nil
}

View File

@@ -1,282 +0,0 @@
package flagger
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/open-feature/go-sdk/openfeature"
)
// Any feature flag provider has to implement this interface.
type FlaggerProvider interface {
openfeature.FeatureProvider
// List returns all the feature flags
List(ctx context.Context) ([]*featuretypes.GettableFeature, error)
}
// This is the consumer facing interface for the Flagger service.
type Flagger interface {
Boolean(ctx context.Context, flag string, evalCtx featuretypes.FlaggerEvaluationContext) (bool, error)
String(ctx context.Context, flag string, evalCtx featuretypes.FlaggerEvaluationContext) (string, error)
Float(ctx context.Context, flag string, evalCtx featuretypes.FlaggerEvaluationContext) (float64, error)
Int(ctx context.Context, flag string, evalCtx featuretypes.FlaggerEvaluationContext) (int64, error)
Object(ctx context.Context, flag string, evalCtx featuretypes.FlaggerEvaluationContext) (any, error)
List(ctx context.Context, evalCtx featuretypes.FlaggerEvaluationContext) ([]*featuretypes.GettableFeature, error)
}
// This is the concrete implementation of the Flagger interface.
type flagger struct {
registry featuretypes.Registry
settings factory.ScopedProviderSettings
providers map[string]FlaggerProvider
clients map[string]*openfeature.Client
}
func New(ctx context.Context, ps factory.ProviderSettings, config Config, registry featuretypes.Registry, factories ...factory.ProviderFactory[FlaggerProvider, Config]) (Flagger, error) {
settings := factory.NewScopedProviderSettings(ps, "github.com/SigNoz/signoz/pkg/flagger")
providers := make(map[string]FlaggerProvider)
clients := make(map[string]*openfeature.Client)
for _, factory := range factories {
provider, err := factory.New(ctx, ps, config)
if err != nil {
return nil, err
}
providers[provider.Metadata().Name] = provider
openfeatureClient := openfeature.NewClient(provider.Metadata().Name)
if err := openfeature.SetNamedProviderAndWait(provider.Metadata().Name, provider); err != nil {
return nil, err
}
clients[provider.Metadata().Name] = openfeatureClient
}
return &flagger{
registry: registry,
settings: settings,
providers: providers,
clients: clients,
}, nil
}
func (f *flagger) Boolean(ctx context.Context, flag string, evalCtx featuretypes.FlaggerEvaluationContext) (bool, error) {
// check if the feature is present in the default registry
feature, _, err := f.registry.GetByString(flag)
if err != nil {
f.settings.Logger().ErrorContext(ctx, "failed to get feature from default registry", "error", err, "flag", flag)
return false, err
}
// get the default value from the feature from default registry
defaultValue, _, err := featuretypes.VariantValue[bool](feature, feature.DefaultVariant)
if err != nil {
// something which should never happen
f.settings.Logger().ErrorContext(ctx, "failed to get default value from feature", "error", err, "flag", flag)
return false, err
}
// * this logic can be optimised based on priority of the clients and short circuiting
// now ask all the available clients for the value
for _, client := range f.clients {
value, err := client.BooleanValue(ctx, flag, defaultValue, evalCtx.Ctx())
if err != nil {
f.settings.Logger().ErrorContext(ctx, "failed to get value from client", "error", err, "flag", flag, "client", client.Metadata().Name)
continue
}
if value != defaultValue {
return value, nil
}
}
return defaultValue, nil
}
func (f *flagger) String(ctx context.Context, flag string, evalCtx featuretypes.FlaggerEvaluationContext) (string, error) {
// check if the feature is present in the default registry
feature, _, err := f.registry.GetByString(flag)
if err != nil {
f.settings.Logger().ErrorContext(ctx, "failed to get feature from default registry", "error", err, "flag", flag)
return "", err
}
// get the default value from the feature from default registry
defaultValue, _, err := featuretypes.VariantValue[string](feature, feature.DefaultVariant)
if err != nil {
// something which should never happen
f.settings.Logger().ErrorContext(ctx, "failed to get default value from feature", "error", err, "flag", flag)
return "", err
}
// * this logic can be optimised based on priority of the clients and short circuiting
// now ask all the available clients for the value
for _, client := range f.clients {
value, err := client.StringValue(ctx, flag, defaultValue, evalCtx.Ctx())
if err != nil {
f.settings.Logger().WarnContext(ctx, "failed to get value from client", "error", err, "flag", flag, "client", client.Metadata().Name)
continue
}
if value != defaultValue {
return value, nil
}
}
return defaultValue, nil
}
func (f *flagger) Float(ctx context.Context, flag string, evalCtx featuretypes.FlaggerEvaluationContext) (float64, error) {
// check if the feature is present in the default registry
feature, _, err := f.registry.GetByString(flag)
if err != nil {
f.settings.Logger().ErrorContext(ctx, "failed to get feature from default registry", "error", err, "flag", flag)
return 0, err
}
// get the default value from the feature from default registry
defaultValue, _, err := featuretypes.VariantValue[float64](feature, feature.DefaultVariant)
if err != nil {
// something which should never happen
f.settings.Logger().ErrorContext(ctx, "failed to get default value from feature", "error", err, "flag", flag)
return 0, err
}
// * this logic can be optimised based on priority of the clients and short circuiting
// now ask all the available clients for the value
for _, client := range f.clients {
value, err := client.FloatValue(ctx, flag, defaultValue, evalCtx.Ctx())
if err != nil {
f.settings.Logger().WarnContext(ctx, "failed to get value from client", "error", err, "flag", flag, "client", client.Metadata().Name)
continue
}
if value != defaultValue {
return value, nil
}
}
return defaultValue, nil
}
func (f *flagger) Int(ctx context.Context, flag string, evalCtx featuretypes.FlaggerEvaluationContext) (int64, error) {
// check if the feature is present in the default registry
feature, _, err := f.registry.GetByString(flag)
if err != nil {
f.settings.Logger().ErrorContext(ctx, "failed to get feature from default registry", "error", err, "flag", flag)
return 0, err
}
// get the default value from the feature from default registry
defaultValue, _, err := featuretypes.VariantValue[int64](feature, feature.DefaultVariant)
if err != nil {
// something which should never happen
f.settings.Logger().ErrorContext(ctx, "failed to get default value from feature", "error", err, "flag", flag)
return 0, err
}
// * this logic can be optimised based on priority of the clients and short circuiting
// now ask all the available clients for the value
for _, client := range f.clients {
value, err := client.IntValue(ctx, flag, defaultValue, evalCtx.Ctx())
if err != nil {
f.settings.Logger().WarnContext(ctx, "failed to get value from client", "error", err, "flag", flag, "client", client.Metadata().Name)
continue
}
if value != defaultValue {
return value, nil
}
}
return defaultValue, nil
}
func (f *flagger) Object(ctx context.Context, flag string, evalCtx featuretypes.FlaggerEvaluationContext) (any, error) {
// check if the feature is present in the default registry
feature, _, err := f.registry.GetByString(flag)
if err != nil {
f.settings.Logger().ErrorContext(ctx, "failed to get feature from default registry", "error", err, "flag", flag)
return nil, err
}
// get the default value from the feature from default registry
defaultValue, _, err := featuretypes.VariantValue[any](feature, feature.DefaultVariant)
if err != nil {
// something which should never happen
f.settings.Logger().ErrorContext(ctx, "failed to get default value from feature", "error", err, "flag", flag)
return nil, err
}
// * this logic can be optimised based on priority of the clients and short circuiting
// now ask all the available clients for the value
for _, client := range f.clients {
value, err := client.ObjectValue(ctx, flag, defaultValue, evalCtx.Ctx())
if err != nil {
f.settings.Logger().WarnContext(ctx, "failed to get value from client", "error", err, "flag", flag, "client", client.Metadata().Name)
continue
}
// ! for object we do not compare with the default value for now, we will figure this out better in future coming releases
// if value != defaultValue {
// return value, nil
// }
return value, nil
}
return defaultValue, nil
}
func (f *flagger) List(ctx context.Context, evalCtx featuretypes.FlaggerEvaluationContext) ([]*featuretypes.GettableFeature, error) {
// get all the feature from the default registry
allFeatures := f.registry.List()
// make a map of name of feature -> the dict we want to create from all features
featureMap := make(map[string]*featuretypes.GettableFeature, len(allFeatures))
for _, feature := range allFeatures {
variants := make(map[string]any, len(feature.Variants))
for name, value := range feature.Variants {
variants[name.String()] = value.Value
}
featureMap[feature.Name.String()] = &featuretypes.GettableFeature{
Name: feature.Name.String(),
Kind: feature.Kind.StringValue(),
Stage: feature.Stage.StringValue(),
Description: feature.Description,
DefaultVariant: feature.DefaultVariant.String(),
Variants: variants,
ResolvedValue: feature.Variants[feature.DefaultVariant].Value,
}
}
// now call each provider and fix the value in feature map
for _, provider := range f.providers {
pFeatures, err := provider.List(ctx)
if err != nil {
f.settings.Logger().WarnContext(ctx, "failed to get features from provider", "error", err, "provider", provider.Metadata().Name)
continue
}
// merge
for _, pFeature := range pFeatures {
if existing, ok := featureMap[pFeature.Name]; ok {
existing.ResolvedValue = pFeature.ResolvedValue
}
}
}
result := make([]*featuretypes.GettableFeature, 0, len(allFeatures))
for _, f := range featureMap {
result = append(result, f)
}
return result, nil
}

View File

@@ -1,53 +0,0 @@
package flagger
import (
"context"
"net/http"
"time"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Handler interface {
GetFeatures(http.ResponseWriter, *http.Request)
}
type handler struct {
flagger Flagger
}
func NewHandler(flagger Flagger) Handler {
return &handler{
flagger: flagger,
}
}
func (handler *handler) GetFeatures(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID, err := valuer.NewUUID(claims.OrgID)
if err != nil {
render.Error(rw, err)
return
}
evalCtx := featuretypes.NewFlaggerEvaluationContext(orgID)
features, err := handler.flagger.List(ctx, evalCtx)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, features)
}

View File

@@ -1,12 +0,0 @@
package flagger
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
func MustNewRegistry() featuretypes.Registry {
registry, err := featuretypes.NewRegistry()
if err != nil {
panic(err)
}
return registry
}

View File

@@ -17,7 +17,6 @@ import (
"github.com/SigNoz/signoz/pkg/emailing"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/gateway"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/instrumentation"
@@ -106,9 +105,6 @@ type Config struct {
// MetricsExplorer config
MetricsExplorer metricsexplorer.Config `mapstructure:"metricsexplorer"`
// Flagger config
Flagger flagger.Config `mapstructure:"flagger"`
}
// DeprecatedFlags are the flags that are deprecated and scheduled for removal.
@@ -170,7 +166,6 @@ func NewConfig(ctx context.Context, logger *slog.Logger, resolverConfig config.R
gateway.NewConfigFactory(),
tokenizer.NewConfigFactory(),
metricsexplorer.NewConfigFactory(),
flagger.NewConfigFactory(),
}
conf, err := config.New(ctx, resolverConfig, configFactories)

View File

@@ -2,7 +2,6 @@ package signoz
import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/global/signozglobal"
"github.com/SigNoz/signoz/pkg/licensing"
@@ -38,10 +37,9 @@ type Handlers struct {
Services services.Handler
MetricsExplorer metricsexplorer.Handler
Global global.Handler
FlaggerHandler flagger.Handler
}
func NewHandlers(modules Modules, providerSettings factory.ProviderSettings, querier querier.Querier, licensing licensing.Licensing, global global.Global, flaggerService flagger.Flagger) Handlers {
func NewHandlers(modules Modules, providerSettings factory.ProviderSettings, querier querier.Querier, licensing licensing.Licensing, global global.Global) Handlers {
return Handlers{
SavedView: implsavedview.NewHandler(modules.SavedView),
Apdex: implapdex.NewHandler(modules.Apdex),
@@ -53,6 +51,5 @@ func NewHandlers(modules Modules, providerSettings factory.ProviderSettings, que
MetricsExplorer: implmetricsexplorer.NewHandler(modules.MetricsExplorer),
SpanPercentile: implspanpercentile.NewHandler(modules.SpanPercentile),
Global: signozglobal.NewHandler(global),
FlaggerHandler: flagger.NewHandler(flaggerService),
}
}

View File

@@ -40,7 +40,7 @@ func TestNewHandlers(t *testing.T) {
require.NoError(t, err)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{})
handlers := NewHandlers(modules, providerSettings, nil, nil, nil, nil)
handlers := NewHandlers(modules, providerSettings, nil, nil, nil)
reflectVal := reflect.ValueOf(handlers)
for i := 0; i < reflectVal.NumField(); i++ {

View File

@@ -8,7 +8,6 @@ import (
"github.com/SigNoz/signoz/pkg/apiserver"
"github.com/SigNoz/signoz/pkg/apiserver/signozapiserver"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/instrumentation"
@@ -41,7 +40,6 @@ func NewOpenAPI(ctx context.Context, instrumentation instrumentation.Instrumenta
struct{ preference.Handler }{},
struct{ global.Handler }{},
struct{ promote.Handler }{},
struct{ flagger.Handler }{},
).New(ctx, instrumentation.ToProviderSettings(), apiserver.Config{})
if err != nil {
return nil, err

View File

@@ -18,8 +18,6 @@ import (
"github.com/SigNoz/signoz/pkg/emailing/noopemailing"
"github.com/SigNoz/signoz/pkg/emailing/smtpemailing"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/flagger/configflagger"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/global/signozglobal"
"github.com/SigNoz/signoz/pkg/modules/authdomain/implauthdomain"
@@ -56,7 +54,6 @@ import (
"github.com/SigNoz/signoz/pkg/tokenizer/opaquetokenizer"
"github.com/SigNoz/signoz/pkg/tokenizer/tokenizerstore/sqltokenizerstore"
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/web"
"github.com/SigNoz/signoz/pkg/web/noopweb"
@@ -239,7 +236,6 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
implpreference.NewHandler(modules.Preference),
signozglobal.NewHandler(global),
implpromote.NewHandler(modules.Promote),
handlers.FlaggerHandler,
),
)
}
@@ -257,9 +253,3 @@ func NewGlobalProviderFactories() factory.NamedMap[factory.ProviderFactory[globa
signozglobal.NewFactory(),
)
}
func NewFlaggerProviderFactories(registry featuretypes.Registry) factory.NamedMap[factory.ProviderFactory[flagger.FlaggerProvider, flagger.Config]] {
return factory.MustNewNamedMap(
configflagger.NewFactory(registry),
)
}

View File

@@ -14,7 +14,6 @@ import (
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/emailing"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/instrumentation"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/organization"
@@ -67,7 +66,6 @@ type SigNoz struct {
Modules Modules
Handlers Handlers
QueryParser queryparser.QueryParser
Flagger flagger.Flagger
}
func New(
@@ -358,25 +356,11 @@ func New(
return nil, err
}
// Initialize flagger from the available flagger provider factories
flaggerRegistry := flagger.MustNewRegistry()
flaggerProviderFactories := NewFlaggerProviderFactories(flaggerRegistry)
flagger, err := flagger.New(
ctx,
providerSettings,
config.Flagger,
flaggerRegistry,
flaggerProviderFactories.GetInOrder()...,
)
if err != nil {
return nil, err
}
// Initialize all modules
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config)
// Initialize all handlers for the modules
handlers := NewHandlers(modules, providerSettings, querier, licensing, global, flagger)
handlers := NewHandlers(modules, providerSettings, querier, licensing, global)
// Initialize the API server
apiserver, err := factory.NewProviderFromNamedMap(
@@ -450,6 +434,5 @@ func New(
Modules: modules,
Handlers: handlers,
QueryParser: queryParser,
Flagger: flagger,
}, nil
}

View File

@@ -1,23 +0,0 @@
package featuretypes
import (
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/open-feature/go-sdk/openfeature"
)
// A concrete wrapper around the openfeature.EvaluationContext
type FlaggerEvaluationContext struct {
ctx openfeature.EvaluationContext
}
// Creates a new FlaggerEvaluationContext with given details
func NewFlaggerEvaluationContext(orgID valuer.UUID) FlaggerEvaluationContext {
ctx := openfeature.NewTargetlessEvaluationContext(map[string]any{
"orgId": orgID.String(),
})
return FlaggerEvaluationContext{ctx: ctx}
}
func (ctx FlaggerEvaluationContext) Ctx() openfeature.EvaluationContext {
return ctx.ctx
}

View File

@@ -1,113 +0,0 @@
package featuretypes
import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/open-feature/go-sdk/openfeature"
)
var (
ErrCodeFeatureVariantNotFound = errors.MustNewCode("feature_variant_not_found")
ErrCodeFeatureValueNotFound = errors.MustNewCode("feature_value_not_found")
ErrCodeFeatureVariantKindMismatch = errors.MustNewCode("feature_variant_kind_mismatch")
ErrCodeFeatureDefaultVariantNotFound = errors.MustNewCode("feature_default_variant_not_found")
ErrCodeFeatureNotFound = errors.MustNewCode("feature_not_found")
)
// A concrete type for a feature flag
type Feature struct {
// Name of the feature
Name Name `json:"name"`
// Kind of the feature
Kind Kind `json:"kind"`
// Stage of the feature
Stage Stage `json:"stage"`
// Description of the feature
Description string `json:"description"`
// DefaultVariant of the feature
DefaultVariant Name `json:"defaultVariant"`
// Variants of the feature
Variants map[Name]FeatureVariant `json:"variants"`
}
// A concrete type for a feature flag variant
type FeatureVariant struct {
// Name of the variant
Variant Name `json:"variant"`
// Value of the variant
Value any `json:"value"`
}
type GettableFeature struct {
Name string `json:"name"`
Kind string `json:"kind"`
Stage string `json:"stage"`
Description string `json:"description"`
DefaultVariant string `json:"defaultVariant"`
Variants map[string]any `json:"variants"`
ResolvedValue any `json:"resolvedValue"`
}
// This is the helper function to get the value of a variant of a feature
func VariantValue[T any](feature *Feature, variant Name) (t T, detail openfeature.ProviderResolutionDetail, err error) {
value, ok := feature.Variants[variant]
if !ok {
err = errors.Newf(errors.TypeInvalidInput, ErrCodeFeatureVariantNotFound, "variant %s not found for feature %s in variants %v", variant.String(), feature.Name.String(), feature.Variants)
detail = openfeature.ProviderResolutionDetail{
ResolutionError: openfeature.NewGeneralResolutionError(err.Error()),
Reason: openfeature.ErrorReason,
Variant: feature.DefaultVariant.String(),
}
return
}
t, ok = value.Value.(T)
if !ok {
err = errors.Newf(errors.TypeInvalidInput, ErrCodeFeatureVariantKindMismatch, "variant %s for feature %s has type %T, expected %T", variant.String(), feature.Name.String(), value.Value, t)
detail = openfeature.ProviderResolutionDetail{
ResolutionError: openfeature.NewTypeMismatchResolutionError(err.Error()),
Reason: openfeature.ErrorReason,
Variant: variant.String(),
}
return
}
detail = openfeature.ProviderResolutionDetail{
Reason: openfeature.StaticReason,
Variant: variant.String(),
}
return
}
// This is the helper function to get the variant by value for the given feature
func VariantByValue[T comparable](feature *Feature, value T) (featureVariant *FeatureVariant, err error) {
// technically this method should not be called for object kind
// but just for fallback
if feature.Kind == KindObject {
// return the default variant - just for fallback
// ? think more on this
return &FeatureVariant{Variant: feature.DefaultVariant, Value: value}, nil
}
for _, variant := range feature.Variants {
if variant.Value == value {
return &variant, nil
}
}
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeFeatureVariantNotFound, "no variant found for value %v for feature %s in variants %v", value, feature.Name.String(), feature.Variants)
}
func NewBooleanVariants() map[Name]FeatureVariant {
return map[Name]FeatureVariant{
MustNewName("disabled"): {
Variant: MustNewName("disabled"),
Value: false,
},
MustNewName("enabled"): {
Variant: MustNewName("enabled"),
Value: true,
},
}
}

View File

@@ -1,14 +0,0 @@
package featuretypes
import "github.com/SigNoz/signoz/pkg/valuer"
// A concrete type for a feature flag kind
type Kind struct{ valuer.String }
var (
KindBoolean = Kind{valuer.NewString("boolean")}
KindString = Kind{valuer.NewString("string")}
KindFloat = Kind{valuer.NewString("float")}
KindInt = Kind{valuer.NewString("int")}
KindObject = Kind{valuer.NewString("object")}
)

View File

@@ -1,37 +0,0 @@
package featuretypes
import (
"regexp"
"github.com/SigNoz/signoz/pkg/errors"
)
var nameRegex = regexp.MustCompile(`^[a-z][a-z0-9_]+$`)
// Name is a concrete type for a feature name.
// We make this abstract to avoid direct use of strings and enforce
// a consistent way to create and validate feature names.
type Name struct {
s string
}
func NewName(s string) (Name, error) {
if !nameRegex.MatchString(s) {
return Name{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid feature name: %s", s)
}
return Name{s: s}, nil
}
func MustNewName(s string) Name {
name, err := NewName(s)
if err != nil {
panic(err)
}
return name
}
func (n Name) String() string {
return n.s
}

View File

@@ -1,129 +0,0 @@
package featuretypes
import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/open-feature/go-sdk/openfeature"
)
// Consumer facing interface for the feature registry
type Registry interface {
// Returns the feature and the resolution detail for the given name
Get(name Name) (*Feature, openfeature.ProviderResolutionDetail, error)
// Returns the feature and the resolution detail for the given string name
GetByString(name string) (*Feature, openfeature.ProviderResolutionDetail, error)
// Returns all the features in the registry
List() []*Feature
}
// Concrete implementation of the Registry interface
type registry struct {
features map[Name]*Feature
}
// Validates and builds a new registry from a list of features
func NewRegistry(features ...*Feature) (Registry, error) {
registry := &registry{features: make(map[Name]*Feature)}
for _, feature := range features {
// Check if the name is unique
if _, ok := registry.features[feature.Name]; ok {
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "feature name %s already exists", feature.Name.String())
}
// Default variant should always be present
if _, ok := feature.Variants[feature.DefaultVariant]; !ok {
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "default variant %s not found for feature %s in variants %v", feature.DefaultVariant.String(), feature.Name.String(), feature.Variants)
}
switch feature.Kind {
case KindBoolean:
err := validateFeature[bool](feature)
if err != nil {
return nil, err
}
case KindString:
err := validateFeature[string](feature)
if err != nil {
return nil, err
}
case KindFloat:
err := validateFeature[float64](feature)
if err != nil {
return nil, err
}
case KindInt:
err := validateFeature[int64](feature)
if err != nil {
return nil, err
}
case KindObject:
err := validateFeature[any](feature)
if err != nil {
return nil, err
}
}
registry.features[feature.Name] = feature
}
return registry, nil
}
func validateFeature[T any](feature *Feature) error {
_, _, err := VariantValue[T](feature, feature.DefaultVariant)
if err != nil {
return err
}
for variant := range feature.Variants {
_, _, err := VariantValue[T](feature, variant)
if err != nil {
return err
}
}
return nil
}
func (r *registry) Get(name Name) (f *Feature, detail openfeature.ProviderResolutionDetail, err error) {
feature, ok := r.features[name]
if !ok {
err = errors.Newf(errors.TypeNotFound, ErrCodeFeatureNotFound, "feature %s not found", name.String())
detail = openfeature.ProviderResolutionDetail{
ResolutionError: openfeature.NewGeneralResolutionError(err.Error()),
Reason: openfeature.ErrorReason,
}
return
}
return feature, openfeature.ProviderResolutionDetail{}, nil
}
func (r *registry) GetByString(name string) (f *Feature, detail openfeature.ProviderResolutionDetail, err error) {
featureName, err := NewName(name)
if err != nil {
detail = openfeature.ProviderResolutionDetail{
ResolutionError: openfeature.NewFlagNotFoundResolutionError(err.Error()),
Reason: openfeature.ErrorReason,
}
return
}
return r.Get(featureName)
}
func (r *registry) List() []*Feature {
features := make([]*Feature, 0, len(r.features))
for _, f := range r.features {
features = append(features, f)
}
return features
}

View File

@@ -1,20 +0,0 @@
package featuretypes
import "github.com/SigNoz/signoz/pkg/valuer"
// A concrete type for a feature flag stage
type Stage struct{ valuer.String }
var (
// Used when the feature is experimental
StageExperimental = Stage{valuer.NewString("experimental")}
// Used when the feature works and in preview stage but is not ready for production
StagePreview = Stage{valuer.NewString("preview")}
// Used when the feature is stable and ready for production
StageStable = Stage{valuer.NewString("stable")}
// Used when the feature is deprecated and will be removed in the future
StageDeprecated = Stage{valuer.NewString("deprecated")}
)

View File

@@ -81,7 +81,7 @@ class TracesResourceOrAttributeKeys(ABC):
self.is_column = is_column
def np_arr(self) -> np.array:
return np.array([self.name, self.tag_type, self.datatype, self.is_column], dtype=object)
return np.array([self.name, self.tag_type, self.datatype, self.is_column])
class TracesTagAttributes(ABC):
@@ -636,10 +636,8 @@ def insert_traces(
)
attribute_keys: List[TracesResourceOrAttributeKeys] = []
resource_keys: List[TracesResourceOrAttributeKeys] = []
for trace in traces:
attribute_keys.extend(trace.attribute_keys)
resource_keys.extend(trace.resource_keys)
if len(attribute_keys) > 0:
clickhouse.conn.insert(
@@ -648,13 +646,6 @@ def insert_traces(
data=[attribute_key.np_arr() for attribute_key in attribute_keys],
)
if len(resource_keys) > 0:
clickhouse.conn.insert(
database="signoz_traces",
table="distributed_span_attributes_keys",
data=[resource_key.np_arr() for resource_key in resource_keys],
)
# Insert main traces
clickhouse.conn.insert(
database="signoz_traces",