diff --git a/.github/ISSUE_TEMPLATE/request_dashboard.md b/.github/ISSUE_TEMPLATE/request_dashboard.md
new file mode 100644
index 0000000000..7972f40625
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/request_dashboard.md
@@ -0,0 +1,58 @@
+---
+name: Request Dashboard
+about: Request a new dashboard for the SigNoz Dashboards repository
+title: ''
+labels: 'dashboard-template'
+assignees: ''
+
+---
+
+## 📝 Dashboard Request Template
+
+*Use this template to request a new dashboard for the SigNoz Dashboards repository. Please provide as much detail as possible to help us understand your needs.*
+
+---
+
+### 1. Dashboard Name
+
+Name of the requested dashboard (e.g., MySQL Monitoring Dashboard):
+
+---
+
+### 2. Expected Dashboard Sections and Panels
+
+#### Section Name
+
+Brief description of the section (e.g., "Resource usage metrics for MySQL database").
+
+#### Panel Name
+
+Panel description (e.g., "Value-type panels displaying current CPU usage, memory usage, etc.").
+
+- **Example:**
+ - **Section**: Resource Metrics
+ - **Panel**: CPU Usage - Displays the current CPU usage across all database instances.
+ - **Panel**: Memory Usage - Displays the total memory used by the MySQL process.
+
+(Repeat this format for additional sections and panels)
+
+---
+
+### 3. Expected Variables
+
+List any variables you expect to use in the dashboard (e.g., `deployment.environment`, `hostname`, etc.).
+
+---
+
+### 4. Additional Comments or Requirements
+
+Any additional details or special requirements for the dashboard?
+
+---
+
+### 📋 Notes
+
+Please review the [CONTRIBUTING.md](https://github.com/SigNoz/dashboards/blob/main/CONTRIBUTING.md) for guidelines on dashboard structure, naming conventions, and how to submit a pull request.
+
+---
+Thank you for your request! We will review it and provide feedback or guidance as necessary.
diff --git a/ee/query-service/anomaly/daily.go b/ee/query-service/anomaly/daily.go
index 2d40974927..bbafe1618e 100644
--- a/ee/query-service/anomaly/daily.go
+++ b/ee/query-service/anomaly/daily.go
@@ -2,6 +2,9 @@ package anomaly
import (
"context"
+
+ querierV2 "go.signoz.io/signoz/pkg/query-service/app/querier/v2"
+ "go.signoz.io/signoz/pkg/query-service/app/queryBuilder"
)
type DailyProvider struct {
@@ -24,9 +27,18 @@ func NewDailyProvider(opts ...GenericProviderOption[*DailyProvider]) *DailyProvi
opt(dp)
}
+ dp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{
+ Reader: dp.reader,
+ Cache: dp.cache,
+ KeyGenerator: queryBuilder.NewKeyGenerator(),
+ FluxInterval: dp.fluxInterval,
+ FeatureLookup: dp.ff,
+ })
+
return dp
}
func (p *DailyProvider) GetAnomalies(ctx context.Context, req *GetAnomaliesRequest) (*GetAnomaliesResponse, error) {
- return nil, nil
+ req.Seasonality = SeasonalityDaily
+ return p.getAnomalies(ctx, req)
}
diff --git a/ee/query-service/anomaly/hourly.go b/ee/query-service/anomaly/hourly.go
index b3af3d01d8..1ee08655f0 100644
--- a/ee/query-service/anomaly/hourly.go
+++ b/ee/query-service/anomaly/hourly.go
@@ -2,6 +2,9 @@ package anomaly
import (
"context"
+
+ querierV2 "go.signoz.io/signoz/pkg/query-service/app/querier/v2"
+ "go.signoz.io/signoz/pkg/query-service/app/queryBuilder"
)
type HourlyProvider struct {
@@ -24,9 +27,18 @@ func NewHourlyProvider(opts ...GenericProviderOption[*HourlyProvider]) *HourlyPr
opt(hp)
}
+ hp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{
+ Reader: hp.reader,
+ Cache: hp.cache,
+ KeyGenerator: queryBuilder.NewKeyGenerator(),
+ FluxInterval: hp.fluxInterval,
+ FeatureLookup: hp.ff,
+ })
+
return hp
}
func (p *HourlyProvider) GetAnomalies(ctx context.Context, req *GetAnomaliesRequest) (*GetAnomaliesResponse, error) {
- return nil, nil
+ req.Seasonality = SeasonalityHourly
+ return p.getAnomalies(ctx, req)
}
diff --git a/ee/query-service/anomaly/params.go b/ee/query-service/anomaly/params.go
index 009374bf62..d39b2fa80f 100644
--- a/ee/query-service/anomaly/params.go
+++ b/ee/query-service/anomaly/params.go
@@ -16,6 +16,13 @@ const (
SeasonalityWeekly Seasonality = "weekly"
)
+var (
+ oneWeekOffset = 24 * 7 * time.Hour.Milliseconds()
+ oneDayOffset = 24 * time.Hour.Milliseconds()
+ oneHourOffset = time.Hour.Milliseconds()
+ fiveMinOffset = 5 * time.Minute.Milliseconds()
+)
+
func (s Seasonality) IsValid() bool {
switch s {
case SeasonalityHourly, SeasonalityDaily, SeasonalityWeekly:
@@ -35,7 +42,7 @@ type GetAnomaliesResponse struct {
}
// anomalyParams is the params for anomaly detection
-// prediction = avg(past_period_query) + avg(current_season_query) - avg(past_season_query)
+// prediction = avg(past_period_query) + avg(current_season_query) - mean(past_season_query, past2_season_query, past3_season_query)
//
// ^ ^
// | |
@@ -49,9 +56,9 @@ type anomalyQueryParams struct {
// and to detect anomalies
CurrentPeriodQuery *v3.QueryRangeParamsV3
// PastPeriodQuery is the query range params for past seasonal period
- // Example: For weekly seasonality, (now-1w-4h-5m, now-1w)
- // : For daily seasonality, (now-1d-2h-5m, now-1d)
- // : For hourly seasonality, (now-1h-30m-5m, now-1h)
+ // Example: For weekly seasonality, (now-1w-5m, now-1w)
+ // : For daily seasonality, (now-1d-5m, now-1d)
+ // : For hourly seasonality, (now-1h-5m, now-1h)
PastPeriodQuery *v3.QueryRangeParamsV3
// CurrentSeasonQuery is the query range params for current period (seasonal)
// Example: For weekly seasonality, this is the query range params for the (now-1w-5m, now)
@@ -63,16 +70,17 @@ type anomalyQueryParams struct {
// : For daily seasonality, this is the query range params for the (now-2d-5m, now-1d)
// : For hourly seasonality, this is the query range params for the (now-2h-5m, now-1h)
PastSeasonQuery *v3.QueryRangeParamsV3
-}
-func copyCompositeQuery(req *v3.QueryRangeParamsV3) *v3.CompositeQuery {
- deepCopyCompositeQuery := *req.CompositeQuery
- deepCopyCompositeQuery.BuilderQueries = make(map[string]*v3.BuilderQuery)
- for k, v := range req.CompositeQuery.BuilderQueries {
- query := *v
- deepCopyCompositeQuery.BuilderQueries[k] = &query
- }
- return &deepCopyCompositeQuery
+ // Past2SeasonQuery is the query range params for past 2 seasonal period to the current season
+ // Example: For weekly seasonality, this is the query range params for the (now-3w-5m, now-2w)
+ // : For daily seasonality, this is the query range params for the (now-3d-5m, now-2d)
+ // : For hourly seasonality, this is the query range params for the (now-3h-5m, now-2h)
+ Past2SeasonQuery *v3.QueryRangeParamsV3
+ // Past3SeasonQuery is the query range params for past 3 seasonal period to the current season
+ // Example: For weekly seasonality, this is the query range params for the (now-4w-5m, now-3w)
+ // : For daily seasonality, this is the query range params for the (now-4d-5m, now-3d)
+ // : For hourly seasonality, this is the query range params for the (now-4h-5m, now-3h)
+ Past3SeasonQuery *v3.QueryRangeParamsV3
}
func updateStepInterval(req *v3.QueryRangeParamsV3) {
@@ -95,7 +103,7 @@ func prepareAnomalyQueryParams(req *v3.QueryRangeParamsV3, seasonality Seasonali
currentPeriodQuery := &v3.QueryRangeParamsV3{
Start: start,
End: end,
- CompositeQuery: req.CompositeQuery,
+ CompositeQuery: req.CompositeQuery.Clone(),
Variables: make(map[string]interface{}, 0),
NoCache: false,
}
@@ -104,24 +112,24 @@ func prepareAnomalyQueryParams(req *v3.QueryRangeParamsV3, seasonality Seasonali
var pastPeriodStart, pastPeriodEnd int64
switch seasonality {
- // for one week period, we fetch the data from the past week with 4 hours offset
+ // for one week period, we fetch the data from the past week with 5 min offset
case SeasonalityWeekly:
- pastPeriodStart = start - 166*time.Hour.Milliseconds() - 4*time.Hour.Milliseconds()
- pastPeriodEnd = end - 166*time.Hour.Milliseconds()
- // for one day period, we fetch the data from the past day with 2 hours offset
+ pastPeriodStart = start - oneWeekOffset - fiveMinOffset
+ pastPeriodEnd = end - oneWeekOffset
+ // for one day period, we fetch the data from the past day with 5 min offset
case SeasonalityDaily:
- pastPeriodStart = start - 23*time.Hour.Milliseconds() - 2*time.Hour.Milliseconds()
- pastPeriodEnd = end - 23*time.Hour.Milliseconds()
- // for one hour period, we fetch the data from the past hour with 30 minutes offset
+ pastPeriodStart = start - oneDayOffset - fiveMinOffset
+ pastPeriodEnd = end - oneDayOffset
+ // for one hour period, we fetch the data from the past hour with 5 min offset
case SeasonalityHourly:
- pastPeriodStart = start - 1*time.Hour.Milliseconds() - 30*time.Minute.Milliseconds()
- pastPeriodEnd = end - 1*time.Hour.Milliseconds()
+ pastPeriodStart = start - oneHourOffset - fiveMinOffset
+ pastPeriodEnd = end - oneHourOffset
}
pastPeriodQuery := &v3.QueryRangeParamsV3{
Start: pastPeriodStart,
End: pastPeriodEnd,
- CompositeQuery: copyCompositeQuery(req),
+ CompositeQuery: req.CompositeQuery.Clone(),
Variables: make(map[string]interface{}, 0),
NoCache: false,
}
@@ -131,20 +139,20 @@ func prepareAnomalyQueryParams(req *v3.QueryRangeParamsV3, seasonality Seasonali
var currentGrowthPeriodStart, currentGrowthPeriodEnd int64
switch seasonality {
case SeasonalityWeekly:
- currentGrowthPeriodStart = start - 7*24*time.Hour.Milliseconds()
+ currentGrowthPeriodStart = start - oneWeekOffset
currentGrowthPeriodEnd = end
case SeasonalityDaily:
- currentGrowthPeriodStart = start - 23*time.Hour.Milliseconds()
+ currentGrowthPeriodStart = start - oneDayOffset
currentGrowthPeriodEnd = end
case SeasonalityHourly:
- currentGrowthPeriodStart = start - 1*time.Hour.Milliseconds()
+ currentGrowthPeriodStart = start - oneHourOffset
currentGrowthPeriodEnd = end
}
currentGrowthQuery := &v3.QueryRangeParamsV3{
Start: currentGrowthPeriodStart,
End: currentGrowthPeriodEnd,
- CompositeQuery: copyCompositeQuery(req),
+ CompositeQuery: req.CompositeQuery.Clone(),
Variables: make(map[string]interface{}, 0),
NoCache: false,
}
@@ -153,30 +161,76 @@ func prepareAnomalyQueryParams(req *v3.QueryRangeParamsV3, seasonality Seasonali
var pastGrowthPeriodStart, pastGrowthPeriodEnd int64
switch seasonality {
case SeasonalityWeekly:
- pastGrowthPeriodStart = start - 14*24*time.Hour.Milliseconds()
- pastGrowthPeriodEnd = start - 7*24*time.Hour.Milliseconds()
+ pastGrowthPeriodStart = start - 2*oneWeekOffset
+ pastGrowthPeriodEnd = start - 1*oneWeekOffset
case SeasonalityDaily:
- pastGrowthPeriodStart = start - 2*time.Hour.Milliseconds()
- pastGrowthPeriodEnd = start - 1*time.Hour.Milliseconds()
+ pastGrowthPeriodStart = start - 2*oneDayOffset
+ pastGrowthPeriodEnd = start - 1*oneDayOffset
case SeasonalityHourly:
- pastGrowthPeriodStart = start - 2*time.Hour.Milliseconds()
- pastGrowthPeriodEnd = start - 1*time.Hour.Milliseconds()
+ pastGrowthPeriodStart = start - 2*oneHourOffset
+ pastGrowthPeriodEnd = start - 1*oneHourOffset
}
pastGrowthQuery := &v3.QueryRangeParamsV3{
Start: pastGrowthPeriodStart,
End: pastGrowthPeriodEnd,
- CompositeQuery: copyCompositeQuery(req),
+ CompositeQuery: req.CompositeQuery.Clone(),
Variables: make(map[string]interface{}, 0),
NoCache: false,
}
updateStepInterval(pastGrowthQuery)
+ var past2GrowthPeriodStart, past2GrowthPeriodEnd int64
+ switch seasonality {
+ case SeasonalityWeekly:
+ past2GrowthPeriodStart = start - 3*oneWeekOffset
+ past2GrowthPeriodEnd = start - 2*oneWeekOffset
+ case SeasonalityDaily:
+ past2GrowthPeriodStart = start - 3*oneDayOffset
+ past2GrowthPeriodEnd = start - 2*oneDayOffset
+ case SeasonalityHourly:
+ past2GrowthPeriodStart = start - 3*oneHourOffset
+ past2GrowthPeriodEnd = start - 2*oneHourOffset
+ }
+
+ past2GrowthQuery := &v3.QueryRangeParamsV3{
+ Start: past2GrowthPeriodStart,
+ End: past2GrowthPeriodEnd,
+ CompositeQuery: req.CompositeQuery.Clone(),
+ Variables: make(map[string]interface{}, 0),
+ NoCache: false,
+ }
+ updateStepInterval(past2GrowthQuery)
+
+ var past3GrowthPeriodStart, past3GrowthPeriodEnd int64
+ switch seasonality {
+ case SeasonalityWeekly:
+ past3GrowthPeriodStart = start - 4*oneWeekOffset
+ past3GrowthPeriodEnd = start - 3*oneWeekOffset
+ case SeasonalityDaily:
+ past3GrowthPeriodStart = start - 4*oneDayOffset
+ past3GrowthPeriodEnd = start - 3*oneDayOffset
+ case SeasonalityHourly:
+ past3GrowthPeriodStart = start - 4*oneHourOffset
+ past3GrowthPeriodEnd = start - 3*oneHourOffset
+ }
+
+ past3GrowthQuery := &v3.QueryRangeParamsV3{
+ Start: past3GrowthPeriodStart,
+ End: past3GrowthPeriodEnd,
+ CompositeQuery: req.CompositeQuery.Clone(),
+ Variables: make(map[string]interface{}, 0),
+ NoCache: false,
+ }
+ updateStepInterval(past3GrowthQuery)
+
return &anomalyQueryParams{
CurrentPeriodQuery: currentPeriodQuery,
PastPeriodQuery: pastPeriodQuery,
CurrentSeasonQuery: currentGrowthQuery,
PastSeasonQuery: pastGrowthQuery,
+ Past2SeasonQuery: past2GrowthQuery,
+ Past3SeasonQuery: past3GrowthQuery,
}
}
@@ -185,4 +239,6 @@ type anomalyQueryResults struct {
PastPeriodResults []*v3.Result
CurrentSeasonResults []*v3.Result
PastSeasonResults []*v3.Result
+ Past2SeasonResults []*v3.Result
+ Past3SeasonResults []*v3.Result
}
diff --git a/ee/query-service/anomaly/seasonal.go b/ee/query-service/anomaly/seasonal.go
index ec95804fbe..485ab7f460 100644
--- a/ee/query-service/anomaly/seasonal.go
+++ b/ee/query-service/anomaly/seasonal.go
@@ -3,14 +3,21 @@ package anomaly
import (
"context"
"math"
+ "time"
"go.signoz.io/signoz/pkg/query-service/cache"
"go.signoz.io/signoz/pkg/query-service/interfaces"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
+ "go.signoz.io/signoz/pkg/query-service/postprocess"
"go.signoz.io/signoz/pkg/query-service/utils/labels"
"go.uber.org/zap"
)
+var (
+ // TODO(srikanthccv): make this configurable?
+ movingAvgWindowSize = 7
+)
+
// BaseProvider is an interface that includes common methods for all provider types
type BaseProvider interface {
GetBaseSeasonalProvider() *BaseSeasonalProvider
@@ -46,6 +53,7 @@ func WithReader[T BaseProvider](reader interfaces.Reader) GenericProviderOption[
type BaseSeasonalProvider struct {
querierV2 interfaces.Querier
reader interfaces.Reader
+ fluxInterval time.Duration
cache cache.Cache
keyGenerator cache.KeyGenerator
ff interfaces.FeatureLookup
@@ -53,28 +61,68 @@ type BaseSeasonalProvider struct {
func (p *BaseSeasonalProvider) getQueryParams(req *GetAnomaliesRequest) *anomalyQueryParams {
if !req.Seasonality.IsValid() {
- req.Seasonality = SeasonalityWeekly
+ req.Seasonality = SeasonalityDaily
}
return prepareAnomalyQueryParams(req.Params, req.Seasonality)
}
func (p *BaseSeasonalProvider) getResults(ctx context.Context, params *anomalyQueryParams) (*anomalyQueryResults, error) {
- currentPeriodResults, _, err := p.querierV2.QueryRange(ctx, params.CurrentPeriodQuery, nil)
+ currentPeriodResults, _, err := p.querierV2.QueryRange(ctx, params.CurrentPeriodQuery)
+ if err != nil {
+ return nil, err
+ }
+
+ currentPeriodResults, err = postprocess.PostProcessResult(currentPeriodResults, params.CurrentPeriodQuery)
+ if err != nil {
+ return nil, err
+ }
+
+ pastPeriodResults, _, err := p.querierV2.QueryRange(ctx, params.PastPeriodQuery)
+ if err != nil {
+ return nil, err
+ }
+
+ pastPeriodResults, err = postprocess.PostProcessResult(pastPeriodResults, params.PastPeriodQuery)
+ if err != nil {
+ return nil, err
+ }
+
+ currentSeasonResults, _, err := p.querierV2.QueryRange(ctx, params.CurrentSeasonQuery)
+ if err != nil {
+ return nil, err
+ }
+
+ currentSeasonResults, err = postprocess.PostProcessResult(currentSeasonResults, params.CurrentSeasonQuery)
if err != nil {
return nil, err
}
- pastPeriodResults, _, err := p.querierV2.QueryRange(ctx, params.PastPeriodQuery, nil)
+ pastSeasonResults, _, err := p.querierV2.QueryRange(ctx, params.PastSeasonQuery)
if err != nil {
return nil, err
}
- currentSeasonResults, _, err := p.querierV2.QueryRange(ctx, params.CurrentSeasonQuery, nil)
+ pastSeasonResults, err = postprocess.PostProcessResult(pastSeasonResults, params.PastSeasonQuery)
if err != nil {
return nil, err
}
- pastSeasonResults, _, err := p.querierV2.QueryRange(ctx, params.PastSeasonQuery, nil)
+ past2SeasonResults, _, err := p.querierV2.QueryRange(ctx, params.Past2SeasonQuery)
+ if err != nil {
+ return nil, err
+ }
+
+ past2SeasonResults, err = postprocess.PostProcessResult(past2SeasonResults, params.Past2SeasonQuery)
+ if err != nil {
+ return nil, err
+ }
+
+ past3SeasonResults, _, err := p.querierV2.QueryRange(ctx, params.Past3SeasonQuery)
+ if err != nil {
+ return nil, err
+ }
+
+ past3SeasonResults, err = postprocess.PostProcessResult(past3SeasonResults, params.Past3SeasonQuery)
if err != nil {
return nil, err
}
@@ -84,10 +132,18 @@ func (p *BaseSeasonalProvider) getResults(ctx context.Context, params *anomalyQu
PastPeriodResults: pastPeriodResults,
CurrentSeasonResults: currentSeasonResults,
PastSeasonResults: pastSeasonResults,
+ Past2SeasonResults: past2SeasonResults,
+ Past3SeasonResults: past3SeasonResults,
}, nil
}
+// getMatchingSeries gets the matching series from the query result
+// for the given series
func (p *BaseSeasonalProvider) getMatchingSeries(queryResult *v3.Result, series *v3.Series) *v3.Series {
+ if queryResult == nil || len(queryResult.Series) == 0 {
+ return nil
+ }
+
for _, curr := range queryResult.Series {
currLabels := labels.FromMap(curr.Labels)
seriesLabels := labels.FromMap(series.Labels)
@@ -99,6 +155,9 @@ func (p *BaseSeasonalProvider) getMatchingSeries(queryResult *v3.Result, series
}
func (p *BaseSeasonalProvider) getAvg(series *v3.Series) float64 {
+ if series == nil || len(series.Points) == 0 {
+ return 0
+ }
var sum float64
for _, smpl := range series.Points {
sum += smpl.Value
@@ -107,6 +166,9 @@ func (p *BaseSeasonalProvider) getAvg(series *v3.Series) float64 {
}
func (p *BaseSeasonalProvider) getStdDev(series *v3.Series) float64 {
+ if series == nil || len(series.Points) == 0 {
+ return 0
+ }
avg := p.getAvg(series)
var sum float64
for _, smpl := range series.Points {
@@ -115,15 +177,65 @@ func (p *BaseSeasonalProvider) getStdDev(series *v3.Series) float64 {
return math.Sqrt(sum / float64(len(series.Points)))
}
-func (p *BaseSeasonalProvider) getPredictedSeries(series, prevSeries, currentSeasonSeries, pastSeasonSeries *v3.Series) *v3.Series {
+// getMovingAvg gets the moving average for the given series
+// for the given window size and start index
+func (p *BaseSeasonalProvider) getMovingAvg(series *v3.Series, movingAvgWindowSize, startIdx int) float64 {
+ if series == nil || len(series.Points) == 0 {
+ return 0
+ }
+ if startIdx >= len(series.Points)-movingAvgWindowSize {
+ startIdx = len(series.Points) - movingAvgWindowSize
+ }
+ var sum float64
+ points := series.Points[startIdx:]
+ for i := 0; i < movingAvgWindowSize && i < len(points); i++ {
+ sum += points[i].Value
+ }
+ avg := sum / float64(movingAvgWindowSize)
+ return avg
+}
+
+func (p *BaseSeasonalProvider) getMean(floats ...float64) float64 {
+ if len(floats) == 0 {
+ return 0
+ }
+ var sum float64
+ for _, f := range floats {
+ sum += f
+ }
+ return sum / float64(len(floats))
+}
+
+func (p *BaseSeasonalProvider) getPredictedSeries(
+ series, prevSeries, currentSeasonSeries, pastSeasonSeries, past2SeasonSeries, past3SeasonSeries *v3.Series,
+) *v3.Series {
predictedSeries := &v3.Series{
Labels: series.Labels,
LabelsArray: series.LabelsArray,
Points: []v3.Point{},
}
- for _, curr := range series.Points {
- predictedValue := p.getAvg(prevSeries) + p.getAvg(currentSeasonSeries) - p.getAvg(pastSeasonSeries)
+ // for each point in the series, get the predicted value
+ // the predicted value is the moving average (with window size = 7) of the previous period series
+ // plus the average of the current season series
+ // minus the mean of the past season series, past2 season series and past3 season series
+ for idx, curr := range series.Points {
+ predictedValue :=
+ p.getMovingAvg(prevSeries, movingAvgWindowSize, idx) +
+ p.getAvg(currentSeasonSeries) -
+ p.getMean(p.getAvg(pastSeasonSeries), p.getAvg(past2SeasonSeries), p.getAvg(past3SeasonSeries))
+
+ if predictedValue < 0 {
+ predictedValue = p.getMovingAvg(prevSeries, movingAvgWindowSize, idx)
+ }
+
+ zap.L().Info("predictedSeries",
+ zap.Float64("movingAvg", p.getMovingAvg(prevSeries, movingAvgWindowSize, idx)),
+ zap.Float64("avg", p.getAvg(currentSeasonSeries)),
+ zap.Float64("mean", p.getMean(p.getAvg(pastSeasonSeries), p.getAvg(past2SeasonSeries), p.getAvg(past3SeasonSeries))),
+ zap.Any("labels", series.Labels),
+ zap.Float64("predictedValue", predictedValue),
+ )
predictedSeries.Points = append(predictedSeries.Points, v3.Point{
Timestamp: curr.Timestamp,
Value: predictedValue,
@@ -133,33 +245,80 @@ func (p *BaseSeasonalProvider) getPredictedSeries(series, prevSeries, currentSea
return predictedSeries
}
-func (p *BaseSeasonalProvider) getExpectedValue(_, prevSeries, currentSeasonSeries, pastSeasonSeries *v3.Series) float64 {
- prevSeriesAvg := p.getAvg(prevSeries)
+// getBounds gets the upper and lower bounds for the given series
+// for the given z score threshold
+// moving avg of the previous period series + z score threshold * std dev of the series
+// moving avg of the previous period series - z score threshold * std dev of the series
+func (p *BaseSeasonalProvider) getBounds(
+ series, prevSeries, _, _, _, _ *v3.Series,
+ zScoreThreshold float64,
+) (*v3.Series, *v3.Series) {
+ upperBoundSeries := &v3.Series{
+ Labels: series.Labels,
+ LabelsArray: series.LabelsArray,
+ Points: []v3.Point{},
+ }
+
+ lowerBoundSeries := &v3.Series{
+ Labels: series.Labels,
+ LabelsArray: series.LabelsArray,
+ Points: []v3.Point{},
+ }
+
+ for idx, curr := range series.Points {
+ upperBound := p.getMovingAvg(prevSeries, movingAvgWindowSize, idx) + zScoreThreshold*p.getStdDev(series)
+ lowerBound := p.getMovingAvg(prevSeries, movingAvgWindowSize, idx) - zScoreThreshold*p.getStdDev(series)
+ upperBoundSeries.Points = append(upperBoundSeries.Points, v3.Point{
+ Timestamp: curr.Timestamp,
+ Value: upperBound,
+ })
+ lowerBoundSeries.Points = append(lowerBoundSeries.Points, v3.Point{
+ Timestamp: curr.Timestamp,
+ Value: math.Max(lowerBound, 0),
+ })
+ }
+
+ return upperBoundSeries, lowerBoundSeries
+}
+
+// getExpectedValue gets the expected value for the given series
+// for the given index
+// prevSeriesAvg + currentSeasonSeriesAvg - mean of past season series, past2 season series and past3 season series
+func (p *BaseSeasonalProvider) getExpectedValue(
+ _, prevSeries, currentSeasonSeries, pastSeasonSeries, past2SeasonSeries, past3SeasonSeries *v3.Series, idx int,
+) float64 {
+ prevSeriesAvg := p.getMovingAvg(prevSeries, movingAvgWindowSize, idx)
currentSeasonSeriesAvg := p.getAvg(currentSeasonSeries)
pastSeasonSeriesAvg := p.getAvg(pastSeasonSeries)
- zap.L().Debug("getExpectedValue",
- zap.Float64("prevSeriesAvg", prevSeriesAvg),
- zap.Float64("currentSeasonSeriesAvg", currentSeasonSeriesAvg),
- zap.Float64("pastSeasonSeriesAvg", pastSeasonSeriesAvg),
- zap.Float64("expectedValue", prevSeriesAvg+currentSeasonSeriesAvg-pastSeasonSeriesAvg),
- )
- return prevSeriesAvg + currentSeasonSeriesAvg - pastSeasonSeriesAvg
+ past2SeasonSeriesAvg := p.getAvg(past2SeasonSeries)
+ past3SeasonSeriesAvg := p.getAvg(past3SeasonSeries)
+ return prevSeriesAvg + currentSeasonSeriesAvg - p.getMean(pastSeasonSeriesAvg, past2SeasonSeriesAvg, past3SeasonSeriesAvg)
}
-func (p *BaseSeasonalProvider) getScore(series, prevSeries, weekSeries, weekPrevSeries *v3.Series, value float64) float64 {
- expectedValue := p.getExpectedValue(series, prevSeries, weekSeries, weekPrevSeries)
+// getScore gets the anomaly score for the given series
+// for the given index
+// (value - expectedValue) / std dev of the series
+func (p *BaseSeasonalProvider) getScore(
+ series, prevSeries, weekSeries, weekPrevSeries, past2SeasonSeries, past3SeasonSeries *v3.Series, value float64, idx int,
+) float64 {
+ expectedValue := p.getExpectedValue(series, prevSeries, weekSeries, weekPrevSeries, past2SeasonSeries, past3SeasonSeries, idx)
return (value - expectedValue) / p.getStdDev(weekSeries)
}
-func (p *BaseSeasonalProvider) getAnomalyScores(series, prevSeries, currentSeasonSeries, pastSeasonSeries *v3.Series) *v3.Series {
+// getAnomalyScores gets the anomaly scores for the given series
+// for the given index
+// (value - expectedValue) / std dev of the series
+func (p *BaseSeasonalProvider) getAnomalyScores(
+ series, prevSeries, currentSeasonSeries, pastSeasonSeries, past2SeasonSeries, past3SeasonSeries *v3.Series,
+) *v3.Series {
anomalyScoreSeries := &v3.Series{
Labels: series.Labels,
LabelsArray: series.LabelsArray,
Points: []v3.Point{},
}
- for _, curr := range series.Points {
- anomalyScore := p.getScore(series, prevSeries, currentSeasonSeries, pastSeasonSeries, curr.Value)
+ for idx, curr := range series.Points {
+ anomalyScore := p.getScore(series, prevSeries, currentSeasonSeries, pastSeasonSeries, past2SeasonSeries, past3SeasonSeries, curr.Value, idx)
anomalyScoreSeries.Points = append(anomalyScoreSeries.Points, v3.Point{
Timestamp: curr.Timestamp,
Value: anomalyScore,
@@ -169,7 +328,7 @@ func (p *BaseSeasonalProvider) getAnomalyScores(series, prevSeries, currentSeaso
return anomalyScoreSeries
}
-func (p *BaseSeasonalProvider) GetAnomalies(ctx context.Context, req *GetAnomaliesRequest) (*GetAnomaliesResponse, error) {
+func (p *BaseSeasonalProvider) getAnomalies(ctx context.Context, req *GetAnomaliesRequest) (*GetAnomaliesResponse, error) {
anomalyParams := p.getQueryParams(req)
anomalyQueryResults, err := p.getResults(ctx, anomalyParams)
if err != nil {
@@ -196,7 +355,32 @@ func (p *BaseSeasonalProvider) GetAnomalies(ctx context.Context, req *GetAnomali
pastSeasonResultsMap[result.QueryName] = result
}
+ past2SeasonResultsMap := make(map[string]*v3.Result)
+ for _, result := range anomalyQueryResults.Past2SeasonResults {
+ past2SeasonResultsMap[result.QueryName] = result
+ }
+
+ past3SeasonResultsMap := make(map[string]*v3.Result)
+ for _, result := range anomalyQueryResults.Past3SeasonResults {
+ past3SeasonResultsMap[result.QueryName] = result
+ }
+
for _, result := range currentPeriodResultsMap {
+ funcs := req.Params.CompositeQuery.BuilderQueries[result.QueryName].Functions
+
+ var zScoreThreshold float64
+ for _, f := range funcs {
+ if f.Name == v3.FunctionNameAnomaly {
+ value, ok := f.NamedArgs["z_score_threshold"]
+ if ok {
+ zScoreThreshold = value.(float64)
+ } else {
+ zScoreThreshold = 3
+ }
+ break
+ }
+ }
+
pastPeriodResult, ok := pastPeriodResultsMap[result.QueryName]
if !ok {
continue
@@ -209,21 +393,72 @@ func (p *BaseSeasonalProvider) GetAnomalies(ctx context.Context, req *GetAnomali
if !ok {
continue
}
+ past2SeasonResult, ok := past2SeasonResultsMap[result.QueryName]
+ if !ok {
+ continue
+ }
+ past3SeasonResult, ok := past3SeasonResultsMap[result.QueryName]
+ if !ok {
+ continue
+ }
for _, series := range result.Series {
+ stdDev := p.getStdDev(series)
+ zap.L().Info("stdDev", zap.Float64("stdDev", stdDev), zap.Any("labels", series.Labels))
+
pastPeriodSeries := p.getMatchingSeries(pastPeriodResult, series)
currentSeasonSeries := p.getMatchingSeries(currentSeasonResult, series)
pastSeasonSeries := p.getMatchingSeries(pastSeasonResult, series)
-
- predictedSeries := p.getPredictedSeries(series, pastPeriodSeries, currentSeasonSeries, pastSeasonSeries)
+ past2SeasonSeries := p.getMatchingSeries(past2SeasonResult, series)
+ past3SeasonSeries := p.getMatchingSeries(past3SeasonResult, series)
+
+ prevSeriesAvg := p.getAvg(pastPeriodSeries)
+ currentSeasonSeriesAvg := p.getAvg(currentSeasonSeries)
+ pastSeasonSeriesAvg := p.getAvg(pastSeasonSeries)
+ past2SeasonSeriesAvg := p.getAvg(past2SeasonSeries)
+ past3SeasonSeriesAvg := p.getAvg(past3SeasonSeries)
+ zap.L().Info("getAvg", zap.Float64("prevSeriesAvg", prevSeriesAvg), zap.Float64("currentSeasonSeriesAvg", currentSeasonSeriesAvg), zap.Float64("pastSeasonSeriesAvg", pastSeasonSeriesAvg), zap.Float64("past2SeasonSeriesAvg", past2SeasonSeriesAvg), zap.Float64("past3SeasonSeriesAvg", past3SeasonSeriesAvg), zap.Any("labels", series.Labels))
+
+ predictedSeries := p.getPredictedSeries(
+ series,
+ pastPeriodSeries,
+ currentSeasonSeries,
+ pastSeasonSeries,
+ past2SeasonSeries,
+ past3SeasonSeries,
+ )
result.PredictedSeries = append(result.PredictedSeries, predictedSeries)
- anomalyScoreSeries := p.getAnomalyScores(series, pastPeriodSeries, currentSeasonSeries, pastSeasonSeries)
+ upperBoundSeries, lowerBoundSeries := p.getBounds(
+ series,
+ pastPeriodSeries,
+ currentSeasonSeries,
+ pastSeasonSeries,
+ past2SeasonSeries,
+ past3SeasonSeries,
+ zScoreThreshold,
+ )
+ result.UpperBoundSeries = append(result.UpperBoundSeries, upperBoundSeries)
+ result.LowerBoundSeries = append(result.LowerBoundSeries, lowerBoundSeries)
+
+ anomalyScoreSeries := p.getAnomalyScores(
+ series,
+ pastPeriodSeries,
+ currentSeasonSeries,
+ pastSeasonSeries,
+ past2SeasonSeries,
+ past3SeasonSeries,
+ )
result.AnomalyScores = append(result.AnomalyScores, anomalyScoreSeries)
}
}
+ results := make([]*v3.Result, 0, len(currentPeriodResultsMap))
+ for _, result := range currentPeriodResultsMap {
+ results = append(results, result)
+ }
+
return &GetAnomaliesResponse{
- Results: anomalyQueryResults.CurrentPeriodResults,
+ Results: results,
}, nil
}
diff --git a/ee/query-service/anomaly/weekly.go b/ee/query-service/anomaly/weekly.go
index e41261df24..407e7e6440 100644
--- a/ee/query-service/anomaly/weekly.go
+++ b/ee/query-service/anomaly/weekly.go
@@ -2,6 +2,9 @@ package anomaly
import (
"context"
+
+ querierV2 "go.signoz.io/signoz/pkg/query-service/app/querier/v2"
+ "go.signoz.io/signoz/pkg/query-service/app/queryBuilder"
)
type WeeklyProvider struct {
@@ -23,9 +26,18 @@ func NewWeeklyProvider(opts ...GenericProviderOption[*WeeklyProvider]) *WeeklyPr
opt(wp)
}
+ wp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{
+ Reader: wp.reader,
+ Cache: wp.cache,
+ KeyGenerator: queryBuilder.NewKeyGenerator(),
+ FluxInterval: wp.fluxInterval,
+ FeatureLookup: wp.ff,
+ })
+
return wp
}
func (p *WeeklyProvider) GetAnomalies(ctx context.Context, req *GetAnomaliesRequest) (*GetAnomaliesResponse, error) {
- return nil, nil
+ req.Seasonality = SeasonalityWeekly
+ return p.getAnomalies(ctx, req)
}
diff --git a/frontend/package.json b/frontend/package.json
index 51097f7696..a9119d0e63 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -207,7 +207,6 @@
"eslint-plugin-sonarjs": "^0.12.0",
"husky": "^7.0.4",
"is-ci": "^3.0.1",
- "jest-playwright-preset": "^1.7.2",
"jest-styled-components": "^7.0.8",
"lint-staged": "^12.5.0",
"msw": "1.3.2",
diff --git a/frontend/public/locales/en-GB/services.json b/frontend/public/locales/en-GB/services.json
index 4c49847031..f04c851759 100644
--- a/frontend/public/locales/en-GB/services.json
+++ b/frontend/public/locales/en-GB/services.json
@@ -1,3 +1,3 @@
{
- "rps_over_100": "You are sending data at more than 100 RPS, your ingestion may be rate limited. Please reach out to us via Intercom support."
+ "rps_over_100": "You are sending data at more than 100 RPS, your ingestion may be rate limited. Please reach out to us via Intercom support or "
}
diff --git a/frontend/public/locales/en/services.json b/frontend/public/locales/en/services.json
index 4c49847031..f04c851759 100644
--- a/frontend/public/locales/en/services.json
+++ b/frontend/public/locales/en/services.json
@@ -1,3 +1,3 @@
{
- "rps_over_100": "You are sending data at more than 100 RPS, your ingestion may be rate limited. Please reach out to us via Intercom support."
+ "rps_over_100": "You are sending data at more than 100 RPS, your ingestion may be rate limited. Please reach out to us via Intercom support or "
}
diff --git a/frontend/src/container/BillingContainer/BillingContainer.styles.scss b/frontend/src/container/BillingContainer/BillingContainer.styles.scss
index e4c7deec06..2bc41d89e6 100644
--- a/frontend/src/container/BillingContainer/BillingContainer.styles.scss
+++ b/frontend/src/container/BillingContainer/BillingContainer.styles.scss
@@ -50,6 +50,13 @@
align-items: center;
}
}
+
+ .billing-update-note {
+ text-align: left;
+ font-size: 13px;
+ color: var(--bg-vanilla-200);
+ margin-top: 16px;
+ }
}
.ant-skeleton.ant-skeleton-element.ant-skeleton-active {
@@ -75,5 +82,9 @@
}
}
}
+
+ .billing-update-note {
+ color: var(--bg-ink-200);
+ }
}
}
diff --git a/frontend/src/container/BillingContainer/BillingContainer.tsx b/frontend/src/container/BillingContainer/BillingContainer.tsx
index e366f068b2..449474a429 100644
--- a/frontend/src/container/BillingContainer/BillingContainer.tsx
+++ b/frontend/src/container/BillingContainer/BillingContainer.tsx
@@ -348,7 +348,12 @@ export default function BillingContainer(): JSX.Element {
const BillingUsageGraphCallback = useCallback(
() =>
!isLoading && !isFetchingBillingData ? (
-
+ <>
+
+
+ Note: Billing metrics are updated once every 24 hours.
+
+ >
) : (
diff --git a/frontend/src/container/CreateAlertRule/defaults.ts b/frontend/src/container/CreateAlertRule/defaults.ts
index 870bd63265..f9735e7644 100644
--- a/frontend/src/container/CreateAlertRule/defaults.ts
+++ b/frontend/src/container/CreateAlertRule/defaults.ts
@@ -65,7 +65,7 @@ export const logAlertDefaults: AlertDef = {
chQueries: {
A: {
name: 'A',
- query: `select \ntoStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 MINUTE) AS interval, \ntoFloat64(count()) as value \nFROM signoz_logs.distributed_logs \nWHERE timestamp BETWEEN {{.start_timestamp_nano}} AND {{.end_timestamp_nano}} \nGROUP BY interval;\n\n-- available variables:\n-- \t{{.start_timestamp_nano}}\n-- \t{{.end_timestamp_nano}}\n\n-- required columns (or alias):\n-- \tvalue\n-- \tinterval`,
+ query: `select \ntoStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 MINUTE) AS interval, \ntoFloat64(count()) as value \nFROM signoz_logs.distributed_logs_v2 \nWHERE timestamp BETWEEN {{.start_timestamp_nano}} AND {{.end_timestamp_nano}} \nGROUP BY interval;\n\n-- available variables:\n-- \t{{.start_timestamp_nano}}\n-- \t{{.end_timestamp_nano}}\n\n-- required columns (or alias):\n-- \tvalue\n-- \tinterval`,
legend: '',
disabled: false,
},
diff --git a/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx b/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx
index 7511a4d445..974a35a39c 100644
--- a/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx
+++ b/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx
@@ -140,7 +140,7 @@ function FullView({
const [graphsVisibilityStates, setGraphsVisibilityStates] = useState<
boolean[]
- >(Array(response.data?.payload.data.result.length).fill(true));
+ >(Array(response.data?.payload?.data?.result?.length).fill(true));
useEffect(() => {
const {
diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx
index 9901243b2f..8dc46c5a5a 100644
--- a/frontend/src/container/LogsExplorerViews/index.tsx
+++ b/frontend/src/container/LogsExplorerViews/index.tsx
@@ -133,6 +133,9 @@ function LogsExplorerViews({
// State
const [page, setPage] = useState(1);
const [logs, setLogs] = useState([]);
+ const [lastLogLineTimestamp, setLastLogLineTimestamp] = useState<
+ number | string | null
+ >();
const [requestData, setRequestData] = useState(null);
const [showFormatMenuItems, setShowFormatMenuItems] = useState(false);
const [queryId, setQueryId] = useState(v4());
@@ -270,6 +273,14 @@ function LogsExplorerViews({
start: minTime,
end: maxTime,
}),
+ // send the lastLogTimeStamp only when the panel type is list and the orderBy is timestamp and the order is desc
+ lastLogLineTimestamp:
+ panelType === PANEL_TYPES.LIST &&
+ requestData?.builder?.queryData?.[0]?.orderBy?.[0]?.columnName ===
+ 'timestamp' &&
+ requestData?.builder?.queryData?.[0]?.orderBy?.[0]?.order === 'desc'
+ ? lastLogLineTimestamp
+ : undefined,
},
undefined,
listQueryKeyRef,
@@ -347,6 +358,10 @@ function LogsExplorerViews({
pageSize: nextPageSize,
});
+ // initialise the last log timestamp to null as we don't have the logs.
+ // as soon as we scroll to the end of the logs we set the lastLogLineTimestamp to the last log timestamp.
+ setLastLogLineTimestamp(lastLog.timestamp);
+
setPage((prevPage) => prevPage + 1);
setRequestData(newRequestData);
@@ -539,6 +554,11 @@ function LogsExplorerViews({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
+ useEffect(() => {
+ // clear the lastLogLineTimestamp when the data changes
+ setLastLogLineTimestamp(null);
+ }, [data]);
+
useEffect(() => {
if (
requestData?.id !== stagedQuery?.id ||
diff --git a/frontend/src/container/ServiceApplication/ServiceMetrics/ServiceMetricTable.tsx b/frontend/src/container/ServiceApplication/ServiceMetrics/ServiceMetricTable.tsx
index 6430cc9c8f..1a3b99d6dd 100644
--- a/frontend/src/container/ServiceApplication/ServiceMetrics/ServiceMetricTable.tsx
+++ b/frontend/src/container/ServiceApplication/ServiceMetrics/ServiceMetricTable.tsx
@@ -92,9 +92,10 @@ function ServiceMetricTable({
return (
<>
{RPS > MAX_RPS_LIMIT && (
-
+
{getText('rps_over_100')}
+ email
)}
diff --git a/frontend/src/container/ServiceApplication/ServiceTraces/ServiceTracesTable.tsx b/frontend/src/container/ServiceApplication/ServiceTraces/ServiceTracesTable.tsx
index 6633b7a1aa..42d22e8980 100644
--- a/frontend/src/container/ServiceApplication/ServiceTraces/ServiceTracesTable.tsx
+++ b/frontend/src/container/ServiceApplication/ServiceTraces/ServiceTracesTable.tsx
@@ -49,10 +49,11 @@ function ServiceTraceTable({
return (
<>
{RPS > MAX_RPS_LIMIT && (
-
-
+
+
{getText('rps_over_100')}
-
+ email
+
)}
diff --git a/frontend/src/container/SideNav/menuItems.tsx b/frontend/src/container/SideNav/menuItems.tsx
index be694227a1..6d24b74c53 100644
--- a/frontend/src/container/SideNav/menuItems.tsx
+++ b/frontend/src/container/SideNav/menuItems.tsx
@@ -1,7 +1,6 @@
import { RocketOutlined } from '@ant-design/icons';
import ROUTES from 'constants/routes';
import {
- AreaChart,
BarChart2,
BellDot,
BugIcon,
@@ -114,11 +113,6 @@ const menuItems: SidebarItem[] = [
icon: ,
isBeta: true,
},
- {
- key: ROUTES.USAGE_EXPLORER,
- label: 'Usage Explorer',
- icon: ,
- },
{
key: ROUTES.BILLING,
label: 'Billing',
diff --git a/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts b/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts
index 6fd42175ad..7b99b9d250 100644
--- a/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts
+++ b/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts
@@ -70,7 +70,7 @@ export const useFetchKeysAndValues = (
const queryFiltersWithoutId = useMemo(
() => ({
...query.filters,
- items: query.filters.items.map((item) => {
+ items: query.filters?.items?.map((item) => {
const filterWithoutId = cloneDeep(item);
unset(filterWithoutId, 'id');
return filterWithoutId;
diff --git a/frontend/src/lib/dashboard/prepareQueryRangePayload.ts b/frontend/src/lib/dashboard/prepareQueryRangePayload.ts
index 181a83914b..ffc2f0477c 100644
--- a/frontend/src/lib/dashboard/prepareQueryRangePayload.ts
+++ b/frontend/src/lib/dashboard/prepareQueryRangePayload.ts
@@ -1,6 +1,7 @@
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import getStep from 'lib/getStep';
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
+import { isUndefined } from 'lodash-es';
import store from 'store';
import { QueryRangePayload } from 'types/api/metrics/getQueryRange';
import { EQueryType } from 'types/common/dashboard';
@@ -24,7 +25,11 @@ export const prepareQueryRangePayload = ({
fillGaps = false,
}: GetQueryResultsProps): PrepareQueryRangePayload => {
let legendMap: Record = {};
- const { allowSelectedIntervalForStepGen, ...restParams } = params;
+ const {
+ allowSelectedIntervalForStepGen,
+ lastLogLineTimestamp,
+ ...restParams
+ } = params;
const compositeQuery: QueryRangePayload['compositeQuery'] = {
queryType: query.queryType,
@@ -90,9 +95,13 @@ export const prepareQueryRangePayload = ({
interval: globalSelectedInterval,
});
+ const endLogTimeStamp = !isUndefined(lastLogLineTimestamp)
+ ? new Date(lastLogLineTimestamp as string | number)?.getTime() || undefined
+ : undefined;
+
const queryPayload: QueryRangePayload = {
start: parseInt(start, 10) * 1e3,
- end: parseInt(end, 10) * 1e3,
+ end: endLogTimeStamp || parseInt(end, 10) * 1e3,
step: getStep({
start: allowSelectedIntervalForStepGen
? start
diff --git a/frontend/src/pages/LogsExplorer/index.tsx b/frontend/src/pages/LogsExplorer/index.tsx
index 9e23b34c2c..5e4d1cf55f 100644
--- a/frontend/src/pages/LogsExplorer/index.tsx
+++ b/frontend/src/pages/LogsExplorer/index.tsx
@@ -67,7 +67,7 @@ function LogsExplorer(): JSX.Element {
}
if (
currentQuery.builder.queryData.length === 1 &&
- currentQuery.builder.queryData[0].groupBy.length > 0
+ currentQuery.builder.queryData?.[0]?.groupBy?.length > 0
) {
handleChangeSelectedView(SELECTED_VIEWS.QUERY_BUILDER);
}
diff --git a/frontend/src/pages/Support/Support.tsx b/frontend/src/pages/Support/Support.tsx
index 0dbd7a9526..9d3d8fff8f 100644
--- a/frontend/src/pages/Support/Support.tsx
+++ b/frontend/src/pages/Support/Support.tsx
@@ -83,7 +83,7 @@ const supportChannels = [
name: 'Schedule a call',
icon: ,
title: 'Schedule a call with the founders.',
- url: 'https://calendly.com/pranay-signoz/signoz-intro-calls',
+ url: 'https://calendly.com/vishal-signoz/30min',
btnText: 'Schedule call',
},
{
diff --git a/frontend/src/periscope/components/KeyValueLabel/KeyValueLabel.styles.scss b/frontend/src/periscope/components/KeyValueLabel/KeyValueLabel.styles.scss
index 88ae57f4e8..e325b85113 100644
--- a/frontend/src/periscope/components/KeyValueLabel/KeyValueLabel.styles.scss
+++ b/frontend/src/periscope/components/KeyValueLabel/KeyValueLabel.styles.scss
@@ -12,6 +12,10 @@
font-weight: 400;
line-height: 18px;
letter-spacing: -0.005em;
+ &,
+ &:hover {
+ color: var(--text-vanilla-400);
+ }
}
&__key {
background: var(--bg-ink-400);
@@ -20,13 +24,15 @@
&__value {
background: var(--bg-slate-400);
}
- color: var(--text-vanilla-400);
}
.lightMode {
.key-value-label {
border-color: var(--bg-vanilla-400);
- color: var(--text-ink-400);
+ &__key,
+ &__value {
+ color: var(--text-ink-400);
+ }
&__key {
background: var(--bg-vanilla-300);
}
diff --git a/frontend/src/periscope/components/KeyValueLabel/KeyValueLabel.tsx b/frontend/src/periscope/components/KeyValueLabel/KeyValueLabel.tsx
index 377c647a3f..c0987ccff1 100644
--- a/frontend/src/periscope/components/KeyValueLabel/KeyValueLabel.tsx
+++ b/frontend/src/periscope/components/KeyValueLabel/KeyValueLabel.tsx
@@ -1,6 +1,7 @@
import './KeyValueLabel.styles.scss';
import { Tooltip } from 'antd';
+import { useMemo } from 'react';
import TrimmedText from '../TrimmedText/TrimmedText';
@@ -15,19 +16,33 @@ export default function KeyValueLabel({
badgeValue,
maxCharacters = 20,
}: KeyValueLabelProps): JSX.Element | null {
+ const isUrl = useMemo(() => /^https?:\/\//.test(badgeValue), [badgeValue]);
+
if (!badgeKey || !badgeValue) {
return null;
}
+
return (
-
-
-
+
+ ) : (
+
+
+
+
+
+ )}
);
}
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 2ef8b540e0..1c501a08d3 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -169,7 +169,7 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98"
integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==
-"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.7.2", "@babel/core@^7.7.5", "@babel/core@^7.8.0":
+"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.7.2", "@babel/core@^7.8.0":
version "7.21.4"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz"
integrity sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==
@@ -2659,18 +2659,6 @@
dependencies:
tslib "2.5.0"
-"@hapi/hoek@^9.0.0":
- version "9.3.0"
- resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz"
- integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==
-
-"@hapi/topo@^5.0.0":
- version "5.1.0"
- resolved "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz"
- integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
- dependencies:
- "@hapi/hoek" "^9.0.0"
-
"@humanwhocodes/config-array@^0.5.0":
version "0.5.0"
resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz"
@@ -3751,23 +3739,6 @@
unplugin "1.0.1"
uuid "^9.0.0"
-"@sideway/address@^4.1.3":
- version "4.1.4"
- resolved "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz"
- integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==
- dependencies:
- "@hapi/hoek" "^9.0.0"
-
-"@sideway/formula@^3.0.1":
- version "3.0.1"
- resolved "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz"
- integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
-
-"@sideway/pinpoint@^2.0.0":
- version "2.0.0"
- resolved "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz"
- integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
-
"@signozhq/design-tokens@0.0.8":
version "0.0.8"
resolved "https://registry.yarnpkg.com/@signozhq/design-tokens/-/design-tokens-0.0.8.tgz#368dc92cfe01d0cd893df140445c5d9dfd944a88"
@@ -4591,13 +4562,6 @@
resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz"
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
-"@types/wait-on@^5.2.0":
- version "5.3.1"
- resolved "https://registry.npmjs.org/@types/wait-on/-/wait-on-5.3.1.tgz"
- integrity sha512-2FFOKCF/YydrMUaqg+fkk49qf0e5rDgwt6aQsMzFQzbS419h2gNOXyiwp/o2yYy27bi/C1z+HgfncryjGzlvgQ==
- dependencies:
- "@types/node" "*"
-
"@types/webpack-dev-server@^4.7.2":
version "4.7.2"
resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-4.7.2.tgz#a12d9881aa23cdd4cecbb2d31fa784a45c4967e0"
@@ -5428,18 +5392,6 @@ anymatch@^3.0.3, anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
-append-transform@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz"
- integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==
- dependencies:
- default-require-extensions "^3.0.0"
-
-archy@^1.0.0:
- version "1.0.0"
- resolved "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz"
- integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==
-
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz"
@@ -5635,13 +5587,6 @@ axios@1.7.4:
form-data "^4.0.0"
proxy-from-env "^1.1.0"
-axios@^0.21.1:
- version "0.21.4"
- resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
- integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
- dependencies:
- follow-redirects "^1.14.0"
-
axobject-query@^3.1.1:
version "3.1.1"
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz"
@@ -6315,16 +6260,6 @@ bytes@3.1.2:
resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
-caching-transform@^4.0.0:
- version "4.0.0"
- resolved "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz"
- integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==
- dependencies:
- hasha "^5.0.0"
- make-dir "^3.0.0"
- package-hash "^4.0.0"
- write-file-atomic "^3.0.0"
-
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz"
@@ -6355,7 +6290,7 @@ camelcase-keys@^6.2.2:
map-obj "^4.0.0"
quick-lru "^4.0.1"
-camelcase@^5.0.0, camelcase@^5.3.1:
+camelcase@^5.3.1:
version "5.3.1"
resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
@@ -6620,15 +6555,6 @@ cli-width@^3.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
-cliui@^6.0.0:
- version "6.0.0"
- resolved "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz"
- integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
- dependencies:
- string-width "^4.2.0"
- strip-ansi "^6.0.0"
- wrap-ansi "^6.2.0"
-
cliui@^7.0.2:
version "7.0.4"
resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz"
@@ -6772,16 +6698,6 @@ commander@^10.0.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
-commander@^3.0.2:
- version "3.0.2"
- resolved "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz"
- integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==
-
-commander@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
- integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
-
commander@^7.0.0, commander@^7.2.0:
version "7.2.0"
resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz"
@@ -6802,11 +6718,6 @@ common-path-prefix@^3.0.0:
resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0"
integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==
-commondir@^1.0.1:
- version "1.0.1"
- resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz"
- integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
-
compare-func@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz"
@@ -7073,7 +6984,7 @@ cross-spawn@^6.0.5:
shebang-command "^1.2.0"
which "^1.2.9"
-cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
+cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@@ -7303,14 +7214,6 @@ custom-event-polyfill@^1.0.6:
resolved "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz"
integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==
-cwd@^0.10.0:
- version "0.10.0"
- resolved "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz"
- integrity sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==
- dependencies:
- find-pkg "^0.1.2"
- fs-exists-sync "^0.1.0"
-
"d3-array@1 - 3", "d3-array@2 - 3", "d3-array@2.10.0 - 3":
version "3.2.3"
resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.3.tgz"
@@ -7555,7 +7458,7 @@ decamelize-keys@^1.1.0:
decamelize "^1.1.0"
map-obj "^1.0.0"
-decamelize@^1.1.0, decamelize@^1.2.0:
+decamelize@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
@@ -7637,13 +7540,6 @@ default-gateway@^6.0.3:
dependencies:
execa "^5.0.0"
-default-require-extensions@^3.0.0:
- version "3.0.1"
- resolved "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz"
- integrity sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==
- dependencies:
- strip-bom "^4.0.0"
-
defaults@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a"
@@ -8163,11 +8059,6 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
-es6-error@^4.0.1:
- version "4.1.1"
- resolved "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz"
- integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
-
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz"
@@ -8608,18 +8499,6 @@ exit@^0.1.2:
resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz"
integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
-expand-tilde@^1.2.2:
- version "1.2.2"
- resolved "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz"
- integrity sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==
- dependencies:
- os-homedir "^1.0.1"
-
-expect-playwright@^0.8.0:
- version "0.8.0"
- resolved "https://registry.npmjs.org/expect-playwright/-/expect-playwright-0.8.0.tgz"
- integrity sha512-+kn8561vHAY+dt+0gMqqj1oY+g5xWrsuGMk4QGxotT2WS545nVqqjs37z6hrYfIuucwqthzwJfCJUEYqixyljg==
-
expect@^27.5.1:
version "27.5.1"
resolved "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz"
@@ -8828,15 +8707,6 @@ finalhandler@1.2.0:
statuses "2.0.1"
unpipe "~1.0.0"
-find-cache-dir@^3.2.0:
- version "3.3.2"
- resolved "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz"
- integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==
- dependencies:
- commondir "^1.0.1"
- make-dir "^3.0.2"
- pkg-dir "^4.1.0"
-
find-cache-dir@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-4.0.0.tgz#a30ee0448f81a3990708f6453633c733e2f6eec2"
@@ -8845,30 +8715,6 @@ find-cache-dir@^4.0.0:
common-path-prefix "^3.0.0"
pkg-dir "^7.0.0"
-find-file-up@^0.1.2:
- version "0.1.3"
- resolved "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz"
- integrity sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==
- dependencies:
- fs-exists-sync "^0.1.0"
- resolve-dir "^0.1.0"
-
-find-pkg@^0.1.2:
- version "0.1.2"
- resolved "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz"
- integrity sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==
- dependencies:
- find-file-up "^0.1.2"
-
-find-process@^1.4.4:
- version "1.4.7"
- resolved "https://registry.npmjs.org/find-process/-/find-process-1.4.7.tgz"
- integrity sha512-/U4CYp1214Xrp3u3Fqr9yNynUrr5Le4y0SsJh2lMDDSbpwYSz3M2SMWQC+wqcx79cN8PQtHQIL8KnuY9M66fdg==
- dependencies:
- chalk "^4.0.0"
- commander "^5.1.0"
- debug "^4.1.1"
-
find-up@^4.0.0, find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz"
@@ -8925,7 +8771,7 @@ flubber@^0.4.2:
svgpath "^2.2.1"
topojson-client "^3.0.0"
-follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.15.6:
+follow-redirects@^1.0.0, follow-redirects@^1.15.6:
version "1.15.6"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
@@ -8962,14 +8808,6 @@ force-graph@1:
kapsule "^1.14"
lodash-es "4"
-foreground-child@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz"
- integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==
- dependencies:
- cross-spawn "^7.0.0"
- signal-exit "^3.0.2"
-
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz"
@@ -9008,16 +8846,11 @@ fresh@0.5.2:
resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
-fromentries@^1.2.0, fromentries@^1.3.2:
+fromentries@^1.3.2:
version "1.3.2"
resolved "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz"
integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==
-fs-exists-sync@^0.1.0:
- version "0.1.0"
- resolved "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz"
- integrity sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==
-
fs-extra@^10.0.0:
version "10.1.0"
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz"
@@ -9104,7 +8937,7 @@ geotiff@^2.0.7:
web-worker "^1.2.0"
xml-utils "^1.0.2"
-get-caller-file@^2.0.1, get-caller-file@^2.0.5:
+get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
@@ -9208,24 +9041,6 @@ global-dirs@^0.1.1:
dependencies:
ini "^1.3.4"
-global-modules@^0.2.3:
- version "0.2.3"
- resolved "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz"
- integrity sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==
- dependencies:
- global-prefix "^0.1.4"
- is-windows "^0.2.0"
-
-global-prefix@^0.1.4:
- version "0.1.5"
- resolved "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz"
- integrity sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==
- dependencies:
- homedir-polyfill "^1.0.0"
- ini "^1.3.4"
- is-windows "^0.2.0"
- which "^1.2.12"
-
global@^4.3.0, global@~4.4.0:
version "4.4.0"
resolved "https://registry.npmjs.org/global/-/global-4.4.0.tgz"
@@ -9272,7 +9087,7 @@ gopd@^1.0.1:
dependencies:
get-intrinsic "^1.1.3"
-graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
+graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
version "4.2.11"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -9345,14 +9160,6 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
-hasha@^5.0.0:
- version "5.2.2"
- resolved "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz"
- integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==
- dependencies:
- is-stream "^2.0.0"
- type-fest "^0.8.0"
-
hasown@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c"
@@ -9633,13 +9440,6 @@ hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-
dependencies:
react-is "^16.7.0"
-homedir-polyfill@^1.0.0:
- version "1.0.3"
- resolved "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz"
- integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
- dependencies:
- parse-passwd "^1.0.0"
-
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz"
@@ -10376,16 +10176,6 @@ is-what@^3.14.1:
resolved "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz"
integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==
-is-windows@^0.2.0:
- version "0.2.0"
- resolved "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz"
- integrity sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==
-
-is-windows@^1.0.2:
- version "1.0.2"
- resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz"
- integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-
is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
@@ -10423,23 +10213,6 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz"
integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
-istanbul-lib-hook@^3.0.0:
- version "3.0.0"
- resolved "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz"
- integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==
- dependencies:
- append-transform "^2.0.0"
-
-istanbul-lib-instrument@^4.0.0:
- version "4.0.3"
- resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz"
- integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==
- dependencies:
- "@babel/core" "^7.7.5"
- "@istanbuljs/schema" "^0.1.2"
- istanbul-lib-coverage "^3.0.0"
- semver "^6.3.0"
-
istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0:
version "5.2.1"
resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz"
@@ -10451,18 +10224,6 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0:
istanbul-lib-coverage "^3.2.0"
semver "^6.3.0"
-istanbul-lib-processinfo@^2.0.2:
- version "2.0.3"
- resolved "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz"
- integrity sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==
- dependencies:
- archy "^1.0.0"
- cross-spawn "^7.0.3"
- istanbul-lib-coverage "^3.2.0"
- p-map "^3.0.0"
- rimraf "^3.0.0"
- uuid "^8.3.2"
-
istanbul-lib-report@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz"
@@ -10481,7 +10242,7 @@ istanbul-lib-source-maps@^4.0.0:
istanbul-lib-coverage "^3.0.0"
source-map "^0.6.1"
-istanbul-reports@^3.0.2, istanbul-reports@^3.1.3:
+istanbul-reports@^3.1.3:
version "3.1.5"
resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz"
integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==
@@ -10777,39 +10538,11 @@ jest-mock@^27.5.1:
"@jest/types" "^27.5.1"
"@types/node" "*"
-jest-playwright-preset@^1.7.2:
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/jest-playwright-preset/-/jest-playwright-preset-1.7.2.tgz#708942c4dcc1edc85429079d2b47a9382298c454"
- integrity sha512-0M7M3z342bdKQLnS70cIptlJsW+uuGptbPnqIMg4K5Vp/L/DhqdTKZK7WM4n6miAUnZdUcjXKOdQWfZW/aBo7w==
- dependencies:
- expect-playwright "^0.8.0"
- jest-process-manager "^0.3.1"
- nyc "^15.1.0"
- playwright-core ">=1.2.0"
- rimraf "^3.0.2"
- uuid "^8.3.2"
-
jest-pnp-resolver@^1.2.2:
version "1.2.3"
resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz"
integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==
-jest-process-manager@^0.3.1:
- version "0.3.1"
- resolved "https://registry.npmjs.org/jest-process-manager/-/jest-process-manager-0.3.1.tgz"
- integrity sha512-x9W54UgZ7IkzUHgXtnI1x4GKOVjxtwW0CA/7yGbTHtT/YhENO0Lic2yfVyC/gekn7OIEMcQmy0L1r9WLQABfqw==
- dependencies:
- "@types/wait-on" "^5.2.0"
- chalk "^4.1.0"
- cwd "^0.10.0"
- exit "^0.1.2"
- find-process "^1.4.4"
- prompts "^2.4.1"
- signal-exit "^3.0.3"
- spawnd "^5.0.0"
- tree-kill "^1.2.2"
- wait-on "^5.3.0"
-
jest-regex-util@^27.5.1:
version "27.5.1"
resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz"
@@ -11037,17 +10770,6 @@ jju@~1.4.0:
resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a"
integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==
-joi@^17.3.0:
- version "17.9.2"
- resolved "https://registry.npmjs.org/joi/-/joi-17.9.2.tgz"
- integrity sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==
- dependencies:
- "@hapi/hoek" "^9.0.0"
- "@hapi/topo" "^5.0.0"
- "@sideway/address" "^4.1.3"
- "@sideway/formula" "^3.0.1"
- "@sideway/pinpoint" "^2.0.0"
-
js-base64@^3.7.2:
version "3.7.5"
resolved "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz"
@@ -11475,11 +11197,6 @@ lodash.debounce@^4.0.8:
resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
-lodash.flattendeep@^4.4.0:
- version "4.4.0"
- resolved "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz"
- integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==
-
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
@@ -11614,7 +11331,7 @@ make-dir@^2.1.0:
pify "^4.0.1"
semver "^5.6.0"
-make-dir@^3.0.0, make-dir@^3.0.2:
+make-dir@^3.0.0:
version "3.1.0"
resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
@@ -12456,7 +12173,7 @@ minimist-options@4.1.0:
is-plain-obj "^1.1.0"
kind-of "^6.0.3"
-minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
+minimist@^1.2.0, minimist@^1.2.6:
version "1.2.8"
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
@@ -12691,13 +12408,6 @@ node-int64@^0.4.0:
resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz"
integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
-node-preload@^0.2.1:
- version "0.2.1"
- resolved "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz"
- integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==
- dependencies:
- process-on-spawn "^1.0.0"
-
node-releases@^2.0.13:
version "2.0.13"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"
@@ -12787,39 +12497,6 @@ nwsapi@^2.2.0:
resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.4.tgz"
integrity sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==
-nyc@^15.1.0:
- version "15.1.0"
- resolved "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz"
- integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==
- dependencies:
- "@istanbuljs/load-nyc-config" "^1.0.0"
- "@istanbuljs/schema" "^0.1.2"
- caching-transform "^4.0.0"
- convert-source-map "^1.7.0"
- decamelize "^1.2.0"
- find-cache-dir "^3.2.0"
- find-up "^4.1.0"
- foreground-child "^2.0.0"
- get-package-type "^0.1.0"
- glob "^7.1.6"
- istanbul-lib-coverage "^3.0.0"
- istanbul-lib-hook "^3.0.0"
- istanbul-lib-instrument "^4.0.0"
- istanbul-lib-processinfo "^2.0.2"
- istanbul-lib-report "^3.0.0"
- istanbul-lib-source-maps "^4.0.0"
- istanbul-reports "^3.0.2"
- make-dir "^3.0.0"
- node-preload "^0.2.1"
- p-map "^3.0.0"
- process-on-spawn "^1.0.0"
- resolve-from "^5.0.0"
- rimraf "^3.0.0"
- signal-exit "^3.0.2"
- spawn-wrap "^2.0.0"
- test-exclude "^6.0.0"
- yargs "^15.0.2"
-
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
@@ -13013,11 +12690,6 @@ ora@^5.4.1:
strip-ansi "^6.0.0"
wcwidth "^1.0.1"
-os-homedir@^1.0.1:
- version "1.0.2"
- resolved "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz"
- integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==
-
os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
@@ -13080,13 +12752,6 @@ p-locate@^6.0.0:
dependencies:
p-limit "^4.0.0"
-p-map@^3.0.0:
- version "3.0.0"
- resolved "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz"
- integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==
- dependencies:
- aggregate-error "^3.0.0"
-
p-map@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz"
@@ -13107,16 +12772,6 @@ p-try@^2.0.0:
resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
-package-hash@^4.0.0:
- version "4.0.0"
- resolved "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz"
- integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==
- dependencies:
- graceful-fs "^4.1.15"
- hasha "^5.0.0"
- lodash.flattendeep "^4.4.0"
- release-zalgo "^1.0.0"
-
pako@^2.0.4:
version "2.1.0"
resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz"
@@ -13229,11 +12884,6 @@ parse-numeric-range@^1.3.0:
resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3"
integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==
-parse-passwd@^1.0.0:
- version "1.0.0"
- resolved "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz"
- integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==
-
parse5-htmlparser2-tree-adapter@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz"
@@ -13410,7 +13060,7 @@ pirates@^4.0.4:
resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz"
integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==
-pkg-dir@^4.1.0, pkg-dir@^4.2.0:
+pkg-dir@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz"
integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
@@ -13424,7 +13074,7 @@ pkg-dir@^7.0.0:
dependencies:
find-up "^6.3.0"
-playwright-core@1.33.0, playwright-core@>=1.2.0:
+playwright-core@1.33.0:
version "1.33.0"
resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.33.0.tgz"
integrity sha512-aizyPE1Cj62vAECdph1iaMILpT0WUDCq3E6rW6I+dleSbBoGbktvJtzS6VHkZ4DKNEOG9qJpiom/ZxO+S15LAw==
@@ -13807,13 +13457,6 @@ process-nextick-args@~2.0.0:
resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
-process-on-spawn@^1.0.0:
- version "1.0.0"
- resolved "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz"
- integrity sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==
- dependencies:
- fromentries "^1.2.0"
-
process@^0.11.10:
version "0.11.10"
resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
@@ -13829,7 +13472,7 @@ promise-polyfill@^3.1.0:
resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-3.1.0.tgz"
integrity sha512-t20OwHJ4ZOUj5fV+qms67oczphAVkRC6Rrjcrne+V1FJkQMym7n69xJmYyXHulm9OUQ0Ie5KSzg0QhOYgaxy+w==
-prompts@^2.0.1, prompts@^2.4.1:
+prompts@^2.0.1:
version "2.4.2"
resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz"
integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
@@ -15010,13 +14653,6 @@ relateurl@^0.2.7:
resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz"
integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
-release-zalgo@^1.0.0:
- version "1.0.0"
- resolved "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz"
- integrity sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==
- dependencies:
- es6-error "^4.0.1"
-
remark-gfm@~3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-3.0.1.tgz#0b180f095e3036545e9dddac0e8df3fa5cfee54f"
@@ -15080,11 +14716,6 @@ require-from-string@^2.0.2:
resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
-require-main-filename@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz"
- integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
-
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz"
@@ -15107,14 +14738,6 @@ resolve-cwd@^3.0.0:
dependencies:
resolve-from "^5.0.0"
-resolve-dir@^0.1.0:
- version "0.1.1"
- resolved "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz"
- integrity sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==
- dependencies:
- expand-tilde "^1.2.2"
- global-modules "^0.2.3"
-
resolve-from@5.0.0, resolve-from@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz"
@@ -15250,13 +14873,6 @@ rxjs@7.8.0:
dependencies:
tslib "^2.1.0"
-rxjs@^6.6.3:
- version "6.6.7"
- resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz"
- integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
- dependencies:
- tslib "^1.9.0"
-
rxjs@^7.5.5:
version "7.8.1"
resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz"
@@ -15483,11 +15099,6 @@ serve-static@1.15.0:
parseurl "~1.3.3"
send "0.18.0"
-set-blocking@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz"
- integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
-
set-cookie-parser@^2.4.6:
version "2.6.0"
resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz#131921e50f62ff1a66a461d7d62d7b21d5d15a51"
@@ -15722,28 +15333,6 @@ space-separated-tokens@^2.0.0:
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f"
integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
-spawn-wrap@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz"
- integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==
- dependencies:
- foreground-child "^2.0.0"
- is-windows "^1.0.2"
- make-dir "^3.0.0"
- rimraf "^3.0.0"
- signal-exit "^3.0.2"
- which "^2.0.1"
-
-spawnd@^5.0.0:
- version "5.0.0"
- resolved "https://registry.npmjs.org/spawnd/-/spawnd-5.0.0.tgz"
- integrity sha512-28+AJr82moMVWolQvlAIv3JcYDkjkFTEmfDc503wxrF5l2rQ3dFz6DpbXp3kD4zmgGGldfM4xM4v1sFj/ZaIOA==
- dependencies:
- exit "^0.1.2"
- signal-exit "^3.0.3"
- tree-kill "^1.2.2"
- wait-port "^0.2.9"
-
spdx-correct@^3.0.0:
version "3.2.0"
resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz"
@@ -16450,11 +16039,6 @@ tr46@~0.0.3:
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
-tree-kill@^1.2.2:
- version "1.2.2"
- resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz"
- integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
-
trim-lines@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
@@ -16541,7 +16125,7 @@ tslib@2.5.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0:
resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz"
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
-tslib@^1.8.1, tslib@^1.9.0:
+tslib@^1.8.1:
version "1.14.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@@ -16614,7 +16198,7 @@ type-fest@^0.6.0:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz"
integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==
-type-fest@^0.8.0, type-fest@^0.8.1:
+type-fest@^0.8.1:
version "0.8.1"
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
@@ -17161,26 +16745,6 @@ w3c-xmlserializer@^2.0.0:
dependencies:
xml-name-validator "^3.0.0"
-wait-on@^5.3.0:
- version "5.3.0"
- resolved "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz"
- integrity sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg==
- dependencies:
- axios "^0.21.1"
- joi "^17.3.0"
- lodash "^4.17.21"
- minimist "^1.2.5"
- rxjs "^6.6.3"
-
-wait-port@^0.2.9:
- version "0.2.14"
- resolved "https://registry.npmjs.org/wait-port/-/wait-port-0.2.14.tgz"
- integrity sha512-kIzjWcr6ykl7WFbZd0TMae8xovwqcqbx6FM9l+7agOgUByhzdjfzZBPK2CPufldTOMxbUivss//Sh9MFawmPRQ==
- dependencies:
- chalk "^2.4.2"
- commander "^3.0.2"
- debug "^4.1.1"
-
walker@^1.0.7, walker@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"
@@ -17549,11 +17113,6 @@ which-collection@^1.0.1:
is-weakmap "^2.0.1"
is-weakset "^2.0.1"
-which-module@^2.0.0:
- version "2.0.1"
- resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz"
- integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
-
which-typed-array@^1.1.10, which-typed-array@^1.1.11, which-typed-array@^1.1.2:
version "1.1.11"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a"
@@ -17577,7 +17136,7 @@ which-typed-array@^1.1.9:
has-tostringtag "^1.0.0"
is-typed-array "^1.1.10"
-which@^1.2.12, which@^1.2.9:
+which@^1.2.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -17731,11 +17290,6 @@ xtend@^4.0.0:
resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
-y18n@^4.0.0:
- version "4.0.3"
- resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz"
- integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
-
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
@@ -17761,36 +17315,11 @@ yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.3:
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
-yargs-parser@^18.1.2:
- version "18.1.3"
- resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz"
- integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
- dependencies:
- camelcase "^5.0.0"
- decamelize "^1.2.0"
-
yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
-yargs@^15.0.2:
- version "15.4.1"
- resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz"
- integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
- dependencies:
- cliui "^6.0.0"
- decamelize "^1.2.0"
- find-up "^4.1.0"
- get-caller-file "^2.0.1"
- require-directory "^2.1.1"
- require-main-filename "^2.0.0"
- set-blocking "^2.0.0"
- string-width "^4.2.0"
- which-module "^2.0.0"
- y18n "^4.0.0"
- yargs-parser "^18.1.2"
-
yargs@^16.2.0:
version "16.2.0"
resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz"
diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go
index cf2e8fadf2..bb2a84b487 100644
--- a/pkg/query-service/app/clickhouseReader/reader.go
+++ b/pkg/query-service/app/clickhouseReader/reader.go
@@ -1,15 +1,12 @@
package clickhouseReader
import (
- "bytes"
"context"
"database/sql"
"encoding/json"
"fmt"
- "io"
"math"
"math/rand"
- "net/http"
"os"
"reflect"
"regexp"
@@ -136,8 +133,9 @@ type ClickHouseReader struct {
liveTailRefreshSeconds int
cluster string
- useLogsNewSchema bool
- logsTableName string
+ useLogsNewSchema bool
+ logsTableName string
+ logsLocalTableName string
}
// NewTraceReader returns a TraceReader for the database
@@ -172,7 +170,7 @@ func NewReaderFromClickhouseConnection(
cluster string,
useLogsNewSchema bool,
) *ClickHouseReader {
- alertManager, err := am.New("")
+ alertManager, err := am.New()
if err != nil {
zap.L().Error("failed to initialize alert manager", zap.Error(err))
zap.L().Error("check if the alert manager URL is correctly set and valid")
@@ -202,8 +200,10 @@ func NewReaderFromClickhouseConnection(
}
logsTableName := options.primary.LogsTable
+ logsLocalTableName := options.primary.LogsLocalTable
if useLogsNewSchema {
logsTableName = options.primary.LogsTableV2
+ logsLocalTableName = options.primary.LogsLocalTableV2
}
return &ClickHouseReader{
@@ -240,6 +240,7 @@ func NewReaderFromClickhouseConnection(
logsResourceTableV2: options.primary.LogsResourceTableV2,
logsResourceLocalTableV2: options.primary.LogsResourceLocalTableV2,
logsTableName: logsTableName,
+ logsLocalTableName: logsLocalTableName,
}
}
@@ -410,267 +411,6 @@ func (r *ClickHouseReader) GetConn() clickhouse.Conn {
return r.db
}
-func (r *ClickHouseReader) LoadChannel(channel *model.ChannelItem) *model.ApiError {
-
- receiver := &am.Receiver{}
- if err := json.Unmarshal([]byte(channel.Data), receiver); err != nil { // Parse []byte to go struct pointer
- return &model.ApiError{Typ: model.ErrorBadData, Err: err}
- }
-
- response, err := http.Post(constants.GetAlertManagerApiPrefix()+"v1/receivers", "application/json", bytes.NewBuffer([]byte(channel.Data)))
-
- if err != nil {
- zap.L().Error("Error in getting response of API call to alertmanager/v1/receivers", zap.Error(err))
- return &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
- if response.StatusCode > 299 {
- responseData, _ := io.ReadAll(response.Body)
-
- err := fmt.Errorf("error in getting 2xx response in API call to alertmanager/v1/receivers")
- zap.L().Error("Error in getting 2xx response in API call to alertmanager/v1/receivers", zap.String("Status", response.Status), zap.String("Data", string(responseData)))
-
- return &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
-
- return nil
-}
-
-func (r *ClickHouseReader) GetChannel(id string) (*model.ChannelItem, *model.ApiError) {
-
- idInt, _ := strconv.Atoi(id)
- channel := model.ChannelItem{}
-
- query := "SELECT id, created_at, updated_at, name, type, data data FROM notification_channels WHERE id=? "
-
- stmt, err := r.localDB.Preparex(query)
-
- if err != nil {
- zap.L().Error("Error in preparing sql query for GetChannel", zap.Error(err))
- return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
-
- err = stmt.Get(&channel, idInt)
-
- if err != nil {
- zap.L().Error("Error in getting channel with id", zap.Int("id", idInt), zap.Error(err))
- return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
-
- return &channel, nil
-
-}
-
-func (r *ClickHouseReader) DeleteChannel(id string) *model.ApiError {
-
- idInt, _ := strconv.Atoi(id)
-
- channelToDelete, apiErrorObj := r.GetChannel(id)
-
- if apiErrorObj != nil {
- return apiErrorObj
- }
-
- tx, err := r.localDB.Begin()
- if err != nil {
- return &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
-
- {
- stmt, err := tx.Prepare(`DELETE FROM notification_channels WHERE id=$1;`)
- if err != nil {
- zap.L().Error("Error in preparing statement for INSERT to notification_channels", zap.Error(err))
- tx.Rollback()
- return &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
- defer stmt.Close()
-
- if _, err := stmt.Exec(idInt); err != nil {
- zap.L().Error("Error in Executing prepared statement for INSERT to notification_channels", zap.Error(err))
- tx.Rollback() // return an error too, we may want to wrap them
- return &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
- }
-
- apiError := r.alertManager.DeleteRoute(channelToDelete.Name)
- if apiError != nil {
- tx.Rollback()
- return apiError
- }
-
- err = tx.Commit()
- if err != nil {
- zap.L().Error("Error in committing transaction for DELETE command to notification_channels", zap.Error(err))
- return &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
-
- return nil
-
-}
-
-func (r *ClickHouseReader) GetChannels() (*[]model.ChannelItem, *model.ApiError) {
-
- channels := []model.ChannelItem{}
-
- query := "SELECT id, created_at, updated_at, name, type, data data FROM notification_channels"
-
- err := r.localDB.Select(&channels, query)
-
- zap.L().Info(query)
-
- if err != nil {
- zap.L().Error("Error in processing sql query", zap.Error(err))
- return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
-
- return &channels, nil
-
-}
-
-func getChannelType(receiver *am.Receiver) string {
-
- if receiver.EmailConfigs != nil {
- return "email"
- }
- if receiver.OpsGenieConfigs != nil {
- return "opsgenie"
- }
- if receiver.PagerdutyConfigs != nil {
- return "pagerduty"
- }
- if receiver.PushoverConfigs != nil {
- return "pushover"
- }
- if receiver.SNSConfigs != nil {
- return "sns"
- }
- if receiver.SlackConfigs != nil {
- return "slack"
- }
- if receiver.VictorOpsConfigs != nil {
- return "victorops"
- }
- if receiver.WebhookConfigs != nil {
- return "webhook"
- }
- if receiver.WechatConfigs != nil {
- return "wechat"
- }
- if receiver.MSTeamsConfigs != nil {
- return "msteams"
- }
- return ""
-}
-
-func (r *ClickHouseReader) EditChannel(receiver *am.Receiver, id string) (*am.Receiver, *model.ApiError) {
-
- idInt, _ := strconv.Atoi(id)
-
- channel, apiErrObj := r.GetChannel(id)
-
- if apiErrObj != nil {
- return nil, apiErrObj
- }
- if channel.Name != receiver.Name {
- return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("channel name cannot be changed")}
- }
-
- tx, err := r.localDB.Begin()
- if err != nil {
- return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
-
- channel_type := getChannelType(receiver)
-
- // check if channel type is supported in the current user plan
- if err := r.featureFlags.CheckFeature(fmt.Sprintf("ALERT_CHANNEL_%s", strings.ToUpper(channel_type))); err != nil {
- zap.L().Warn("an unsupported feature was blocked", zap.Error(err))
- return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("unsupported feature. please upgrade your plan to access this feature")}
- }
-
- receiverString, _ := json.Marshal(receiver)
-
- {
- stmt, err := tx.Prepare(`UPDATE notification_channels SET updated_at=$1, type=$2, data=$3 WHERE id=$4;`)
-
- if err != nil {
- zap.L().Error("Error in preparing statement for UPDATE to notification_channels", zap.Error(err))
- tx.Rollback()
- return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
- defer stmt.Close()
-
- if _, err := stmt.Exec(time.Now(), channel_type, string(receiverString), idInt); err != nil {
- zap.L().Error("Error in Executing prepared statement for UPDATE to notification_channels", zap.Error(err))
- tx.Rollback() // return an error too, we may want to wrap them
- return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
- }
-
- apiError := r.alertManager.EditRoute(receiver)
- if apiError != nil {
- tx.Rollback()
- return nil, apiError
- }
-
- err = tx.Commit()
- if err != nil {
- zap.L().Error("Error in committing transaction for INSERT to notification_channels", zap.Error(err))
- return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
-
- return receiver, nil
-
-}
-
-func (r *ClickHouseReader) CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError) {
-
- channel_type := getChannelType(receiver)
-
- // check if channel type is supported in the current user plan
- if err := r.featureFlags.CheckFeature(fmt.Sprintf("ALERT_CHANNEL_%s", strings.ToUpper(channel_type))); err != nil {
- zap.L().Warn("an unsupported feature was blocked", zap.Error(err))
- return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("unsupported feature. please upgrade your plan to access this feature")}
- }
-
- receiverString, _ := json.Marshal(receiver)
-
- tx, err := r.localDB.Begin()
- if err != nil {
- return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
-
- {
- stmt, err := tx.Prepare(`INSERT INTO notification_channels (created_at, updated_at, name, type, data) VALUES($1,$2,$3,$4,$5);`)
- if err != nil {
- zap.L().Error("Error in preparing statement for INSERT to notification_channels", zap.Error(err))
- tx.Rollback()
- return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
- defer stmt.Close()
-
- if _, err := stmt.Exec(time.Now(), time.Now(), receiver.Name, channel_type, string(receiverString)); err != nil {
- zap.L().Error("Error in Executing prepared statement for INSERT to notification_channels", zap.Error(err))
- tx.Rollback() // return an error too, we may want to wrap them
- return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
- }
-
- apiError := r.alertManager.AddRoute(receiver)
- if apiError != nil {
- tx.Rollback()
- return nil, apiError
- }
-
- err = tx.Commit()
- if err != nil {
- zap.L().Error("Error in committing transaction for INSERT to notification_channels", zap.Error(err))
- return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
- }
-
- return receiver, nil
-
-}
-
func (r *ClickHouseReader) GetInstantQueryMetricsResult(ctx context.Context, queryParams *model.InstantQueryMetricsParams) (*promql.Result, *stats.QueryStats, *model.ApiError) {
qry, err := r.queryEngine.NewInstantQuery(ctx, r.remoteStorage, nil, queryParams.Query, queryParams.Time)
if err != nil {
@@ -988,7 +728,7 @@ func (r *ClickHouseReader) GetServiceOverview(ctx context.Context, queryParams *
return &serviceOverviewItems, nil
}
-func buildFilterArrayQuery(ctx context.Context, excludeMap map[string]struct{}, params []string, filter string, query *string, args []interface{}) []interface{} {
+func buildFilterArrayQuery(_ context.Context, excludeMap map[string]struct{}, params []string, filter string, query *string, args []interface{}) []interface{} {
for i, e := range params {
filterKey := filter + String(5)
if i == 0 && i == len(params)-1 {
@@ -1497,7 +1237,7 @@ func String(length int) string {
return StringWithCharset(length, charset)
}
-func buildQueryWithTagParams(ctx context.Context, tags []model.TagQuery) (string, []interface{}, *model.ApiError) {
+func buildQueryWithTagParams(_ context.Context, tags []model.TagQuery) (string, []interface{}, *model.ApiError) {
query := ""
var args []interface{}
for _, item := range tags {
@@ -1707,7 +1447,7 @@ func (r *ClickHouseReader) GetTagFilters(ctx context.Context, queryParams *model
return &tagFiltersResult, nil
}
-func excludeTags(ctx context.Context, tags []string) []string {
+func excludeTags(_ context.Context, tags []string) []string {
excludedTagsMap := map[string]bool{
"http.code": true,
"http.route": true,
@@ -2461,7 +2201,7 @@ func (r *ClickHouseReader) SetTTL(ctx context.Context,
return &model.SetTTLResponseItem{Message: "move ttl has been successfully set up"}, nil
}
-func (r *ClickHouseReader) deleteTtlTransactions(ctx context.Context, numberOfTransactionsStore int) {
+func (r *ClickHouseReader) deleteTtlTransactions(_ context.Context, numberOfTransactionsStore int) {
_, err := r.localDB.Exec("DELETE FROM ttl_status WHERE transaction_id NOT IN (SELECT distinct transaction_id FROM ttl_status ORDER BY created_at DESC LIMIT ?)", numberOfTransactionsStore)
if err != nil {
zap.L().Error("Error in processing ttl_status delete sql query", zap.Error(err))
@@ -2469,7 +2209,7 @@ func (r *ClickHouseReader) deleteTtlTransactions(ctx context.Context, numberOfTr
}
// checkTTLStatusItem checks if ttl_status table has an entry for the given table name
-func (r *ClickHouseReader) checkTTLStatusItem(ctx context.Context, tableName string) (model.TTLStatusItem, *model.ApiError) {
+func (r *ClickHouseReader) checkTTLStatusItem(_ context.Context, tableName string) (model.TTLStatusItem, *model.ApiError) {
statusItem := []model.TTLStatusItem{}
query := `SELECT id, status, ttl, cold_storage_ttl FROM ttl_status WHERE table_name = ? ORDER BY created_at DESC`
@@ -3268,23 +3008,23 @@ func (r *ClickHouseReader) GetLogFields(ctx context.Context) (*model.GetFieldsRe
resources = removeUnderscoreDuplicateFields(resources)
statements := []model.ShowCreateTableStatement{}
- query = fmt.Sprintf("SHOW CREATE TABLE %s.%s", r.logsDB, r.logsTable)
+ query = fmt.Sprintf("SHOW CREATE TABLE %s.%s", r.logsDB, r.logsLocalTableName)
err = r.db.Select(ctx, &statements, query)
if err != nil {
return nil, &model.ApiError{Err: err, Typ: model.ErrorInternal}
}
- extractSelectedAndInterestingFields(statements[0].Statement, constants.Attributes, &attributes, &response)
- extractSelectedAndInterestingFields(statements[0].Statement, constants.Resources, &resources, &response)
+ r.extractSelectedAndInterestingFields(statements[0].Statement, constants.Attributes, &attributes, &response)
+ r.extractSelectedAndInterestingFields(statements[0].Statement, constants.Resources, &resources, &response)
return &response, nil
}
-func extractSelectedAndInterestingFields(tableStatement string, fieldType string, fields *[]model.LogField, response *model.GetFieldsResponse) {
+func (r *ClickHouseReader) extractSelectedAndInterestingFields(tableStatement string, fieldType string, fields *[]model.LogField, response *model.GetFieldsResponse) {
for _, field := range *fields {
field.Type = fieldType
// all static fields are assumed to be selected as we don't allow changing them
- if isSelectedField(tableStatement, field) {
+ if isColumn(r.useLogsNewSchema, tableStatement, field.Type, field.Name, field.DataType) {
response.Selected = append(response.Selected, field)
} else {
response.Interesting = append(response.Interesting, field)
@@ -3292,13 +3032,6 @@ func extractSelectedAndInterestingFields(tableStatement string, fieldType string
}
}
-func isSelectedField(tableStatement string, field model.LogField) bool {
- // in case of attributes and resources, if there is a materialized column present then it is selected
- // TODO: handle partial change complete eg:- index is removed but materialized column is still present
- name := utils.GetClickhouseColumnName(field.Type, field.DataType, field.Name)
- return strings.Contains(tableStatement, name)
-}
-
func (r *ClickHouseReader) UpdateLogFieldV2(ctx context.Context, field *model.UpdateField) *model.ApiError {
if !field.Selected {
return model.ForbiddenError(errors.New("removing a selected field is not allowed, please reach out to support."))
@@ -3974,7 +3707,8 @@ func isColumn(useLogsNewSchema bool, tableStatement, attrType, field, datType st
// value of attrType will be `resource` or `tag`, if `tag` change it to `attribute`
var name string
if useLogsNewSchema {
- name = utils.GetClickhouseColumnNameV2(attrType, datType, field)
+ // adding explict '`'
+ name = fmt.Sprintf("`%s`", utils.GetClickhouseColumnNameV2(attrType, datType, field))
} else {
name = utils.GetClickhouseColumnName(attrType, datType, field)
}
@@ -4033,7 +3767,7 @@ func (r *ClickHouseReader) GetLogAggregateAttributes(ctx context.Context, req *v
defer rows.Close()
statements := []model.ShowCreateTableStatement{}
- query = fmt.Sprintf("SHOW CREATE TABLE %s.%s", r.logsDB, r.logsTable)
+ query = fmt.Sprintf("SHOW CREATE TABLE %s.%s", r.logsDB, r.logsLocalTableName)
err = r.db.Select(ctx, &statements, query)
if err != nil {
return nil, fmt.Errorf("error while fetching logs schema: %s", err.Error())
@@ -4087,7 +3821,7 @@ func (r *ClickHouseReader) GetLogAttributeKeys(ctx context.Context, req *v3.Filt
defer rows.Close()
statements := []model.ShowCreateTableStatement{}
- query = fmt.Sprintf("SHOW CREATE TABLE %s.%s", r.logsDB, r.logsTable)
+ query = fmt.Sprintf("SHOW CREATE TABLE %s.%s", r.logsDB, r.logsLocalTableName)
err = r.db.Select(ctx, &statements, query)
if err != nil {
return nil, fmt.Errorf("error while fetching logs schema: %s", err.Error())
@@ -4174,10 +3908,10 @@ func (r *ClickHouseReader) GetLogAttributeValues(ctx context.Context, req *v3.Fi
// prepare the query and run
if len(req.SearchText) != 0 {
- query = fmt.Sprintf("select distinct %s from %s.%s where timestamp >= toInt64(toUnixTimestamp(now() - INTERVAL 48 HOUR)*1000000000) and %s ILIKE $1 limit $2", selectKey, r.logsDB, r.logsTable, filterValueColumnWhere)
+ query = fmt.Sprintf("select distinct %s from %s.%s where timestamp >= toInt64(toUnixTimestamp(now() - INTERVAL 48 HOUR)*1000000000) and %s ILIKE $1 limit $2", selectKey, r.logsDB, r.logsLocalTableName, filterValueColumnWhere)
rows, err = r.db.Query(ctx, query, searchText, req.Limit)
} else {
- query = fmt.Sprintf("select distinct %s from %s.%s where timestamp >= toInt64(toUnixTimestamp(now() - INTERVAL 48 HOUR)*1000000000) limit $1", selectKey, r.logsDB, r.logsTable)
+ query = fmt.Sprintf("select distinct %s from %s.%s where timestamp >= toInt64(toUnixTimestamp(now() - INTERVAL 48 HOUR)*1000000000) limit $1", selectKey, r.logsDB, r.logsLocalTableName)
rows, err = r.db.Query(ctx, query, req.Limit)
}
} else if len(req.SearchText) != 0 {
@@ -4364,41 +4098,65 @@ func (r *ClickHouseReader) GetQBFilterSuggestionsForLogs(
func (r *ClickHouseReader) getValuesForLogAttributes(
ctx context.Context, attributes []v3.AttributeKey, limit uint64,
) ([][]any, *model.ApiError) {
- // query top `limit` distinct values seen for `tagKey`s of interest
- // ordered by timestamp when the value was seen
- query := fmt.Sprintf(
- `
- select tagKey, stringTagValue, int64TagValue, float64TagValue
- from (
- select
- tagKey,
- stringTagValue,
- int64TagValue,
- float64TagValue,
- row_number() over (partition by tagKey order by ts desc) as rank
- from (
- select
- tagKey,
- stringTagValue,
- int64TagValue,
- float64TagValue,
- max(timestamp) as ts
- from %s.%s
- where tagKey in $1
- group by (tagKey, stringTagValue, int64TagValue, float64TagValue)
- )
+ /*
+ The query used here needs to be as cheap as possible, and while uncommon, it is possible for
+ a tag to have 100s of millions of values (eg: message, request_id)
+
+ Construct a query to UNION the result of querying first `limit` values for each attribute. For example:
+ ```
+ select * from (
+ (
+ select tagKey, stringTagValue, int64TagValue, float64TagValue
+ from signoz_logs.distributed_tag_attributes
+ where tagKey = $1 and (
+ stringTagValue != '' or int64TagValue is not null or float64TagValue is not null
+ )
+ limit 2
+ ) UNION DISTINCT (
+ select tagKey, stringTagValue, int64TagValue, float64TagValue
+ from signoz_logs.distributed_tag_attributes
+ where tagKey = $2 and (
+ stringTagValue != '' or int64TagValue is not null or float64TagValue is not null
+ )
+ limit 2
+ )
+ ) settings max_threads=2
+ ```
+ Since tag_attributes table uses ReplacingMergeTree, the values would be distinct and no order by
+ is being used to ensure the `limit` clause minimizes the amount of data scanned.
+
+ This query scanned ~30k rows per attribute on fiscalnote-v2 for attributes like `message` and `time`
+ that had >~110M values each
+ */
+
+ if len(attributes) > 10 {
+ zap.L().Error(
+ "log attribute values requested for too many attributes. This can lead to slow and costly queries",
+ zap.Int("count", len(attributes)),
)
- where rank <= %d
- `,
- r.logsDB, r.logsTagAttributeTable, limit,
- )
+ attributes = attributes[:10]
+ }
+
+ tagQueries := []string{}
+ tagKeyQueryArgs := []any{}
+ for idx, attrib := range attributes {
+ tagQueries = append(tagQueries, fmt.Sprintf(`(
+ select tagKey, stringTagValue, int64TagValue, float64TagValue
+ from %s.%s
+ where tagKey = $%d and (
+ stringTagValue != '' or int64TagValue is not null or float64TagValue is not null
+ )
+ limit %d
+ )`, r.logsDB, r.logsTagAttributeTable, idx+1, limit))
- attribNames := []string{}
- for _, attrib := range attributes {
- attribNames = append(attribNames, attrib.Key)
+ tagKeyQueryArgs = append(tagKeyQueryArgs, attrib.Key)
}
- rows, err := r.db.Query(ctx, query, attribNames)
+ query := fmt.Sprintf(`select * from (
+ %s
+ ) settings max_threads=2`, strings.Join(tagQueries, " UNION DISTINCT "))
+
+ rows, err := r.db.Query(ctx, query, tagKeyQueryArgs...)
if err != nil {
zap.L().Error("couldn't query attrib values for suggestions", zap.Error(err))
return nil, model.InternalError(fmt.Errorf(
diff --git a/pkg/query-service/app/dashboards/model.go b/pkg/query-service/app/dashboards/model.go
index 1d7e51e820..989d266b51 100644
--- a/pkg/query-service/app/dashboards/model.go
+++ b/pkg/query-service/app/dashboards/model.go
@@ -453,6 +453,7 @@ func GetDashboardsInfo(ctx context.Context) (*model.DashboardsInfo, error) {
totalDashboardsWithPanelAndName := 0
var dashboardNames []string
count := 0
+ logChQueriesCount := 0
for _, dashboard := range dashboardsData {
if isDashboardWithPanelAndName(dashboard.Data) {
totalDashboardsWithPanelAndName = totalDashboardsWithPanelAndName + 1
@@ -468,12 +469,16 @@ func GetDashboardsInfo(ctx context.Context) (*model.DashboardsInfo, error) {
if isDashboardWithTSV2(dashboard.Data) {
count = count + 1
}
+ if isDashboardWithLogsClickhouseQuery(dashboard.Data) {
+ logChQueriesCount = logChQueriesCount + 1
+ }
}
dashboardsInfo.DashboardNames = dashboardNames
dashboardsInfo.TotalDashboards = len(dashboardsData)
dashboardsInfo.TotalDashboardsWithPanelAndName = totalDashboardsWithPanelAndName
dashboardsInfo.QueriesWithTSV2 = count
+ dashboardsInfo.DashboardsWithLogsChQuery = logChQueriesCount
return &dashboardsInfo, nil
}
@@ -485,6 +490,16 @@ func isDashboardWithTSV2(data map[string]interface{}) bool {
return strings.Contains(string(jsonData), "time_series_v2")
}
+func isDashboardWithLogsClickhouseQuery(data map[string]interface{}) bool {
+ jsonData, err := json.Marshal(data)
+ if err != nil {
+ return false
+ }
+ result := strings.Contains(string(jsonData), "signoz_logs.distributed_logs ") ||
+ strings.Contains(string(jsonData), "signoz_logs.logs ")
+ return result
+}
+
func isDashboardWithPanelAndName(data map[string]interface{}) bool {
isDashboardName := false
isDashboardWithPanelAndName := false
diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go
index 20852f1660..219181dc7f 100644
--- a/pkg/query-service/app/http_handler.go
+++ b/pkg/query-service/app/http_handler.go
@@ -41,6 +41,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/cache"
"go.signoz.io/signoz/pkg/query-service/common"
"go.signoz.io/signoz/pkg/query-service/constants"
+ "go.signoz.io/signoz/pkg/query-service/contextlinks"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/postprocess"
@@ -151,7 +152,7 @@ type APIHandlerOpts struct {
// NewAPIHandler returns an APIHandler
func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
- alertManager, err := am.New("")
+ alertManager, err := am.New()
if err != nil {
return nil, err
}
@@ -767,6 +768,48 @@ func (aH *APIHandler) getOverallStateTransitions(w http.ResponseWriter, r *http.
aH.Respond(w, stateItems)
}
+func (aH *APIHandler) metaForLinks(ctx context.Context, rule *rules.GettableRule) ([]v3.FilterItem, []v3.AttributeKey, map[string]v3.AttributeKey) {
+ filterItems := []v3.FilterItem{}
+ groupBy := []v3.AttributeKey{}
+ keys := make(map[string]v3.AttributeKey)
+
+ if rule.AlertType == rules.AlertTypeLogs {
+ logFields, err := aH.reader.GetLogFields(ctx)
+ if err == nil {
+ params := &v3.QueryRangeParamsV3{
+ CompositeQuery: rule.RuleCondition.CompositeQuery,
+ }
+ keys = model.GetLogFieldsV3(ctx, params, logFields)
+ } else {
+ zap.L().Error("failed to get log fields using empty keys; the link might not work as expected", zap.Error(err))
+ }
+ } else if rule.AlertType == rules.AlertTypeTraces {
+ traceFields, err := aH.reader.GetSpanAttributeKeys(ctx)
+ if err == nil {
+ keys = traceFields
+ } else {
+ zap.L().Error("failed to get span attributes using empty keys; the link might not work as expected", zap.Error(err))
+ }
+ }
+
+ if rule.AlertType == rules.AlertTypeLogs || rule.AlertType == rules.AlertTypeTraces {
+ if rule.RuleCondition.CompositeQuery != nil {
+ if rule.RuleCondition.QueryType() == v3.QueryTypeBuilder {
+ selectedQuery := rule.RuleCondition.GetSelectedQueryName()
+ if rule.RuleCondition.CompositeQuery.BuilderQueries[selectedQuery] != nil &&
+ rule.RuleCondition.CompositeQuery.BuilderQueries[selectedQuery].Filters != nil {
+ filterItems = rule.RuleCondition.CompositeQuery.BuilderQueries[selectedQuery].Filters.Items
+ }
+ if rule.RuleCondition.CompositeQuery.BuilderQueries[selectedQuery] != nil &&
+ rule.RuleCondition.CompositeQuery.BuilderQueries[selectedQuery].GroupBy != nil {
+ groupBy = rule.RuleCondition.CompositeQuery.BuilderQueries[selectedQuery].GroupBy
+ }
+ }
+ }
+ }
+ return filterItems, groupBy, keys
+}
+
func (aH *APIHandler) getRuleStateHistory(w http.ResponseWriter, r *http.Request) {
ruleID := mux.Vars(r)["id"]
params := model.QueryRuleStateHistory{}
@@ -794,24 +837,18 @@ func (aH *APIHandler) getRuleStateHistory(w http.ResponseWriter, r *http.Request
if err != nil {
continue
}
- filterItems := []v3.FilterItem{}
- if rule.AlertType == rules.AlertTypeLogs || rule.AlertType == rules.AlertTypeTraces {
- if rule.RuleCondition.CompositeQuery != nil {
- if rule.RuleCondition.QueryType() == v3.QueryTypeBuilder {
- for _, query := range rule.RuleCondition.CompositeQuery.BuilderQueries {
- if query.Filters != nil && len(query.Filters.Items) > 0 {
- filterItems = append(filterItems, query.Filters.Items...)
- }
- }
- }
- }
- }
- newFilters := common.PrepareFilters(lbls, filterItems)
- ts := time.Unix(res.Items[idx].UnixMilli/1000, 0)
+ filterItems, groupBy, keys := aH.metaForLinks(r.Context(), rule)
+ newFilters := contextlinks.PrepareFilters(lbls, filterItems, groupBy, keys)
+ end := time.Unix(res.Items[idx].UnixMilli/1000, 0)
+ // why are we subtracting 3 minutes?
+ // the query range is calculated based on the rule's evalWindow and evalDelay
+ // alerts have 2 minutes delay built in, so we need to subtract that from the start time
+ // to get the correct query range
+ start := end.Add(-time.Duration(rule.EvalWindow)).Add(-3 * time.Minute)
if rule.AlertType == rules.AlertTypeLogs {
- res.Items[idx].RelatedLogsLink = common.PrepareLinksToLogs(ts, newFilters)
+ res.Items[idx].RelatedLogsLink = contextlinks.PrepareLinksToLogs(start, end, newFilters)
} else if rule.AlertType == rules.AlertTypeTraces {
- res.Items[idx].RelatedTracesLink = common.PrepareLinksToTraces(ts, newFilters)
+ res.Items[idx].RelatedTracesLink = contextlinks.PrepareLinksToTraces(start, end, newFilters)
}
}
}
@@ -842,12 +879,14 @@ func (aH *APIHandler) getRuleStateHistoryTopContributors(w http.ResponseWriter,
if err != nil {
continue
}
- ts := time.Unix(params.End/1000, 0)
- filters := common.PrepareFilters(lbls, nil)
+ filterItems, groupBy, keys := aH.metaForLinks(r.Context(), rule)
+ newFilters := contextlinks.PrepareFilters(lbls, filterItems, groupBy, keys)
+ end := time.Unix(params.End/1000, 0)
+ start := time.Unix(params.Start/1000, 0)
if rule.AlertType == rules.AlertTypeLogs {
- res[idx].RelatedLogsLink = common.PrepareLinksToLogs(ts, filters)
+ res[idx].RelatedLogsLink = contextlinks.PrepareLinksToLogs(start, end, newFilters)
} else if rule.AlertType == rules.AlertTypeTraces {
- res[idx].RelatedTracesLink = common.PrepareLinksToTraces(ts, filters)
+ res[idx].RelatedTracesLink = contextlinks.PrepareLinksToTraces(start, end, newFilters)
}
}
}
@@ -1051,23 +1090,6 @@ func (aH *APIHandler) getDashboard(w http.ResponseWriter, r *http.Request) {
}
-func (aH *APIHandler) saveAndReturn(w http.ResponseWriter, r *http.Request, signozDashboard model.DashboardData) {
- toSave := make(map[string]interface{})
- toSave["title"] = signozDashboard.Title
- toSave["description"] = signozDashboard.Description
- toSave["tags"] = signozDashboard.Tags
- toSave["layout"] = signozDashboard.Layout
- toSave["widgets"] = signozDashboard.Widgets
- toSave["variables"] = signozDashboard.Variables
-
- dashboard, apiError := dashboards.CreateDashboard(r.Context(), toSave, aH.featureFlags)
- if apiError != nil {
- RespondError(w, apiError, nil)
- return
- }
- aH.Respond(w, dashboard)
-}
-
func (aH *APIHandler) createDashboards(w http.ResponseWriter, r *http.Request) {
var postData map[string]interface{}
@@ -1182,7 +1204,7 @@ func (aH *APIHandler) editRule(w http.ResponseWriter, r *http.Request) {
func (aH *APIHandler) getChannel(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
- channel, apiErrorObj := aH.reader.GetChannel(id)
+ channel, apiErrorObj := aH.ruleManager.RuleDB().GetChannel(id)
if apiErrorObj != nil {
RespondError(w, apiErrorObj, nil)
return
@@ -1192,7 +1214,7 @@ func (aH *APIHandler) getChannel(w http.ResponseWriter, r *http.Request) {
func (aH *APIHandler) deleteChannel(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
- apiErrorObj := aH.reader.DeleteChannel(id)
+ apiErrorObj := aH.ruleManager.RuleDB().DeleteChannel(id)
if apiErrorObj != nil {
RespondError(w, apiErrorObj, nil)
return
@@ -1201,7 +1223,7 @@ func (aH *APIHandler) deleteChannel(w http.ResponseWriter, r *http.Request) {
}
func (aH *APIHandler) listChannels(w http.ResponseWriter, r *http.Request) {
- channels, apiErrorObj := aH.reader.GetChannels()
+ channels, apiErrorObj := aH.ruleManager.RuleDB().GetChannels()
if apiErrorObj != nil {
RespondError(w, apiErrorObj, nil)
return
@@ -1254,7 +1276,7 @@ func (aH *APIHandler) editChannel(w http.ResponseWriter, r *http.Request) {
return
}
- _, apiErrorObj := aH.reader.EditChannel(receiver, id)
+ _, apiErrorObj := aH.ruleManager.RuleDB().EditChannel(receiver, id)
if apiErrorObj != nil {
RespondError(w, apiErrorObj, nil)
@@ -1282,7 +1304,7 @@ func (aH *APIHandler) createChannel(w http.ResponseWriter, r *http.Request) {
return
}
- _, apiErrorObj := aH.reader.CreateChannel(receiver)
+ _, apiErrorObj := aH.ruleManager.RuleDB().CreateChannel(receiver)
if apiErrorObj != nil {
RespondError(w, apiErrorObj, nil)
@@ -3527,55 +3549,6 @@ func (aH *APIHandler) autoCompleteAttributeValues(w http.ResponseWriter, r *http
aH.Respond(w, response)
}
-func (aH *APIHandler) getLogFieldsV3(ctx context.Context, queryRangeParams *v3.QueryRangeParamsV3) (map[string]v3.AttributeKey, error) {
- data := map[string]v3.AttributeKey{}
- for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
- if query.DataSource == v3.DataSourceLogs {
- fields, apiError := aH.reader.GetLogFields(ctx)
- if apiError != nil {
- return nil, apiError.Err
- }
-
- // top level fields meta will always be present in the frontend. (can be support for that as enchancement)
- getType := func(t string) (v3.AttributeKeyType, bool) {
- if t == "attributes" {
- return v3.AttributeKeyTypeTag, false
- } else if t == "resources" {
- return v3.AttributeKeyTypeResource, false
- }
- return "", true
- }
-
- for _, selectedField := range fields.Selected {
- fieldType, pass := getType(selectedField.Type)
- if pass {
- continue
- }
- data[selectedField.Name] = v3.AttributeKey{
- Key: selectedField.Name,
- Type: fieldType,
- DataType: v3.AttributeKeyDataType(strings.ToLower(selectedField.DataType)),
- IsColumn: true,
- }
- }
- for _, interestingField := range fields.Interesting {
- fieldType, pass := getType(interestingField.Type)
- if pass {
- continue
- }
- data[interestingField.Name] = v3.AttributeKey{
- Key: interestingField.Name,
- Type: fieldType,
- DataType: v3.AttributeKeyDataType(strings.ToLower(interestingField.DataType)),
- IsColumn: false,
- }
- }
- break
- }
- }
- return data, nil
-}
-
func (aH *APIHandler) getSpanKeysV3(ctx context.Context, queryRangeParams *v3.QueryRangeParamsV3) (map[string]v3.AttributeKey, error) {
data := map[string]v3.AttributeKey{}
for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
@@ -3617,14 +3590,14 @@ func (aH *APIHandler) queryRangeV3(ctx context.Context, queryRangeParams *v3.Que
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeBuilder {
// check if any enrichment is required for logs if yes then enrich them
if logsv3.EnrichmentRequired(queryRangeParams) {
- // get the fields if any logs query is present
- var fields map[string]v3.AttributeKey
- fields, err = aH.getLogFieldsV3(ctx, queryRangeParams)
+ logsFields, err := aH.reader.GetLogFields(ctx)
if err != nil {
apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err}
RespondError(w, apiErrObj, errQuriesByName)
return
}
+ // get the fields if any logs query is present
+ fields := model.GetLogFieldsV3(ctx, queryRangeParams, logsFields)
logsv3.Enrich(queryRangeParams, fields)
}
@@ -3656,15 +3629,19 @@ func (aH *APIHandler) queryRangeV3(ctx context.Context, queryRangeParams *v3.Que
// Hook up query progress tracking if requested
queryIdHeader := r.Header.Get("X-SIGNOZ-QUERY-ID")
if len(queryIdHeader) > 0 {
- ctx = context.WithValue(ctx, "queryId", queryIdHeader)
-
onQueryFinished, err := aH.reader.ReportQueryStartForProgressTracking(queryIdHeader)
+
if err != nil {
zap.L().Error(
"couldn't report query start for progress tracking",
zap.String("queryId", queryIdHeader), zap.Error(err),
)
+
} else {
+ // Adding queryId to the context signals clickhouse queries to report progress
+ //lint:ignore SA1029 ignore for now
+ ctx = context.WithValue(ctx, "queryId", queryIdHeader)
+
defer func() {
go onQueryFinished()
}()
@@ -3915,13 +3892,13 @@ func (aH *APIHandler) liveTailLogsV2(w http.ResponseWriter, r *http.Request) {
// check if any enrichment is required for logs if yes then enrich them
if logsv3.EnrichmentRequired(queryRangeParams) {
// get the fields if any logs query is present
- var fields map[string]v3.AttributeKey
- fields, err = aH.getLogFieldsV3(r.Context(), queryRangeParams)
+ logsFields, err := aH.reader.GetLogFields(r.Context())
if err != nil {
apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err}
RespondError(w, apiErrObj, nil)
return
}
+ fields := model.GetLogFieldsV3(r.Context(), queryRangeParams, logsFields)
logsv3.Enrich(queryRangeParams, fields)
}
@@ -4000,14 +3977,14 @@ func (aH *APIHandler) liveTailLogs(w http.ResponseWriter, r *http.Request) {
case v3.QueryTypeBuilder:
// check if any enrichment is required for logs if yes then enrich them
if logsv3.EnrichmentRequired(queryRangeParams) {
- // get the fields if any logs query is present
- var fields map[string]v3.AttributeKey
- fields, err = aH.getLogFieldsV3(r.Context(), queryRangeParams)
+ logsFields, err := aH.reader.GetLogFields(r.Context())
if err != nil {
apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err}
RespondError(w, apiErrObj, nil)
return
}
+ // get the fields if any logs query is present
+ fields := model.GetLogFieldsV3(r.Context(), queryRangeParams, logsFields)
logsv3.Enrich(queryRangeParams, fields)
}
@@ -4084,13 +4061,13 @@ func (aH *APIHandler) queryRangeV4(ctx context.Context, queryRangeParams *v3.Que
// check if any enrichment is required for logs if yes then enrich them
if logsv3.EnrichmentRequired(queryRangeParams) {
// get the fields if any logs query is present
- var fields map[string]v3.AttributeKey
- fields, err = aH.getLogFieldsV3(ctx, queryRangeParams)
+ logsFields, err := aH.reader.GetLogFields(r.Context())
if err != nil {
apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err}
- RespondError(w, apiErrObj, errQuriesByName)
+ RespondError(w, apiErrObj, nil)
return
}
+ fields := model.GetLogFieldsV3(r.Context(), queryRangeParams, logsFields)
logsv3.Enrich(queryRangeParams, fields)
}
diff --git a/pkg/query-service/app/logs/v3/enrich_query.go b/pkg/query-service/app/logs/v3/enrich_query.go
index 8a7bc85970..b8ed0ff801 100644
--- a/pkg/query-service/app/logs/v3/enrich_query.go
+++ b/pkg/query-service/app/logs/v3/enrich_query.go
@@ -94,11 +94,11 @@ func Enrich(params *v3.QueryRangeParamsV3, fields map[string]v3.AttributeKey) {
if query.Expression != queryName && query.DataSource != v3.DataSourceLogs {
continue
}
- enrichLogsQuery(query, fields)
+ EnrichLogsQuery(query, fields)
}
}
-func enrichLogsQuery(query *v3.BuilderQuery, fields map[string]v3.AttributeKey) error {
+func EnrichLogsQuery(query *v3.BuilderQuery, fields map[string]v3.AttributeKey) error {
// enrich aggregation attribute
if query.AggregateAttribute.Key != "" {
query.AggregateAttribute = enrichFieldWithMetadata(query.AggregateAttribute, fields)
diff --git a/pkg/query-service/app/logs/v4/query_builder.go b/pkg/query-service/app/logs/v4/query_builder.go
index 421b36aa62..47fda73c2a 100644
--- a/pkg/query-service/app/logs/v4/query_builder.go
+++ b/pkg/query-service/app/logs/v4/query_builder.go
@@ -80,9 +80,23 @@ func getSelectLabels(aggregatorOperator v3.AggregateOperator, groupBy []v3.Attri
func getExistsNexistsFilter(op v3.FilterOperator, item v3.FilterItem) string {
if _, ok := constants.StaticFieldsLogsV3[item.Key.Key]; ok && item.Key.Type == v3.AttributeKeyTypeUnspecified {
- // no exists filter for static fields as they exists everywhere
- // TODO(nitya): Think what we can do here
- return ""
+ // https://opentelemetry.io/docs/specs/otel/logs/data-model/
+ // for top level keys of the log model: trace_id, span_id, severity_number, trace_flags etc
+ // we don't have an exists column.
+ // to check if they exists/nexists
+ // we can use = 0 or != 0 for numbers
+ // we can use = '' or != '' for strings
+ chOp := "!="
+ if op == v3.FilterOperatorNotExists {
+ chOp = "="
+ }
+ key := getClickhouseKey(item.Key)
+ if item.Key.DataType == v3.AttributeKeyDataTypeString {
+ return fmt.Sprintf("%s %s ''", key, chOp)
+ }
+ // we just have two types, number and string for top level columns
+
+ return fmt.Sprintf("%s %s 0", key, chOp)
} else if item.Key.IsColumn {
// get filter for materialized columns
val := true
diff --git a/pkg/query-service/app/logs/v4/query_builder_test.go b/pkg/query-service/app/logs/v4/query_builder_test.go
index 7bc831437c..1b24a6aac6 100644
--- a/pkg/query-service/app/logs/v4/query_builder_test.go
+++ b/pkg/query-service/app/logs/v4/query_builder_test.go
@@ -147,7 +147,15 @@ func Test_getExistsNexistsFilter(t *testing.T) {
op: v3.FilterOperatorExists,
item: v3.FilterItem{Key: v3.AttributeKey{Key: "trace_id", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeUnspecified}},
},
- want: "",
+ want: "trace_id != ''",
+ },
+ {
+ name: "exists top level column- number",
+ args: args{
+ op: v3.FilterOperatorNotExists,
+ item: v3.FilterItem{Key: v3.AttributeKey{Key: "severity_number", DataType: v3.AttributeKeyDataTypeArrayFloat64, Type: v3.AttributeKeyTypeUnspecified}},
+ },
+ want: "severity_number = 0",
},
}
for _, tt := range tests {
diff --git a/pkg/query-service/app/traces/v3/query_builder.go b/pkg/query-service/app/traces/v3/query_builder.go
index 4f1ad86f23..c66b95ea56 100644
--- a/pkg/query-service/app/traces/v3/query_builder.go
+++ b/pkg/query-service/app/traces/v3/query_builder.go
@@ -545,25 +545,29 @@ func Enrich(params *v3.QueryRangeParamsV3, keys map[string]v3.AttributeKey) {
if params.CompositeQuery.QueryType == v3.QueryTypeBuilder {
for _, query := range params.CompositeQuery.BuilderQueries {
if query.DataSource == v3.DataSourceTraces {
- // enrich aggregate attribute
- query.AggregateAttribute = enrichKeyWithMetadata(query.AggregateAttribute, keys)
- // enrich filter items
- if query.Filters != nil && len(query.Filters.Items) > 0 {
- for idx, filter := range query.Filters.Items {
- query.Filters.Items[idx].Key = enrichKeyWithMetadata(filter.Key, keys)
- }
- }
- // enrich group by
- for idx, groupBy := range query.GroupBy {
- query.GroupBy[idx] = enrichKeyWithMetadata(groupBy, keys)
- }
- // enrich order by
- query.OrderBy = enrichOrderBy(query.OrderBy, keys)
- // enrich select columns
- for idx, selectColumn := range query.SelectColumns {
- query.SelectColumns[idx] = enrichKeyWithMetadata(selectColumn, keys)
- }
+ EnrichTracesQuery(query, keys)
}
}
}
}
+
+func EnrichTracesQuery(query *v3.BuilderQuery, keys map[string]v3.AttributeKey) {
+ // enrich aggregate attribute
+ query.AggregateAttribute = enrichKeyWithMetadata(query.AggregateAttribute, keys)
+ // enrich filter items
+ if query.Filters != nil && len(query.Filters.Items) > 0 {
+ for idx, filter := range query.Filters.Items {
+ query.Filters.Items[idx].Key = enrichKeyWithMetadata(filter.Key, keys)
+ }
+ }
+ // enrich group by
+ for idx, groupBy := range query.GroupBy {
+ query.GroupBy[idx] = enrichKeyWithMetadata(groupBy, keys)
+ }
+ // enrich order by
+ query.OrderBy = enrichOrderBy(query.OrderBy, keys)
+ // enrich select columns
+ for idx, selectColumn := range query.SelectColumns {
+ query.SelectColumns[idx] = enrichKeyWithMetadata(selectColumn, keys)
+ }
+}
diff --git a/pkg/query-service/app/traces/v3/utils.go b/pkg/query-service/app/traces/v3/utils.go
index cbd0940a16..7d4edd5223 100644
--- a/pkg/query-service/app/traces/v3/utils.go
+++ b/pkg/query-service/app/traces/v3/utils.go
@@ -8,6 +8,39 @@ import (
"go.uber.org/zap"
)
+var TracesListViewDefaultSelectedColumns = []v3.AttributeKey{
+ {
+ Key: "serviceName",
+ DataType: v3.AttributeKeyDataTypeString,
+ Type: v3.AttributeKeyTypeTag,
+ IsColumn: true,
+ },
+ {
+ Key: "name",
+ DataType: v3.AttributeKeyDataTypeString,
+ Type: v3.AttributeKeyTypeTag,
+ IsColumn: true,
+ },
+ {
+ Key: "durationNano",
+ DataType: v3.AttributeKeyDataTypeArrayFloat64,
+ Type: v3.AttributeKeyTypeTag,
+ IsColumn: true,
+ },
+ {
+ Key: "httpMethod",
+ DataType: v3.AttributeKeyDataTypeString,
+ Type: v3.AttributeKeyTypeTag,
+ IsColumn: true,
+ },
+ {
+ Key: "responseStatusCode",
+ DataType: v3.AttributeKeyDataTypeString,
+ Type: v3.AttributeKeyTypeTag,
+ IsColumn: true,
+ },
+}
+
// check if traceId filter is used in traces query and return the list of traceIds
func TraceIdFilterUsedWithEqual(params *v3.QueryRangeParamsV3) (bool, []string) {
compositeQuery := params.CompositeQuery
diff --git a/pkg/query-service/common/query_range.go b/pkg/query-service/common/query_range.go
index e0c675c50a..c352c7d9f2 100644
--- a/pkg/query-service/common/query_range.go
+++ b/pkg/query-service/common/query_range.go
@@ -1,10 +1,7 @@
package common
import (
- "encoding/json"
- "fmt"
"math"
- "net/url"
"time"
"go.signoz.io/signoz/pkg/query-service/constants"
@@ -73,183 +70,3 @@ func LCMList(nums []int64) int64 {
}
return result
}
-
-// TODO(srikanthccv): move the custom function in threshold_rule.go to here
-func PrepareLinksToTraces(ts time.Time, filterItems []v3.FilterItem) string {
-
- start := ts.Add(-time.Minute * 15)
- end := ts.Add(time.Minute * 15)
-
- // Traces list view expects time in nanoseconds
- tr := v3.URLShareableTimeRange{
- Start: start.UnixNano(),
- End: end.UnixNano(),
- PageSize: 100,
- }
-
- options := v3.URLShareableOptions{
- MaxLines: 2,
- Format: "list",
- SelectColumns: constants.TracesListViewDefaultSelectedColumns,
- }
-
- period, _ := json.Marshal(tr)
- urlEncodedTimeRange := url.QueryEscape(string(period))
-
- urlData := v3.URLShareableCompositeQuery{
- QueryType: string(v3.QueryTypeBuilder),
- Builder: v3.URLShareableBuilderQuery{
- QueryData: []v3.BuilderQuery{
- {
- DataSource: v3.DataSourceTraces,
- QueryName: "A",
- AggregateOperator: v3.AggregateOperatorNoOp,
- AggregateAttribute: v3.AttributeKey{},
- Filters: &v3.FilterSet{
- Items: filterItems,
- Operator: "AND",
- },
- Expression: "A",
- Disabled: false,
- Having: []v3.Having{},
- StepInterval: 60,
- OrderBy: []v3.OrderBy{
- {
- ColumnName: "timestamp",
- Order: "desc",
- },
- },
- },
- },
- QueryFormulas: make([]string, 0),
- },
- }
-
- data, _ := json.Marshal(urlData)
- compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
-
- optionsData, _ := json.Marshal(options)
- urlEncodedOptions := url.QueryEscape(string(optionsData))
-
- return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
-}
-
-func PrepareLinksToLogs(ts time.Time, filterItems []v3.FilterItem) string {
- start := ts.Add(-time.Minute * 15)
- end := ts.Add(time.Minute * 15)
-
- // Logs list view expects time in milliseconds
- // Logs list view expects time in milliseconds
- tr := v3.URLShareableTimeRange{
- Start: start.UnixMilli(),
- End: end.UnixMilli(),
- PageSize: 100,
- }
-
- options := v3.URLShareableOptions{
- MaxLines: 2,
- Format: "list",
- SelectColumns: []v3.AttributeKey{},
- }
-
- period, _ := json.Marshal(tr)
- urlEncodedTimeRange := url.QueryEscape(string(period))
-
- urlData := v3.URLShareableCompositeQuery{
- QueryType: string(v3.QueryTypeBuilder),
- Builder: v3.URLShareableBuilderQuery{
- QueryData: []v3.BuilderQuery{
- {
- DataSource: v3.DataSourceLogs,
- QueryName: "A",
- AggregateOperator: v3.AggregateOperatorNoOp,
- AggregateAttribute: v3.AttributeKey{},
- Filters: &v3.FilterSet{
- Items: filterItems,
- Operator: "AND",
- },
- Expression: "A",
- Disabled: false,
- Having: []v3.Having{},
- StepInterval: 60,
- OrderBy: []v3.OrderBy{
- {
- ColumnName: "timestamp",
- Order: "desc",
- },
- },
- },
- },
- QueryFormulas: make([]string, 0),
- },
- }
-
- data, _ := json.Marshal(urlData)
- compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
-
- optionsData, _ := json.Marshal(options)
- urlEncodedOptions := url.QueryEscape(string(optionsData))
-
- return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
-}
-
-// The following function is used to prepare the where clause for the query
-// `lbls` contains the key value pairs of the labels from the result of the query
-// We iterate over the where clause and replace the labels with the actual values
-// There are two cases:
-// 1. The label is present in the where clause
-// 2. The label is not present in the where clause
-//
-// Example for case 2:
-// Latency by serviceName without any filter
-// In this case, for each service with latency > threshold we send a notification
-// The expectation will be that clicking on the related traces for service A, will
-// take us to the traces page with the filter serviceName=A
-// So for all the missing labels in the where clause, we add them as key = value
-//
-// Example for case 1:
-// Severity text IN (WARN, ERROR)
-// In this case, the Severity text will appear in the `lbls` if it were part of the group
-// by clause, in which case we replace it with the actual value for the notification
-// i.e Severity text = WARN
-// If the Severity text is not part of the group by clause, then we add it as it is
-func PrepareFilters(labels map[string]string, filters []v3.FilterItem) []v3.FilterItem {
- var filterItems []v3.FilterItem
-
- added := make(map[string]struct{})
-
- for _, item := range filters {
- exists := false
- for key, value := range labels {
- if item.Key.Key == key {
- // if the label is present in the where clause, replace it with key = value
- filterItems = append(filterItems, v3.FilterItem{
- Key: item.Key,
- Operator: v3.FilterOperatorEqual,
- Value: value,
- })
- exists = true
- added[key] = struct{}{}
- break
- }
- }
-
- if !exists {
- // if the label is not present in the where clause, add it as it is
- filterItems = append(filterItems, item)
- }
- }
-
- // add the labels which are not present in the where clause
- for key, value := range labels {
- if _, ok := added[key]; !ok {
- filterItems = append(filterItems, v3.FilterItem{
- Key: v3.AttributeKey{Key: key},
- Operator: v3.FilterOperatorEqual,
- Value: value,
- })
- }
- }
-
- return filterItems
-}
diff --git a/pkg/query-service/constants/constants.go b/pkg/query-service/constants/constants.go
index 71a1e39032..78ee31e1a1 100644
--- a/pkg/query-service/constants/constants.go
+++ b/pkg/query-service/constants/constants.go
@@ -401,39 +401,6 @@ const TIMESTAMP = "timestamp"
const FirstQueryGraphLimit = "first_query_graph_limit"
const SecondQueryGraphLimit = "second_query_graph_limit"
-var TracesListViewDefaultSelectedColumns = []v3.AttributeKey{
- {
- Key: "serviceName",
- DataType: v3.AttributeKeyDataTypeString,
- Type: v3.AttributeKeyTypeTag,
- IsColumn: true,
- },
- {
- Key: "name",
- DataType: v3.AttributeKeyDataTypeString,
- Type: v3.AttributeKeyTypeTag,
- IsColumn: true,
- },
- {
- Key: "durationNano",
- DataType: v3.AttributeKeyDataTypeArrayFloat64,
- Type: v3.AttributeKeyTypeTag,
- IsColumn: true,
- },
- {
- Key: "httpMethod",
- DataType: v3.AttributeKeyDataTypeString,
- Type: v3.AttributeKeyTypeTag,
- IsColumn: true,
- },
- {
- Key: "responseStatusCode",
- DataType: v3.AttributeKeyDataTypeString,
- Type: v3.AttributeKeyTypeTag,
- IsColumn: true,
- },
-}
-
const DefaultFilterSuggestionsAttributesLimit = 50
const MaxFilterSuggestionsAttributesLimit = 100
const DefaultFilterSuggestionsExamplesLimit = 2
diff --git a/pkg/query-service/contextlinks/links.go b/pkg/query-service/contextlinks/links.go
new file mode 100644
index 0000000000..d0d8400e74
--- /dev/null
+++ b/pkg/query-service/contextlinks/links.go
@@ -0,0 +1,203 @@
+package contextlinks
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/url"
+ "time"
+
+ tracesV3 "go.signoz.io/signoz/pkg/query-service/app/traces/v3"
+ v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
+)
+
+func PrepareLinksToTraces(start, end time.Time, filterItems []v3.FilterItem) string {
+
+ // Traces list view expects time in nanoseconds
+ tr := v3.URLShareableTimeRange{
+ Start: start.UnixNano(),
+ End: end.UnixNano(),
+ PageSize: 100,
+ }
+
+ options := v3.URLShareableOptions{
+ MaxLines: 2,
+ Format: "list",
+ SelectColumns: tracesV3.TracesListViewDefaultSelectedColumns,
+ }
+
+ period, _ := json.Marshal(tr)
+ urlEncodedTimeRange := url.QueryEscape(string(period))
+
+ builderQuery := v3.BuilderQuery{
+ DataSource: v3.DataSourceTraces,
+ QueryName: "A",
+ AggregateOperator: v3.AggregateOperatorNoOp,
+ AggregateAttribute: v3.AttributeKey{},
+ Filters: &v3.FilterSet{
+ Items: filterItems,
+ Operator: "AND",
+ },
+ Expression: "A",
+ Disabled: false,
+ Having: []v3.Having{},
+ StepInterval: 60,
+ OrderBy: []v3.OrderBy{
+ {
+ ColumnName: "timestamp",
+ Order: "desc",
+ },
+ },
+ }
+
+ urlData := v3.URLShareableCompositeQuery{
+ QueryType: string(v3.QueryTypeBuilder),
+ Builder: v3.URLShareableBuilderQuery{
+ QueryData: []v3.BuilderQuery{
+ builderQuery,
+ },
+ QueryFormulas: make([]string, 0),
+ },
+ }
+
+ data, _ := json.Marshal(urlData)
+ compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
+
+ optionsData, _ := json.Marshal(options)
+ urlEncodedOptions := url.QueryEscape(string(optionsData))
+
+ return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
+}
+
+func PrepareLinksToLogs(start, end time.Time, filterItems []v3.FilterItem) string {
+
+ // Logs list view expects time in milliseconds
+ tr := v3.URLShareableTimeRange{
+ Start: start.UnixMilli(),
+ End: end.UnixMilli(),
+ PageSize: 100,
+ }
+
+ options := v3.URLShareableOptions{
+ MaxLines: 2,
+ Format: "list",
+ SelectColumns: []v3.AttributeKey{},
+ }
+
+ period, _ := json.Marshal(tr)
+ urlEncodedTimeRange := url.QueryEscape(string(period))
+
+ builderQuery := v3.BuilderQuery{
+ DataSource: v3.DataSourceLogs,
+ QueryName: "A",
+ AggregateOperator: v3.AggregateOperatorNoOp,
+ AggregateAttribute: v3.AttributeKey{},
+ Filters: &v3.FilterSet{
+ Items: filterItems,
+ Operator: "AND",
+ },
+ Expression: "A",
+ Disabled: false,
+ Having: []v3.Having{},
+ StepInterval: 60,
+ OrderBy: []v3.OrderBy{
+ {
+ ColumnName: "timestamp",
+ Order: "desc",
+ },
+ },
+ }
+
+ urlData := v3.URLShareableCompositeQuery{
+ QueryType: string(v3.QueryTypeBuilder),
+ Builder: v3.URLShareableBuilderQuery{
+ QueryData: []v3.BuilderQuery{
+ builderQuery,
+ },
+ QueryFormulas: make([]string, 0),
+ },
+ }
+
+ data, _ := json.Marshal(urlData)
+ compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
+
+ optionsData, _ := json.Marshal(options)
+ urlEncodedOptions := url.QueryEscape(string(optionsData))
+
+ return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
+}
+
+// The following function is used to prepare the where clause for the query
+// `lbls` contains the key value pairs of the labels from the result of the query
+// We iterate over the where clause and replace the labels with the actual values
+// There are two cases:
+// 1. The label is present in the where clause
+// 2. The label is not present in the where clause
+//
+// Example for case 2:
+// Latency by serviceName without any filter
+// In this case, for each service with latency > threshold we send a notification
+// The expectation will be that clicking on the related traces for service A, will
+// take us to the traces page with the filter serviceName=A
+// So for all the missing labels in the where clause, we add them as key = value
+//
+// Example for case 1:
+// Severity text IN (WARN, ERROR)
+// In this case, the Severity text will appear in the `lbls` if it were part of the group
+// by clause, in which case we replace it with the actual value for the notification
+// i.e Severity text = WARN
+// If the Severity text is not part of the group by clause, then we add it as it is
+func PrepareFilters(labels map[string]string, whereClauseItems []v3.FilterItem, groupByItems []v3.AttributeKey, keys map[string]v3.AttributeKey) []v3.FilterItem {
+ var filterItems []v3.FilterItem
+
+ added := make(map[string]struct{})
+
+ for _, item := range whereClauseItems {
+ exists := false
+ for key, value := range labels {
+ if item.Key.Key == key {
+ // if the label is present in the where clause, replace it with key = value
+ filterItems = append(filterItems, v3.FilterItem{
+ Key: item.Key,
+ Operator: v3.FilterOperatorEqual,
+ Value: value,
+ })
+ exists = true
+ added[key] = struct{}{}
+ break
+ }
+ }
+
+ if !exists {
+ // if there is no label for the filter item, add it as it is
+ filterItems = append(filterItems, item)
+ }
+ }
+
+ // if there are labels which are not part of the where clause, but
+ // exist in the result, then they could be part of the group by clause
+ for key, value := range labels {
+ if _, ok := added[key]; !ok {
+ // start by taking the attribute key from the keys map, if not present, create a new one
+ attributeKey, ok := keys[key]
+ if !ok {
+ attributeKey = v3.AttributeKey{Key: key}
+ }
+
+ // if there is a group by item with the same key, use that instead
+ for _, groupByItem := range groupByItems {
+ if groupByItem.Key == key {
+ attributeKey = groupByItem
+ break
+ }
+ }
+
+ filterItems = append(filterItems, v3.FilterItem{
+ Key: attributeKey,
+ Operator: v3.FilterOperatorEqual,
+ Value: value,
+ })
+ }
+ }
+
+ return filterItems
+}
diff --git a/pkg/query-service/integrations/alertManager/manager.go b/pkg/query-service/integrations/alertManager/manager.go
index d80893010e..10db4debd7 100644
--- a/pkg/query-service/integrations/alertManager/manager.go
+++ b/pkg/query-service/integrations/alertManager/manager.go
@@ -24,42 +24,62 @@ type Manager interface {
TestReceiver(receiver *Receiver) *model.ApiError
}
-func New(url string) (Manager, error) {
-
- if url == "" {
- url = constants.GetAlertManagerApiPrefix()
+func defaultOptions() []ManagerOptions {
+ return []ManagerOptions{
+ WithURL(constants.GetAlertManagerApiPrefix()),
+ WithChannelApiPath(constants.AmChannelApiPath),
}
+}
- urlParsed, err := neturl.Parse(url)
- if err != nil {
- return nil, err
+type ManagerOptions func(m *manager) error
+
+func New(opts ...ManagerOptions) (Manager, error) {
+ m := &manager{}
+
+ newOpts := defaultOptions()
+ newOpts = append(newOpts, opts...)
+
+ for _, opt := range newOpts {
+ err := opt(m)
+ if err != nil {
+ return nil, err
+ }
}
- return &manager{
- url: url,
- parsedURL: urlParsed,
- }, nil
+ return m, nil
}
-type manager struct {
- url string
- parsedURL *neturl.URL
+func WithURL(url string) ManagerOptions {
+ return func(m *manager) error {
+ m.url = url
+ parsedURL, err := neturl.Parse(url)
+ if err != nil {
+ return err
+ }
+ m.parsedURL = parsedURL
+ return nil
+ }
}
-func prepareAmChannelApiURL() string {
- basePath := constants.GetAlertManagerApiPrefix()
- AmChannelApiPath := constants.AmChannelApiPath
-
- if len(AmChannelApiPath) > 0 && rune(AmChannelApiPath[0]) == rune('/') {
- AmChannelApiPath = AmChannelApiPath[1:]
+func WithChannelApiPath(path string) ManagerOptions {
+ return func(m *manager) error {
+ m.channelApiPath = path
+ return nil
}
+}
+
+type manager struct {
+ url string
+ parsedURL *neturl.URL
+ channelApiPath string
+}
- return fmt.Sprintf("%s%s", basePath, AmChannelApiPath)
+func (m *manager) prepareAmChannelApiURL() string {
+ return fmt.Sprintf("%s%s", m.url, m.channelApiPath)
}
-func prepareTestApiURL() string {
- basePath := constants.GetAlertManagerApiPrefix()
- return fmt.Sprintf("%s%s", basePath, "v1/testReceiver")
+func (m *manager) prepareTestApiURL() string {
+ return fmt.Sprintf("%s%s", m.url, "v1/testReceiver")
}
func (m *manager) URL() *neturl.URL {
@@ -79,7 +99,7 @@ func (m *manager) AddRoute(receiver *Receiver) *model.ApiError {
receiverString, _ := json.Marshal(receiver)
- amURL := prepareAmChannelApiURL()
+ amURL := m.prepareAmChannelApiURL()
response, err := http.Post(amURL, contentType, bytes.NewBuffer(receiverString))
if err != nil {
@@ -97,7 +117,7 @@ func (m *manager) AddRoute(receiver *Receiver) *model.ApiError {
func (m *manager) EditRoute(receiver *Receiver) *model.ApiError {
receiverString, _ := json.Marshal(receiver)
- amURL := prepareAmChannelApiURL()
+ amURL := m.prepareAmChannelApiURL()
req, err := http.NewRequest(http.MethodPut, amURL, bytes.NewBuffer(receiverString))
if err != nil {
@@ -126,7 +146,7 @@ func (m *manager) DeleteRoute(name string) *model.ApiError {
values := map[string]string{"name": name}
requestData, _ := json.Marshal(values)
- amURL := prepareAmChannelApiURL()
+ amURL := m.prepareAmChannelApiURL()
req, err := http.NewRequest(http.MethodDelete, amURL, bytes.NewBuffer(requestData))
if err != nil {
@@ -156,7 +176,7 @@ func (m *manager) TestReceiver(receiver *Receiver) *model.ApiError {
receiverBytes, _ := json.Marshal(receiver)
- amTestURL := prepareTestApiURL()
+ amTestURL := m.prepareTestApiURL()
response, err := http.Post(amTestURL, contentType, bytes.NewBuffer(receiverBytes))
if err != nil {
diff --git a/pkg/query-service/integrations/alertManager/notifier.go b/pkg/query-service/integrations/alertManager/notifier.go
index e29879f10a..434e2bc112 100644
--- a/pkg/query-service/integrations/alertManager/notifier.go
+++ b/pkg/query-service/integrations/alertManager/notifier.go
@@ -295,7 +295,7 @@ func newAlertmanagerSet(urls []string, timeout time.Duration, logger log.Logger)
ams := []Manager{}
for _, u := range urls {
- am, err := New(u)
+ am, err := New(WithURL(u))
if err != nil {
level.Error(s.logger).Log(fmt.Sprintf("invalid alert manager url %s: %s", u, err))
} else {
diff --git a/pkg/query-service/interfaces/interface.go b/pkg/query-service/interfaces/interface.go
index dd5b26151c..db2563edab 100644
--- a/pkg/query-service/interfaces/interface.go
+++ b/pkg/query-service/interfaces/interface.go
@@ -8,18 +8,11 @@ import (
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/util/stats"
- am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
"go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
)
type Reader interface {
- GetChannel(id string) (*model.ChannelItem, *model.ApiError)
- GetChannels() (*[]model.ChannelItem, *model.ApiError)
- DeleteChannel(id string) *model.ApiError
- CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError)
- EditChannel(receiver *am.Receiver, id string) (*am.Receiver, *model.ApiError)
-
GetInstantQueryMetricsResult(ctx context.Context, query *model.InstantQueryMetricsParams) (*promql.Result, *stats.QueryStats, *model.ApiError)
GetQueryRangeResult(ctx context.Context, query *model.QueryRangeParams) (*promql.Result, *stats.QueryStats, *model.ApiError)
GetServiceOverview(ctx context.Context, query *model.GetServiceOverviewParams, skipConfig *model.SkipConfig) (*[]model.ServiceOverviewItem, *model.ApiError)
diff --git a/pkg/query-service/model/logs.go b/pkg/query-service/model/logs.go
index ef1c7ff2e4..0319581516 100644
--- a/pkg/query-service/model/logs.go
+++ b/pkg/query-service/model/logs.go
@@ -1,5 +1,12 @@
package model
+import (
+ "context"
+ "strings"
+
+ v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
+)
+
type LogsLiveTailClientV2 struct {
Name string
Logs chan *SignozLogV2
@@ -21,3 +28,48 @@ type QueryProgress struct {
ElapsedMs uint64 `json:"elapsed_ms"`
}
+
+func GetLogFieldsV3(ctx context.Context, queryRangeParams *v3.QueryRangeParamsV3, fields *GetFieldsResponse) map[string]v3.AttributeKey {
+ data := map[string]v3.AttributeKey{}
+ for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
+ if query.DataSource == v3.DataSourceLogs {
+
+ // top level fields meta will always be present in the frontend. (can be support for that as enchancement)
+ getType := func(t string) (v3.AttributeKeyType, bool) {
+ if t == "attributes" {
+ return v3.AttributeKeyTypeTag, false
+ } else if t == "resources" {
+ return v3.AttributeKeyTypeResource, false
+ }
+ return "", true
+ }
+
+ for _, selectedField := range fields.Selected {
+ fieldType, pass := getType(selectedField.Type)
+ if pass {
+ continue
+ }
+ data[selectedField.Name] = v3.AttributeKey{
+ Key: selectedField.Name,
+ Type: fieldType,
+ DataType: v3.AttributeKeyDataType(strings.ToLower(selectedField.DataType)),
+ IsColumn: true,
+ }
+ }
+ for _, interestingField := range fields.Interesting {
+ fieldType, pass := getType(interestingField.Type)
+ if pass {
+ continue
+ }
+ data[interestingField.Name] = v3.AttributeKey{
+ Key: interestingField.Name,
+ Type: fieldType,
+ DataType: v3.AttributeKeyDataType(strings.ToLower(interestingField.DataType)),
+ IsColumn: false,
+ }
+ }
+ break
+ }
+ }
+ return data
+}
diff --git a/pkg/query-service/model/response.go b/pkg/query-service/model/response.go
index 1b86ff7e8b..03e538879c 100644
--- a/pkg/query-service/model/response.go
+++ b/pkg/query-service/model/response.go
@@ -618,6 +618,7 @@ type AlertsInfo struct {
LogsBasedAlerts int `json:"logsBasedAlerts"`
MetricBasedAlerts int `json:"metricBasedAlerts"`
TracesBasedAlerts int `json:"tracesBasedAlerts"`
+ TotalChannels int `json:"totalChannels"`
SlackChannels int `json:"slackChannels"`
WebHookChannels int `json:"webHookChannels"`
PagerDutyChannels int `json:"pagerDutyChannels"`
diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go
index 9f6086a169..2d99118533 100644
--- a/pkg/query-service/model/v3/v3.go
+++ b/pkg/query-service/model/v3/v3.go
@@ -370,6 +370,22 @@ type QueryRangeParamsV3 struct {
FormatForWeb bool `json:"formatForWeb,omitempty"`
}
+func (q *QueryRangeParamsV3) Clone() *QueryRangeParamsV3 {
+ if q == nil {
+ return nil
+ }
+ return &QueryRangeParamsV3{
+ Start: q.Start,
+ End: q.End,
+ Step: q.Step,
+ CompositeQuery: q.CompositeQuery.Clone(),
+ Variables: q.Variables,
+ NoCache: q.NoCache,
+ Version: q.Version,
+ FormatForWeb: q.FormatForWeb,
+ }
+}
+
type PromQuery struct {
Query string `json:"query"`
Stats string `json:"stats,omitempty"`
@@ -377,6 +393,18 @@ type PromQuery struct {
Legend string `json:"legend,omitempty"`
}
+func (p *PromQuery) Clone() *PromQuery {
+ if p == nil {
+ return nil
+ }
+ return &PromQuery{
+ Query: p.Query,
+ Stats: p.Stats,
+ Disabled: p.Disabled,
+ Legend: p.Legend,
+ }
+}
+
func (p *PromQuery) Validate() error {
if p == nil {
return nil
@@ -395,6 +423,16 @@ type ClickHouseQuery struct {
Legend string `json:"legend,omitempty"`
}
+func (c *ClickHouseQuery) Clone() *ClickHouseQuery {
+ if c == nil {
+ return nil
+ }
+ return &ClickHouseQuery{
+ Query: c.Query,
+ Disabled: c.Disabled,
+ Legend: c.Legend,
+ }
+}
func (c *ClickHouseQuery) Validate() error {
if c == nil {
return nil
@@ -420,6 +458,43 @@ type CompositeQuery struct {
FillGaps bool `json:"fillGaps,omitempty"`
}
+func (c *CompositeQuery) Clone() *CompositeQuery {
+ if c == nil {
+ return nil
+ }
+ var builderQueries map[string]*BuilderQuery
+ if c.BuilderQueries != nil {
+ builderQueries = make(map[string]*BuilderQuery)
+ for name, query := range c.BuilderQueries {
+ builderQueries[name] = query.Clone()
+ }
+ }
+ var clickHouseQueries map[string]*ClickHouseQuery
+ if c.ClickHouseQueries != nil {
+ clickHouseQueries = make(map[string]*ClickHouseQuery)
+ for name, query := range c.ClickHouseQueries {
+ clickHouseQueries[name] = query.Clone()
+ }
+ }
+ var promQueries map[string]*PromQuery
+ if c.PromQueries != nil {
+ promQueries = make(map[string]*PromQuery)
+ for name, query := range c.PromQueries {
+ promQueries[name] = query.Clone()
+ }
+ }
+ return &CompositeQuery{
+ BuilderQueries: builderQueries,
+ ClickHouseQueries: clickHouseQueries,
+ PromQueries: promQueries,
+ PanelType: c.PanelType,
+ QueryType: c.QueryType,
+ Unit: c.Unit,
+ FillGaps: c.FillGaps,
+ }
+
+}
+
func (c *CompositeQuery) EnabledQueries() int {
count := 0
switch c.QueryType {
@@ -645,6 +720,7 @@ const (
FunctionNameMedian5 FunctionName = "median5"
FunctionNameMedian7 FunctionName = "median7"
FunctionNameTimeShift FunctionName = "timeShift"
+ FunctionNameAnomaly FunctionName = "anomaly"
)
func (f FunctionName) Validate() error {
@@ -664,7 +740,8 @@ func (f FunctionName) Validate() error {
FunctionNameMedian3,
FunctionNameMedian5,
FunctionNameMedian7,
- FunctionNameTimeShift:
+ FunctionNameTimeShift,
+ FunctionNameAnomaly:
return nil
default:
return fmt.Errorf("invalid function name: %s", f)
@@ -672,33 +749,68 @@ func (f FunctionName) Validate() error {
}
type Function struct {
- Name FunctionName `json:"name"`
- Args []interface{} `json:"args,omitempty"`
+ Name FunctionName `json:"name"`
+ Args []interface{} `json:"args,omitempty"`
+ NamedArgs map[string]interface{} `json:"namedArgs,omitempty"`
}
type BuilderQuery struct {
- QueryName string `json:"queryName"`
- StepInterval int64 `json:"stepInterval"`
- DataSource DataSource `json:"dataSource"`
- AggregateOperator AggregateOperator `json:"aggregateOperator"`
- AggregateAttribute AttributeKey `json:"aggregateAttribute,omitempty"`
- Temporality Temporality `json:"temporality,omitempty"`
- Filters *FilterSet `json:"filters,omitempty"`
- GroupBy []AttributeKey `json:"groupBy,omitempty"`
- Expression string `json:"expression"`
- Disabled bool `json:"disabled"`
- Having []Having `json:"having,omitempty"`
- Legend string `json:"legend,omitempty"`
- Limit uint64 `json:"limit"`
- Offset uint64 `json:"offset"`
- PageSize uint64 `json:"pageSize"`
- OrderBy []OrderBy `json:"orderBy,omitempty"`
- ReduceTo ReduceToOperator `json:"reduceTo,omitempty"`
- SelectColumns []AttributeKey `json:"selectColumns,omitempty"`
- TimeAggregation TimeAggregation `json:"timeAggregation,omitempty"`
- SpaceAggregation SpaceAggregation `json:"spaceAggregation,omitempty"`
- Functions []Function `json:"functions,omitempty"`
- ShiftBy int64
+ QueryName string `json:"queryName"`
+ StepInterval int64 `json:"stepInterval"`
+ DataSource DataSource `json:"dataSource"`
+ AggregateOperator AggregateOperator `json:"aggregateOperator"`
+ AggregateAttribute AttributeKey `json:"aggregateAttribute,omitempty"`
+ Temporality Temporality `json:"temporality,omitempty"`
+ Filters *FilterSet `json:"filters,omitempty"`
+ GroupBy []AttributeKey `json:"groupBy,omitempty"`
+ Expression string `json:"expression"`
+ Disabled bool `json:"disabled"`
+ Having []Having `json:"having,omitempty"`
+ Legend string `json:"legend,omitempty"`
+ Limit uint64 `json:"limit"`
+ Offset uint64 `json:"offset"`
+ PageSize uint64 `json:"pageSize"`
+ OrderBy []OrderBy `json:"orderBy,omitempty"`
+ ReduceTo ReduceToOperator `json:"reduceTo,omitempty"`
+ SelectColumns []AttributeKey `json:"selectColumns,omitempty"`
+ TimeAggregation TimeAggregation `json:"timeAggregation,omitempty"`
+ SpaceAggregation SpaceAggregation `json:"spaceAggregation,omitempty"`
+ Functions []Function `json:"functions,omitempty"`
+ ShiftBy int64
+ IsAnomaly bool
+ QueriesUsedInFormula []string
+}
+
+func (b *BuilderQuery) Clone() *BuilderQuery {
+ if b == nil {
+ return nil
+ }
+ return &BuilderQuery{
+ QueryName: b.QueryName,
+ StepInterval: b.StepInterval,
+ DataSource: b.DataSource,
+ AggregateOperator: b.AggregateOperator,
+ AggregateAttribute: b.AggregateAttribute,
+ Temporality: b.Temporality,
+ Filters: b.Filters.Clone(),
+ GroupBy: b.GroupBy,
+ Expression: b.Expression,
+ Disabled: b.Disabled,
+ Having: b.Having,
+ Legend: b.Legend,
+ Limit: b.Limit,
+ Offset: b.Offset,
+ PageSize: b.PageSize,
+ OrderBy: b.OrderBy,
+ ReduceTo: b.ReduceTo,
+ SelectColumns: b.SelectColumns,
+ TimeAggregation: b.TimeAggregation,
+ SpaceAggregation: b.SpaceAggregation,
+ Functions: b.Functions,
+ ShiftBy: b.ShiftBy,
+ IsAnomaly: b.IsAnomaly,
+ QueriesUsedInFormula: b.QueriesUsedInFormula,
+ }
}
// CanDefaultZero returns true if the missing value can be substituted by zero
@@ -877,6 +989,16 @@ type FilterSet struct {
Items []FilterItem `json:"items"`
}
+func (f *FilterSet) Clone() *FilterSet {
+ if f == nil {
+ return nil
+ }
+ return &FilterSet{
+ Operator: f.Operator,
+ Items: f.Items,
+ }
+}
+
func (f *FilterSet) Validate() error {
if f == nil {
return nil
@@ -1028,12 +1150,15 @@ type Table struct {
}
type Result struct {
- QueryName string `json:"queryName,omitempty"`
- Series []*Series `json:"series,omitempty"`
- PredictedSeries []*Series `json:"predictedSeries,omitempty"`
- AnomalyScores []*Series `json:"anomalyScores,omitempty"`
- List []*Row `json:"list,omitempty"`
- Table *Table `json:"table,omitempty"`
+ QueryName string `json:"queryName,omitempty"`
+ Series []*Series `json:"series,omitempty"`
+ PredictedSeries []*Series `json:"predictedSeries,omitempty"`
+ UpperBoundSeries []*Series `json:"upperBoundSeries,omitempty"`
+ LowerBoundSeries []*Series `json:"lowerBoundSeries,omitempty"`
+ AnomalyScores []*Series `json:"anomalyScores,omitempty"`
+ List []*Row `json:"list,omitempty"`
+ Table *Table `json:"table,omitempty"`
+ IsAnomaly bool `json:"isAnomaly,omitempty"`
}
type Series struct {
diff --git a/pkg/query-service/rules/alerting.go b/pkg/query-service/rules/alerting.go
index f6826ed3d8..cb5205f99e 100644
--- a/pkg/query-service/rules/alerting.go
+++ b/pkg/query-service/rules/alerting.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/url"
+ "sort"
"strings"
"time"
@@ -124,6 +125,47 @@ type RuleCondition struct {
SelectedQuery string `json:"selectedQueryName,omitempty"`
}
+func (rc *RuleCondition) GetSelectedQueryName() string {
+ if rc != nil {
+ if rc.SelectedQuery != "" {
+ return rc.SelectedQuery
+ }
+
+ queryNames := map[string]struct{}{}
+
+ if rc.CompositeQuery != nil {
+ if rc.QueryType() == v3.QueryTypeBuilder {
+ for name := range rc.CompositeQuery.BuilderQueries {
+ queryNames[name] = struct{}{}
+ }
+ } else if rc.QueryType() == v3.QueryTypeClickHouseSQL {
+ for name := range rc.CompositeQuery.ClickHouseQueries {
+ queryNames[name] = struct{}{}
+ }
+ }
+ }
+
+ // The following logic exists for backward compatibility
+ // If there is no selected query, then
+ // - check if F1 is present, if yes, return F1
+ // - else return the query with max ascii value
+ // this logic is not really correct. we should be considering
+ // whether the query is enabled or not. but this is a temporary
+ // fix to support backward compatibility
+ if _, ok := queryNames["F1"]; ok {
+ return "F1"
+ }
+ keys := make([]string, 0, len(queryNames))
+ for k := range queryNames {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ return keys[len(keys)-1]
+ }
+ // This should never happen
+ return ""
+}
+
func (rc *RuleCondition) IsValid() bool {
if rc.CompositeQuery == nil {
diff --git a/pkg/query-service/rules/base_rule.go b/pkg/query-service/rules/base_rule.go
index 6fbaa655c7..a108938b1d 100644
--- a/pkg/query-service/rules/base_rule.go
+++ b/pkg/query-service/rules/base_rule.go
@@ -202,6 +202,21 @@ func (r *BaseRule) Unit() string {
return ""
}
+func (r *BaseRule) Timestamps(ts time.Time) (time.Time, time.Time) {
+ start := ts.Add(-time.Duration(r.evalWindow)).UnixMilli()
+ end := ts.UnixMilli()
+
+ if r.evalDelay > 0 {
+ start = start - int64(r.evalDelay.Milliseconds())
+ end = end - int64(r.evalDelay.Milliseconds())
+ }
+ // round to minute otherwise we could potentially miss data
+ start = start - (start % (60 * 1000))
+ end = end - (end % (60 * 1000))
+
+ return time.UnixMilli(start), time.UnixMilli(end)
+}
+
func (r *BaseRule) SetLastError(err error) {
r.mtx.Lock()
defer r.mtx.Unlock()
diff --git a/pkg/query-service/rules/db.go b/pkg/query-service/rules/db.go
index f3a9de1c62..e6f8d6301c 100644
--- a/pkg/query-service/rules/db.go
+++ b/pkg/query-service/rules/db.go
@@ -11,6 +11,7 @@ import (
"github.com/jmoiron/sqlx"
"go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/common"
+ am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
"go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.uber.org/zap"
@@ -18,6 +19,12 @@ import (
// Data store to capture user alert rule settings
type RuleDB interface {
+ GetChannel(id string) (*model.ChannelItem, *model.ApiError)
+ GetChannels() (*[]model.ChannelItem, *model.ApiError)
+ DeleteChannel(id string) *model.ApiError
+ CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError)
+ EditChannel(receiver *am.Receiver, id string) (*am.Receiver, *model.ApiError)
+
// CreateRuleTx stores rule in the db and returns tx and group name (on success)
CreateRuleTx(ctx context.Context, rule string) (int64, Tx, error)
@@ -68,13 +75,15 @@ type Tx interface {
type ruleDB struct {
*sqlx.DB
+ alertManager am.Manager
}
// todo: move init methods for creating tables
-func NewRuleDB(db *sqlx.DB) RuleDB {
+func NewRuleDB(db *sqlx.DB, alertManager am.Manager) RuleDB {
return &ruleDB{
db,
+ alertManager,
}
}
@@ -303,6 +312,229 @@ func (r *ruleDB) EditPlannedMaintenance(ctx context.Context, maintenance Planned
return "", nil
}
+func getChannelType(receiver *am.Receiver) string {
+
+ if receiver.EmailConfigs != nil {
+ return "email"
+ }
+ if receiver.OpsGenieConfigs != nil {
+ return "opsgenie"
+ }
+ if receiver.PagerdutyConfigs != nil {
+ return "pagerduty"
+ }
+ if receiver.PushoverConfigs != nil {
+ return "pushover"
+ }
+ if receiver.SNSConfigs != nil {
+ return "sns"
+ }
+ if receiver.SlackConfigs != nil {
+ return "slack"
+ }
+ if receiver.VictorOpsConfigs != nil {
+ return "victorops"
+ }
+ if receiver.WebhookConfigs != nil {
+ return "webhook"
+ }
+ if receiver.WechatConfigs != nil {
+ return "wechat"
+ }
+ if receiver.MSTeamsConfigs != nil {
+ return "msteams"
+ }
+ return ""
+}
+
+func (r *ruleDB) GetChannel(id string) (*model.ChannelItem, *model.ApiError) {
+
+ idInt, _ := strconv.Atoi(id)
+ channel := model.ChannelItem{}
+
+ query := "SELECT id, created_at, updated_at, name, type, data data FROM notification_channels WHERE id=?;"
+
+ stmt, err := r.Preparex(query)
+
+ if err != nil {
+ zap.L().Error("Error in preparing sql query for GetChannel", zap.Error(err))
+ return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+
+ err = stmt.Get(&channel, idInt)
+
+ if err != nil {
+ zap.L().Error("Error in getting channel with id", zap.Int("id", idInt), zap.Error(err))
+ return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+
+ return &channel, nil
+}
+
+func (r *ruleDB) DeleteChannel(id string) *model.ApiError {
+
+ idInt, _ := strconv.Atoi(id)
+
+ channelToDelete, apiErrorObj := r.GetChannel(id)
+
+ if apiErrorObj != nil {
+ return apiErrorObj
+ }
+
+ tx, err := r.Begin()
+ if err != nil {
+ return &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+
+ {
+ stmt, err := tx.Prepare(`DELETE FROM notification_channels WHERE id=$1;`)
+ if err != nil {
+ zap.L().Error("Error in preparing statement for INSERT to notification_channels", zap.Error(err))
+ tx.Rollback()
+ return &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+ defer stmt.Close()
+
+ if _, err := stmt.Exec(idInt); err != nil {
+ zap.L().Error("Error in Executing prepared statement for INSERT to notification_channels", zap.Error(err))
+ tx.Rollback() // return an error too, we may want to wrap them
+ return &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+ }
+
+ apiError := r.alertManager.DeleteRoute(channelToDelete.Name)
+ if apiError != nil {
+ tx.Rollback()
+ return apiError
+ }
+
+ err = tx.Commit()
+ if err != nil {
+ zap.L().Error("Error in committing transaction for DELETE command to notification_channels", zap.Error(err))
+ return &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+
+ return nil
+
+}
+
+func (r *ruleDB) GetChannels() (*[]model.ChannelItem, *model.ApiError) {
+
+ channels := []model.ChannelItem{}
+
+ query := "SELECT id, created_at, updated_at, name, type, data data FROM notification_channels"
+
+ err := r.Select(&channels, query)
+
+ zap.L().Info(query)
+
+ if err != nil {
+ zap.L().Error("Error in processing sql query", zap.Error(err))
+ return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+
+ return &channels, nil
+
+}
+
+func (r *ruleDB) EditChannel(receiver *am.Receiver, id string) (*am.Receiver, *model.ApiError) {
+
+ idInt, _ := strconv.Atoi(id)
+
+ channel, apiErrObj := r.GetChannel(id)
+
+ if apiErrObj != nil {
+ return nil, apiErrObj
+ }
+ if channel.Name != receiver.Name {
+ return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("channel name cannot be changed")}
+ }
+
+ tx, err := r.Begin()
+ if err != nil {
+ return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+
+ channel_type := getChannelType(receiver)
+
+ receiverString, _ := json.Marshal(receiver)
+
+ {
+ stmt, err := tx.Prepare(`UPDATE notification_channels SET updated_at=$1, type=$2, data=$3 WHERE id=$4;`)
+
+ if err != nil {
+ zap.L().Error("Error in preparing statement for UPDATE to notification_channels", zap.Error(err))
+ tx.Rollback()
+ return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+ defer stmt.Close()
+
+ if _, err := stmt.Exec(time.Now(), channel_type, string(receiverString), idInt); err != nil {
+ zap.L().Error("Error in Executing prepared statement for UPDATE to notification_channels", zap.Error(err))
+ tx.Rollback() // return an error too, we may want to wrap them
+ return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+ }
+
+ apiError := r.alertManager.EditRoute(receiver)
+ if apiError != nil {
+ tx.Rollback()
+ return nil, apiError
+ }
+
+ err = tx.Commit()
+ if err != nil {
+ zap.L().Error("Error in committing transaction for INSERT to notification_channels", zap.Error(err))
+ return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+
+ return receiver, nil
+
+}
+
+func (r *ruleDB) CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError) {
+
+ channel_type := getChannelType(receiver)
+
+ receiverString, _ := json.Marshal(receiver)
+
+ tx, err := r.Begin()
+ if err != nil {
+ return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+
+ {
+ stmt, err := tx.Prepare(`INSERT INTO notification_channels (created_at, updated_at, name, type, data) VALUES($1,$2,$3,$4,$5);`)
+ if err != nil {
+ zap.L().Error("Error in preparing statement for INSERT to notification_channels", zap.Error(err))
+ tx.Rollback()
+ return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+ defer stmt.Close()
+
+ if _, err := stmt.Exec(time.Now(), time.Now(), receiver.Name, channel_type, string(receiverString)); err != nil {
+ zap.L().Error("Error in Executing prepared statement for INSERT to notification_channels", zap.Error(err))
+ tx.Rollback() // return an error too, we may want to wrap them
+ return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+ }
+
+ apiError := r.alertManager.AddRoute(receiver)
+ if apiError != nil {
+ tx.Rollback()
+ return nil, apiError
+ }
+
+ err = tx.Commit()
+ if err != nil {
+ zap.L().Error("Error in committing transaction for INSERT to notification_channels", zap.Error(err))
+ return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
+ }
+
+ return receiver, nil
+
+}
+
func (r *ruleDB) GetAlertsInfo(ctx context.Context) (*model.AlertsInfo, error) {
alertsInfo := model.AlertsInfo{}
// fetch alerts from rules db
@@ -353,5 +585,31 @@ func (r *ruleDB) GetAlertsInfo(ctx context.Context) (*model.AlertsInfo, error) {
alertsInfo.TotalAlerts = alertsInfo.TotalAlerts + 1
}
alertsInfo.AlertNames = alertNames
+
+ channels, _ := r.GetChannels()
+ if channels != nil {
+ alertsInfo.TotalChannels = len(*channels)
+ for _, channel := range *channels {
+ if channel.Type == "slack" {
+ alertsInfo.SlackChannels = alertsInfo.SlackChannels + 1
+ }
+ if channel.Type == "webhook" {
+ alertsInfo.WebHookChannels = alertsInfo.WebHookChannels + 1
+ }
+ if channel.Type == "email" {
+ alertsInfo.EmailChannels = alertsInfo.EmailChannels + 1
+ }
+ if channel.Type == "pagerduty" {
+ alertsInfo.PagerDutyChannels = alertsInfo.PagerDutyChannels + 1
+ }
+ if channel.Type == "opsgenie" {
+ alertsInfo.OpsGenieChannels = alertsInfo.OpsGenieChannels + 1
+ }
+ if channel.Type == "msteams" {
+ alertsInfo.MSTeamsChannels = alertsInfo.MSTeamsChannels + 1
+ }
+ }
+ }
+
return &alertsInfo, nil
}
diff --git a/pkg/query-service/rules/manager.go b/pkg/query-service/rules/manager.go
index eaabc4f27a..89dec5f3d1 100644
--- a/pkg/query-service/rules/manager.go
+++ b/pkg/query-service/rules/manager.go
@@ -190,7 +190,12 @@ func NewManager(o *ManagerOptions) (*Manager, error) {
return nil, err
}
- db := NewRuleDB(o.DBConn)
+ amManager, err := am.New()
+ if err != nil {
+ return nil, err
+ }
+
+ db := NewRuleDB(o.DBConn, amManager)
telemetry.GetInstance().SetAlertsInfoCallback(db.GetAlertsInfo)
diff --git a/pkg/query-service/rules/threshold_rule.go b/pkg/query-service/rules/threshold_rule.go
index f7cdfd6708..0f768314cf 100644
--- a/pkg/query-service/rules/threshold_rule.go
+++ b/pkg/query-service/rules/threshold_rule.go
@@ -6,9 +6,7 @@ import (
"encoding/json"
"fmt"
"math"
- "net/url"
"regexp"
- "sort"
"text/template"
"time"
"unicode"
@@ -16,6 +14,7 @@ import (
"go.uber.org/zap"
"go.signoz.io/signoz/pkg/query-service/common"
+ "go.signoz.io/signoz/pkg/query-service/contextlinks"
"go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/query-service/postprocess"
@@ -31,6 +30,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/utils/timestamp"
logsv3 "go.signoz.io/signoz/pkg/query-service/app/logs/v3"
+ tracesV3 "go.signoz.io/signoz/pkg/query-service/app/traces/v3"
"go.signoz.io/signoz/pkg/query-service/formatter"
yaml "gopkg.in/yaml.v2"
@@ -53,6 +53,10 @@ type ThresholdRule struct {
querier interfaces.Querier
// querierV2 is used for alerts created after the introduction of new metrics query builder
querierV2 interfaces.Querier
+
+ // used for attribute metadata enrichment for logs and traces
+ logsKeys map[string]v3.AttributeKey
+ spansKeys map[string]v3.AttributeKey
}
func NewThresholdRule(
@@ -164,16 +168,8 @@ func (r *ThresholdRule) prepareQueryRange(ts time.Time) (*v3.QueryRangeParamsV3,
zap.L().Info("prepareQueryRange", zap.Int64("ts", ts.UnixMilli()), zap.Int64("evalWindow", r.evalWindow.Milliseconds()), zap.Int64("evalDelay", r.evalDelay.Milliseconds()))
- start := ts.Add(-time.Duration(r.evalWindow)).UnixMilli()
- end := ts.UnixMilli()
-
- if r.evalDelay > 0 {
- start = start - int64(r.evalDelay.Milliseconds())
- end = end - int64(r.evalDelay.Milliseconds())
- }
- // round to minute otherwise we could potentially miss data
- start = start - (start % (60 * 1000))
- end = end - (end % (60 * 1000))
+ startTs, endTs := r.Timestamps(ts)
+ start, end := startTs.UnixMilli(), endTs.UnixMilli()
if r.ruleCondition.QueryType() == v3.QueryTypeClickHouseSQL {
params := &v3.QueryRangeParamsV3{
@@ -239,245 +235,76 @@ func (r *ThresholdRule) prepareQueryRange(ts time.Time) (*v3.QueryRangeParamsV3,
}, nil
}
-// The following function is used to prepare the where clause for the query
-// `lbls` contains the key value pairs of the labels from the result of the query
-// We iterate over the where clause and replace the labels with the actual values
-// There are two cases:
-// 1. The label is present in the where clause
-// 2. The label is not present in the where clause
-//
-// Example for case 2:
-// Latency by serviceName without any filter
-// In this case, for each service with latency > threshold we send a notification
-// The expectation will be that clicking on the related traces for service A, will
-// take us to the traces page with the filter serviceName=A
-// So for all the missing labels in the where clause, we add them as key = value
-//
-// Example for case 1:
-// Severity text IN (WARN, ERROR)
-// In this case, the Severity text will appear in the `lbls` if it were part of the group
-// by clause, in which case we replace it with the actual value for the notification
-// i.e Severity text = WARN
-// If the Severity text is not part of the group by clause, then we add it as it is
-func (r *ThresholdRule) fetchFilters(selectedQuery string, lbls labels.Labels) []v3.FilterItem {
- var filterItems []v3.FilterItem
-
- added := make(map[string]struct{})
-
- if r.ruleCondition.CompositeQuery.QueryType == v3.QueryTypeBuilder &&
- r.ruleCondition.CompositeQuery.BuilderQueries[selectedQuery] != nil &&
- r.ruleCondition.CompositeQuery.BuilderQueries[selectedQuery].Filters != nil {
-
- for _, item := range r.ruleCondition.CompositeQuery.BuilderQueries[selectedQuery].Filters.Items {
- exists := false
- for _, label := range lbls {
- if item.Key.Key == label.Name {
- // if the label is present in the where clause, replace it with key = value
- filterItems = append(filterItems, v3.FilterItem{
- Key: item.Key,
- Operator: v3.FilterOperatorEqual,
- Value: label.Value,
- })
- exists = true
- added[label.Name] = struct{}{}
- break
- }
- }
-
- if !exists {
- // if the label is not present in the where clause, add it as it is
- filterItems = append(filterItems, item)
- }
- }
- }
-
- // add the labels which are not present in the where clause
- for _, label := range lbls {
- if _, ok := added[label.Name]; !ok {
- filterItems = append(filterItems, v3.FilterItem{
- Key: v3.AttributeKey{Key: label.Name},
- Operator: v3.FilterOperatorEqual,
- Value: label.Value,
- })
- }
- }
-
- return filterItems
-}
-
func (r *ThresholdRule) prepareLinksToLogs(ts time.Time, lbls labels.Labels) string {
selectedQuery := r.GetSelectedQuery()
+ qr, err := r.prepareQueryRange(ts)
+ if err != nil {
+ return ""
+ }
+ start := time.UnixMilli(qr.Start)
+ end := time.UnixMilli(qr.End)
+
// TODO(srikanthccv): handle formula queries
if selectedQuery < "A" || selectedQuery > "Z" {
return ""
}
- q, err := r.prepareQueryRange(ts)
- if err != nil {
+ q := r.ruleCondition.CompositeQuery.BuilderQueries[selectedQuery]
+ if q == nil {
return ""
}
- // Logs list view expects time in milliseconds
- tr := v3.URLShareableTimeRange{
- Start: q.Start,
- End: q.End,
- PageSize: 100,
- }
-
- options := v3.URLShareableOptions{
- MaxLines: 2,
- Format: "list",
- SelectColumns: []v3.AttributeKey{},
- }
-
- period, _ := json.Marshal(tr)
- urlEncodedTimeRange := url.QueryEscape(string(period))
-
- filterItems := r.fetchFilters(selectedQuery, lbls)
- urlData := v3.URLShareableCompositeQuery{
- QueryType: string(v3.QueryTypeBuilder),
- Builder: v3.URLShareableBuilderQuery{
- QueryData: []v3.BuilderQuery{
- {
- DataSource: v3.DataSourceLogs,
- QueryName: "A",
- AggregateOperator: v3.AggregateOperatorNoOp,
- AggregateAttribute: v3.AttributeKey{},
- Filters: &v3.FilterSet{
- Items: filterItems,
- Operator: "AND",
- },
- Expression: "A",
- Disabled: false,
- Having: []v3.Having{},
- StepInterval: 60,
- OrderBy: []v3.OrderBy{
- {
- ColumnName: "timestamp",
- Order: "desc",
- },
- },
- },
- },
- QueryFormulas: make([]string, 0),
- },
+
+ if q.DataSource != v3.DataSourceLogs {
+ return ""
}
- data, _ := json.Marshal(urlData)
- compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
+ queryFilter := []v3.FilterItem{}
+ if q.Filters != nil {
+ queryFilter = q.Filters.Items
+ }
- optionsData, _ := json.Marshal(options)
- urlEncodedOptions := url.QueryEscape(string(optionsData))
+ filterItems := contextlinks.PrepareFilters(lbls.Map(), queryFilter, q.GroupBy, r.logsKeys)
- return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
+ return contextlinks.PrepareLinksToLogs(start, end, filterItems)
}
func (r *ThresholdRule) prepareLinksToTraces(ts time.Time, lbls labels.Labels) string {
selectedQuery := r.GetSelectedQuery()
+ qr, err := r.prepareQueryRange(ts)
+ if err != nil {
+ return ""
+ }
+ start := time.UnixMilli(qr.Start)
+ end := time.UnixMilli(qr.End)
+
// TODO(srikanthccv): handle formula queries
if selectedQuery < "A" || selectedQuery > "Z" {
return ""
}
- q, err := r.prepareQueryRange(ts)
- if err != nil {
+ q := r.ruleCondition.CompositeQuery.BuilderQueries[selectedQuery]
+ if q == nil {
return ""
}
- // Traces list view expects time in nanoseconds
- tr := v3.URLShareableTimeRange{
- Start: q.Start * time.Second.Microseconds(),
- End: q.End * time.Second.Microseconds(),
- PageSize: 100,
- }
-
- options := v3.URLShareableOptions{
- MaxLines: 2,
- Format: "list",
- SelectColumns: constants.TracesListViewDefaultSelectedColumns,
- }
-
- period, _ := json.Marshal(tr)
- urlEncodedTimeRange := url.QueryEscape(string(period))
-
- filterItems := r.fetchFilters(selectedQuery, lbls)
- urlData := v3.URLShareableCompositeQuery{
- QueryType: string(v3.QueryTypeBuilder),
- Builder: v3.URLShareableBuilderQuery{
- QueryData: []v3.BuilderQuery{
- {
- DataSource: v3.DataSourceTraces,
- QueryName: "A",
- AggregateOperator: v3.AggregateOperatorNoOp,
- AggregateAttribute: v3.AttributeKey{},
- Filters: &v3.FilterSet{
- Items: filterItems,
- Operator: "AND",
- },
- Expression: "A",
- Disabled: false,
- Having: []v3.Having{},
- StepInterval: 60,
- OrderBy: []v3.OrderBy{
- {
- ColumnName: "timestamp",
- Order: "desc",
- },
- },
- },
- },
- QueryFormulas: make([]string, 0),
- },
+
+ if q.DataSource != v3.DataSourceTraces {
+ return ""
}
- data, _ := json.Marshal(urlData)
- compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
+ queryFilter := []v3.FilterItem{}
+ if q.Filters != nil {
+ queryFilter = q.Filters.Items
+ }
- optionsData, _ := json.Marshal(options)
- urlEncodedOptions := url.QueryEscape(string(optionsData))
+ filterItems := contextlinks.PrepareFilters(lbls.Map(), queryFilter, q.GroupBy, r.spansKeys)
- return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
+ return contextlinks.PrepareLinksToTraces(start, end, filterItems)
}
func (r *ThresholdRule) GetSelectedQuery() string {
- if r.ruleCondition != nil {
- if r.ruleCondition.SelectedQuery != "" {
- return r.ruleCondition.SelectedQuery
- }
-
- queryNames := map[string]struct{}{}
-
- if r.ruleCondition.CompositeQuery != nil {
- if r.ruleCondition.QueryType() == v3.QueryTypeBuilder {
- for name := range r.ruleCondition.CompositeQuery.BuilderQueries {
- queryNames[name] = struct{}{}
- }
- } else if r.ruleCondition.QueryType() == v3.QueryTypeClickHouseSQL {
- for name := range r.ruleCondition.CompositeQuery.ClickHouseQueries {
- queryNames[name] = struct{}{}
- }
- }
- }
-
- // The following logic exists for backward compatibility
- // If there is no selected query, then
- // - check if F1 is present, if yes, return F1
- // - else return the query with max ascii value
- // this logic is not really correct. we should be considering
- // whether the query is enabled or not. but this is a temporary
- // fix to support backward compatibility
- if _, ok := queryNames["F1"]; ok {
- return "F1"
- }
- keys := make([]string, 0, len(queryNames))
- for k := range queryNames {
- keys = append(keys, k)
- }
- sort.Strings(keys)
- return keys[len(keys)-1]
- }
- // This should never happen
- return ""
+ return r.ruleCondition.GetSelectedQueryName()
}
func (r *ThresholdRule) buildAndRunQuery(ctx context.Context, ts time.Time) (Vector, error) {
@@ -492,11 +319,37 @@ func (r *ThresholdRule) buildAndRunQuery(ctx context.Context, ts time.Time) (Vec
}
if params.CompositeQuery.QueryType == v3.QueryTypeBuilder {
- // check if any enrichment is required for logs if yes then enrich them
- if logsv3.EnrichmentRequired(params) {
- // Note: Sending empty fields key because enrichment is only needed for json
- // TODO: Add support for attribute enrichment later
- logsv3.Enrich(params, map[string]v3.AttributeKey{})
+ hasLogsQuery := false
+ hasTracesQuery := false
+ for _, query := range params.CompositeQuery.BuilderQueries {
+ if query.DataSource == v3.DataSourceLogs {
+ hasLogsQuery = true
+ }
+ if query.DataSource == v3.DataSourceTraces {
+ hasTracesQuery = true
+ }
+ }
+
+ if hasLogsQuery {
+ // check if any enrichment is required for logs if yes then enrich them
+ if logsv3.EnrichmentRequired(params) {
+ logsFields, err := r.reader.GetLogFields(ctx)
+ if err != nil {
+ return nil, err
+ }
+ logsKeys := model.GetLogFieldsV3(ctx, params, logsFields)
+ r.logsKeys = logsKeys
+ logsv3.Enrich(params, logsKeys)
+ }
+ }
+
+ if hasTracesQuery {
+ spanKeys, err := r.reader.GetSpanAttributeKeys(ctx)
+ if err != nil {
+ return nil, err
+ }
+ r.spansKeys = spanKeys
+ tracesV3.Enrich(params, spanKeys)
}
}
@@ -654,11 +507,13 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time) (interface{}, er
if r.typ == AlertTypeTraces {
link := r.prepareLinksToTraces(ts, smpl.MetricOrig)
if link != "" && r.hostFromSource() != "" {
+ zap.L().Info("adding traces link to annotations", zap.String("link", fmt.Sprintf("%s/traces-explorer?%s", r.hostFromSource(), link)))
annotations = append(annotations, labels.Label{Name: "related_traces", Value: fmt.Sprintf("%s/traces-explorer?%s", r.hostFromSource(), link)})
}
} else if r.typ == AlertTypeLogs {
link := r.prepareLinksToLogs(ts, smpl.MetricOrig)
if link != "" && r.hostFromSource() != "" {
+ zap.L().Info("adding logs link to annotations", zap.String("link", fmt.Sprintf("%s/logs/logs-explorer?%s", r.hostFromSource(), link)))
annotations = append(annotations, labels.Label{Name: "related_logs", Value: fmt.Sprintf("%s/logs/logs-explorer?%s", r.hostFromSource(), link)})
}
}
diff --git a/pkg/query-service/rules/threshold_rule_test.go b/pkg/query-service/rules/threshold_rule_test.go
index e000d71257..65d020d25f 100644
--- a/pkg/query-service/rules/threshold_rule_test.go
+++ b/pkg/query-service/rules/threshold_rule_test.go
@@ -1303,12 +1303,23 @@ func TestThresholdRuleTracesLink(t *testing.T) {
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
}
+ metaCols := make([]cmock.ColumnType, 0)
+ metaCols = append(metaCols, cmock.ColumnType{Name: "DISTINCT(tagKey)", Type: "String"})
+ metaCols = append(metaCols, cmock.ColumnType{Name: "tagType", Type: "String"})
+ metaCols = append(metaCols, cmock.ColumnType{Name: "dataType", Type: "String"})
+ metaCols = append(metaCols, cmock.ColumnType{Name: "isColumn", Type: "Bool"})
+
cols := make([]cmock.ColumnType, 0)
cols = append(cols, cmock.ColumnType{Name: "value", Type: "Float64"})
cols = append(cols, cmock.ColumnType{Name: "attr", Type: "String"})
cols = append(cols, cmock.ColumnType{Name: "timestamp", Type: "String"})
for idx, c := range testCases {
+ metaRows := cmock.NewRows(metaCols, c.metaValues)
+ mock.
+ ExpectQuery("SELECT DISTINCT(tagKey), tagType, dataType, isColumn FROM archiveNamespace.span_attributes_keys").
+ WillReturnRows(metaRows)
+
rows := cmock.NewRows(cols, c.values)
// We are testing the eval logic after the query is run
@@ -1402,12 +1413,38 @@ func TestThresholdRuleLogsLink(t *testing.T) {
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
}
+ attrMetaCols := make([]cmock.ColumnType, 0)
+ attrMetaCols = append(attrMetaCols, cmock.ColumnType{Name: "name", Type: "String"})
+ attrMetaCols = append(attrMetaCols, cmock.ColumnType{Name: "dataType", Type: "String"})
+
+ resourceMetaCols := make([]cmock.ColumnType, 0)
+ resourceMetaCols = append(resourceMetaCols, cmock.ColumnType{Name: "name", Type: "String"})
+ resourceMetaCols = append(resourceMetaCols, cmock.ColumnType{Name: "dataType", Type: "String"})
+
+ createTableCols := make([]cmock.ColumnType, 0)
+ createTableCols = append(createTableCols, cmock.ColumnType{Name: "statement", Type: "String"})
+
cols := make([]cmock.ColumnType, 0)
cols = append(cols, cmock.ColumnType{Name: "value", Type: "Float64"})
cols = append(cols, cmock.ColumnType{Name: "attr", Type: "String"})
cols = append(cols, cmock.ColumnType{Name: "timestamp", Type: "String"})
for idx, c := range testCases {
+ attrMetaRows := cmock.NewRows(attrMetaCols, c.attrMetaValues)
+ mock.
+ ExpectSelect("SELECT DISTINCT name, datatype from signoz_logs.distributed_logs_attribute_keys group by name, datatype").
+ WillReturnRows(attrMetaRows)
+
+ resourceMetaRows := cmock.NewRows(resourceMetaCols, c.resourceMetaValues)
+ mock.
+ ExpectSelect("SELECT DISTINCT name, datatype from signoz_logs.distributed_logs_resource_keys group by name, datatype").
+ WillReturnRows(resourceMetaRows)
+
+ createTableRows := cmock.NewRows(createTableCols, c.createTableValues)
+ mock.
+ ExpectSelect("SHOW CREATE TABLE signoz_logs.logs").
+ WillReturnRows(createTableRows)
+
rows := cmock.NewRows(cols, c.values)
// We are testing the eval logic after the query is run
diff --git a/pkg/query-service/rules/threshold_rule_test_data.go b/pkg/query-service/rules/threshold_rule_test_data.go
index 3a28bdf38b..cc301c5aa9 100644
--- a/pkg/query-service/rules/threshold_rule_test_data.go
+++ b/pkg/query-service/rules/threshold_rule_test_data.go
@@ -4,14 +4,18 @@ import "time"
var (
testCases = []struct {
- targetUnit string
- yAxisUnit string
- values [][]interface{}
- expectAlerts int
- compareOp string
- matchType string
- target float64
- summaryAny []string
+ targetUnit string
+ yAxisUnit string
+ values [][]interface{}
+ metaValues [][]interface{}
+ attrMetaValues [][]interface{}
+ resourceMetaValues [][]interface{}
+ createTableValues [][]interface{}
+ expectAlerts int
+ compareOp string
+ matchType string
+ target float64
+ summaryAny []string
}{
{
targetUnit: "s",
@@ -23,10 +27,16 @@ var (
{float64(299316000), "attr", time.Now().Add(3 * time.Second)}, // 0.3 seconds
{float64(66640400.00000001), "attr", time.Now().Add(4 * time.Second)}, // 0.06 seconds
},
- expectAlerts: 0,
- compareOp: "1", // Above
- matchType: "1", // Once
- target: 1, // 1 second
+ metaValues: [][]interface{}{},
+ createTableValues: [][]interface{}{
+ {"statement"},
+ },
+ attrMetaValues: [][]interface{}{},
+ resourceMetaValues: [][]interface{}{},
+ expectAlerts: 0,
+ compareOp: "1", // Above
+ matchType: "1", // Once
+ target: 1, // 1 second
},
{
targetUnit: "ms",
@@ -38,10 +48,16 @@ var (
{float64(299316000), "attr", time.Now().Add(3 * time.Second)}, // 299.31 ms
{float64(66640400.00000001), "attr", time.Now().Add(4 * time.Second)}, // 66.64 ms
},
- expectAlerts: 4,
- compareOp: "1", // Above
- matchType: "1", // Once
- target: 200, // 200 ms
+ metaValues: [][]interface{}{},
+ createTableValues: [][]interface{}{
+ {"statement"},
+ },
+ attrMetaValues: [][]interface{}{},
+ resourceMetaValues: [][]interface{}{},
+ expectAlerts: 4,
+ compareOp: "1", // Above
+ matchType: "1", // Once
+ target: 200, // 200 ms
summaryAny: []string{
"observed metric value is 299 ms",
"the observed metric value is 573 ms",
@@ -59,10 +75,16 @@ var (
{float64(299316000), "attr", time.Now().Add(3 * time.Second)}, // 0.3 GB
{float64(66640400.00000001), "attr", time.Now().Add(4 * time.Second)}, // 66.64 MB
},
- expectAlerts: 0,
- compareOp: "1", // Above
- matchType: "1", // Once
- target: 200, // 200 GB
+ metaValues: [][]interface{}{},
+ createTableValues: [][]interface{}{
+ {"statement"},
+ },
+ attrMetaValues: [][]interface{}{},
+ resourceMetaValues: [][]interface{}{},
+ expectAlerts: 0,
+ compareOp: "1", // Above
+ matchType: "1", // Once
+ target: 200, // 200 GB
},
}
)
diff --git a/pkg/query-service/telemetry/telemetry.go b/pkg/query-service/telemetry/telemetry.go
index aad1745907..be6ad4719c 100644
--- a/pkg/query-service/telemetry/telemetry.go
+++ b/pkg/query-service/telemetry/telemetry.go
@@ -325,65 +325,46 @@ func createTelemetry() {
if err == nil {
dashboardsInfo, err := telemetry.dashboardsInfoCallback(ctx)
if err == nil {
- channels, err := telemetry.reader.GetChannels()
+ savedViewsInfo, err := telemetry.savedViewsInfoCallback(ctx)
if err == nil {
- for _, channel := range *channels {
- switch channel.Type {
- case "slack":
- alertsInfo.SlackChannels++
- case "webhook":
- alertsInfo.WebHookChannels++
- case "pagerduty":
- alertsInfo.PagerDutyChannels++
- case "opsgenie":
- alertsInfo.OpsGenieChannels++
- case "email":
- alertsInfo.EmailChannels++
- case "msteams":
- alertsInfo.MSTeamsChannels++
- }
+ dashboardsAlertsData := map[string]interface{}{
+ "totalDashboards": dashboardsInfo.TotalDashboards,
+ "totalDashboardsWithPanelAndName": dashboardsInfo.TotalDashboardsWithPanelAndName,
+ "dashboardNames": dashboardsInfo.DashboardNames,
+ "alertNames": alertsInfo.AlertNames,
+ "logsBasedPanels": dashboardsInfo.LogsBasedPanels,
+ "metricBasedPanels": dashboardsInfo.MetricBasedPanels,
+ "tracesBasedPanels": dashboardsInfo.TracesBasedPanels,
+ "dashboardsWithTSV2": dashboardsInfo.QueriesWithTSV2,
+ "dashboardWithLogsChQuery": dashboardsInfo.DashboardsWithLogsChQuery,
+ "totalAlerts": alertsInfo.TotalAlerts,
+ "alertsWithTSV2": alertsInfo.AlertsWithTSV2,
+ "logsBasedAlerts": alertsInfo.LogsBasedAlerts,
+ "metricBasedAlerts": alertsInfo.MetricBasedAlerts,
+ "tracesBasedAlerts": alertsInfo.TracesBasedAlerts,
+ "totalChannels": alertsInfo.TotalChannels,
+ "totalSavedViews": savedViewsInfo.TotalSavedViews,
+ "logsSavedViews": savedViewsInfo.LogsSavedViews,
+ "tracesSavedViews": savedViewsInfo.TracesSavedViews,
+ "slackChannels": alertsInfo.SlackChannels,
+ "webHookChannels": alertsInfo.WebHookChannels,
+ "pagerDutyChannels": alertsInfo.PagerDutyChannels,
+ "opsGenieChannels": alertsInfo.OpsGenieChannels,
+ "emailChannels": alertsInfo.EmailChannels,
+ "msteamsChannels": alertsInfo.MSTeamsChannels,
+ "metricsBuilderQueries": alertsInfo.MetricsBuilderQueries,
+ "metricsClickHouseQueries": alertsInfo.MetricsClickHouseQueries,
+ "metricsPrometheusQueries": alertsInfo.MetricsPrometheusQueries,
+ "spanMetricsPrometheusQueries": alertsInfo.SpanMetricsPrometheusQueries,
+ "alertsWithLogsChQuery": alertsInfo.AlertsWithLogsChQuery,
}
- savedViewsInfo, err := telemetry.savedViewsInfoCallback(ctx)
- if err == nil {
- dashboardsAlertsData := map[string]interface{}{
- "totalDashboards": dashboardsInfo.TotalDashboards,
- "totalDashboardsWithPanelAndName": dashboardsInfo.TotalDashboardsWithPanelAndName,
- "dashboardNames": dashboardsInfo.DashboardNames,
- "alertNames": alertsInfo.AlertNames,
- "logsBasedPanels": dashboardsInfo.LogsBasedPanels,
- "metricBasedPanels": dashboardsInfo.MetricBasedPanels,
- "tracesBasedPanels": dashboardsInfo.TracesBasedPanels,
- "dashboardsWithTSV2": dashboardsInfo.QueriesWithTSV2,
- "dashboardWithLogsChQuery": dashboardsInfo.DashboardsWithLogsChQuery,
- "totalAlerts": alertsInfo.TotalAlerts,
- "alertsWithTSV2": alertsInfo.AlertsWithTSV2,
- "logsBasedAlerts": alertsInfo.LogsBasedAlerts,
- "metricBasedAlerts": alertsInfo.MetricBasedAlerts,
- "tracesBasedAlerts": alertsInfo.TracesBasedAlerts,
- "totalChannels": len(*channels),
- "totalSavedViews": savedViewsInfo.TotalSavedViews,
- "logsSavedViews": savedViewsInfo.LogsSavedViews,
- "tracesSavedViews": savedViewsInfo.TracesSavedViews,
- "slackChannels": alertsInfo.SlackChannels,
- "webHookChannels": alertsInfo.WebHookChannels,
- "pagerDutyChannels": alertsInfo.PagerDutyChannels,
- "opsGenieChannels": alertsInfo.OpsGenieChannels,
- "emailChannels": alertsInfo.EmailChannels,
- "msteamsChannels": alertsInfo.MSTeamsChannels,
- "metricsBuilderQueries": alertsInfo.MetricsBuilderQueries,
- "metricsClickHouseQueries": alertsInfo.MetricsClickHouseQueries,
- "metricsPrometheusQueries": alertsInfo.MetricsPrometheusQueries,
- "spanMetricsPrometheusQueries": alertsInfo.SpanMetricsPrometheusQueries,
- "alertsWithLogsChQuery": alertsInfo.AlertsWithLogsChQuery,
- }
- // send event only if there are dashboards or alerts or channels
- if (dashboardsInfo.TotalDashboards > 0 || alertsInfo.TotalAlerts > 0 || len(*channels) > 0 || savedViewsInfo.TotalSavedViews > 0) && apiErr == nil {
- for _, user := range users {
- if user.Email == DEFAULT_CLOUD_EMAIL {
- continue
- }
- telemetry.SendEvent(TELEMETRY_EVENT_DASHBOARDS_ALERTS, dashboardsAlertsData, user.Email, false, false)
+ // send event only if there are dashboards or alerts or channels
+ if (dashboardsInfo.TotalDashboards > 0 || alertsInfo.TotalAlerts > 0 || alertsInfo.TotalChannels > 0 || savedViewsInfo.TotalSavedViews > 0) && apiErr == nil {
+ for _, user := range users {
+ if user.Email == DEFAULT_CLOUD_EMAIL {
+ continue
}
+ telemetry.SendEvent(TELEMETRY_EVENT_DASHBOARDS_ALERTS, dashboardsAlertsData, user.Email, false, false)
}
}
}
@@ -467,11 +448,9 @@ func getOutboundIP() string {
}
defer resp.Body.Close()
+ ipBody, err := io.ReadAll(resp.Body)
if err == nil {
- ipBody, err := io.ReadAll(resp.Body)
- if err == nil {
- ip = ipBody
- }
+ ip = ipBody
}
return string(ip)
diff --git a/pkg/query-service/tests/integration/filter_suggestions_test.go b/pkg/query-service/tests/integration/filter_suggestions_test.go
index 6859a6ac2f..a1f56115c5 100644
--- a/pkg/query-service/tests/integration/filter_suggestions_test.go
+++ b/pkg/query-service/tests/integration/filter_suggestions_test.go
@@ -186,7 +186,7 @@ func (tb *FilterSuggestionsTestBed) mockAttribValuesQueryResponse(
{Type: "Nullable(Float64)", Name: "float64TagValue"},
}
- expectedAttribKeysInQuery := []string{}
+ expectedAttribKeysInQuery := []any{}
mockResultRows := [][]any{}
for idx, attrib := range expectedAttribs {
expectedAttribKeysInQuery = append(expectedAttribKeysInQuery, attrib.Key)
@@ -198,8 +198,8 @@ func (tb *FilterSuggestionsTestBed) mockAttribValuesQueryResponse(
}
tb.mockClickhouse.ExpectQuery(
- "select.*tagKey.*stringTagValue.*int64TagValue.*float64TagValue.*distributed_tag_attributes.*tagKey.*in.*",
- ).WithArgs(expectedAttribKeysInQuery).WillReturnRows(mockhouse.NewRows(resultCols, mockResultRows))
+ "select.*tagKey.*stringTagValue.*int64TagValue.*float64TagValue.*distributed_tag_attributes.*tagKey",
+ ).WithArgs(expectedAttribKeysInQuery...).WillReturnRows(mockhouse.NewRows(resultCols, mockResultRows))
}
type FilterSuggestionsTestBed struct {
diff --git a/signoz-core-ui b/signoz-core-ui
new file mode 160000
index 0000000000..f8c925d842
--- /dev/null
+++ b/signoz-core-ui
@@ -0,0 +1 @@
+Subproject commit f8c925d842922f8a30063012a7bfb688a3bf0f36