Compare commits

..

1 Commits

Author SHA1 Message Date
Nikhil Soni
9fe79b08de feat(alerts): add docs and agent skill info banner to ClickHouse query editor
Shows a contextual info banner when creating alert rules using ClickHouse
query mode, with doc links that vary by alert type (logs/traces/metrics).
Agent skill link is shown for logs and traces but skipped for metrics.
2026-05-12 15:03:41 +05:30
9 changed files with 80 additions and 123 deletions

View File

@@ -1,16 +1,69 @@
import { Callout } from '@signozhq/ui/callout';
import ClickHouseQueryBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/ClickHouse/query';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import DOCLINKS from 'utils/docLinks';
function ChQuerySection(): JSX.Element {
import 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/ClickHouse/ClickHouse.styles.scss';
const ALERT_TYPE_DOC_LINK: Partial<Record<AlertTypes, string>> = {
[AlertTypes.LOGS_BASED_ALERT]: DOCLINKS.QUERY_CLICKHOUSE_LOGS,
[AlertTypes.TRACES_BASED_ALERT]: DOCLINKS.QUERY_CLICKHOUSE_TRACES,
[AlertTypes.EXCEPTIONS_BASED_ALERT]: DOCLINKS.QUERY_CLICKHOUSE_TRACES,
[AlertTypes.METRICS_BASED_ALERT]: DOCLINKS.QUERY_CLICKHOUSE_METRICS,
};
const ALERT_TYPES_WITH_AGENT_SKILL: AlertTypes[] = [
AlertTypes.LOGS_BASED_ALERT,
AlertTypes.TRACES_BASED_ALERT,
AlertTypes.EXCEPTIONS_BASED_ALERT,
];
interface ChQuerySectionProps {
alertType: AlertTypes;
}
function ChQuerySection({ alertType }: ChQuerySectionProps): JSX.Element {
const { currentQuery } = useQueryBuilder();
const docLink = ALERT_TYPE_DOC_LINK[alertType];
const showAgentSkill = ALERT_TYPES_WITH_AGENT_SKILL.includes(alertType);
return (
<ClickHouseQueryBuilder
key="A"
queryIndex={0}
queryData={currentQuery.clickhouse_sql[0]}
deletable={false}
/>
<>
{docLink && (
<div className="info-banner-wrapper">
<Callout
type="info"
showIcon
title={
<span>
<a href={docLink} target="_blank" rel="noreferrer">
Learn to write faster, optimized queries
</a>
{showAgentSkill && (
<>
{' · Using AI? '}
<a
href={DOCLINKS.AGENT_SKILL_INSTALL}
target="_blank"
rel="noreferrer"
>
Install the SigNoz ClickHouse query agent skill
</a>
</>
)}
</span>
}
/>
</div>
)}
<ClickHouseQueryBuilder
key="A"
queryIndex={0}
queryData={currentQuery.clickhouse_sql[0]}
deletable={false}
/>
</>
);
}

View File

@@ -56,7 +56,9 @@ function QuerySection({
const renderPromqlUI = (): JSX.Element => <PromqlSection />;
const renderChQueryUI = (): JSX.Element => <ChQuerySection />;
const renderChQueryUI = (): JSX.Element => (
<ChQuerySection alertType={alertType} />
);
const isDarkMode = useIsDarkMode();

View File

@@ -10,6 +10,10 @@ const DOCLINKS = {
'https://signoz.io/docs/external-api-monitoring/overview/',
QUERY_CLICKHOUSE_TRACES:
'https://signoz.io/docs/userguide/writing-clickhouse-traces-query/#timestamp-bucketing-for-distributed_signoz_index_v3',
QUERY_CLICKHOUSE_LOGS:
'https://signoz.io/docs/userguide/logs_clickhouse_queries/',
QUERY_CLICKHOUSE_METRICS:
'https://signoz.io/docs/userguide/write-a-metrics-clickhouse-query/',
AGENT_SKILL_INSTALL: 'https://signoz.io/docs/ai/agent-skills/#installation',
};

View File

@@ -1343,7 +1343,7 @@ func getLocalTableName(tableName string) string {
}
func (r *ClickHouseReader) setTTLLogs(ctx context.Context, orgID string, userEmail string, params *retentiontypes.TTLParams) (*retentiontypes.SetTTLResponseItem, *model.ApiError) {
func (r *ClickHouseReader) setTTLLogs(ctx context.Context, orgID string, params *retentiontypes.TTLParams) (*retentiontypes.SetTTLResponseItem, *model.ApiError) {
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
instrumentationtypes.CodeNamespace: "clickhouse-reader",
@@ -1434,10 +1434,6 @@ func (r *ClickHouseReader) setTTLLogs(ctx context.Context, orgID string, userEma
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
UserAuditable: types.UserAuditable{
CreatedBy: userEmail,
UpdatedBy: userEmail,
},
TransactionID: uuid,
TableName: tableName,
TTL: int(params.DelDuration),
@@ -1515,7 +1511,7 @@ func (r *ClickHouseReader) setTTLLogs(ctx context.Context, orgID string, userEma
return &retentiontypes.SetTTLResponseItem{Message: "move ttl has been successfully set up"}, nil
}
func (r *ClickHouseReader) setTTLTraces(ctx context.Context, orgID string, userEmail string, params *retentiontypes.TTLParams) (*retentiontypes.SetTTLResponseItem, *model.ApiError) {
func (r *ClickHouseReader) setTTLTraces(ctx context.Context, orgID string, params *retentiontypes.TTLParams) (*retentiontypes.SetTTLResponseItem, *model.ApiError) {
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
instrumentationtypes.CodeNamespace: "clickhouse-reader",
@@ -1576,10 +1572,6 @@ func (r *ClickHouseReader) setTTLTraces(ctx context.Context, orgID string, userE
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
UserAuditable: types.UserAuditable{
CreatedBy: userEmail,
UpdatedBy: userEmail,
},
TransactionID: uuid,
TableName: tableName,
TTL: int(params.DelDuration),
@@ -1695,7 +1687,7 @@ func (r *ClickHouseReader) hasCustomRetentionColumn(ctx context.Context) (bool,
return true, nil
}
func (r *ClickHouseReader) SetTTLV2(ctx context.Context, orgID string, userEmail string, params *retentiontypes.CustomRetentionTTLParams) (*retentiontypes.CustomRetentionTTLResponse, error) {
func (r *ClickHouseReader) SetTTLV2(ctx context.Context, orgID string, params *retentiontypes.CustomRetentionTTLParams) (*retentiontypes.CustomRetentionTTLResponse, error) {
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
@@ -1726,7 +1718,7 @@ func (r *ClickHouseReader) SetTTLV2(ctx context.Context, orgID string, userEmail
ttlParams.ToColdStorageDuration = 0
}
ttlResult, apiErr := r.SetTTL(ctx, orgID, userEmail, ttlParams)
ttlResult, apiErr := r.SetTTL(ctx, orgID, ttlParams)
if apiErr != nil {
return nil, errorsV2.Wrapf(apiErr.Err, errorsV2.TypeInternal, errorsV2.CodeInternal, "failed to set standard TTL")
}
@@ -1855,10 +1847,6 @@ func (r *ClickHouseReader) SetTTLV2(ctx context.Context, orgID string, userEmail
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
UserAuditable: types.UserAuditable{
CreatedBy: userEmail,
UpdatedBy: userEmail,
},
TransactionID: uuid,
TableName: tableName,
TTL: params.DefaultTTLDays,
@@ -2197,24 +2185,24 @@ func (r *ClickHouseReader) validateTTLConditions(ctx context.Context, ttlConditi
// SetTTL sets the TTL for traces or metrics or logs tables.
// This is an async API which creates goroutines to set TTL.
// Status of TTL update is tracked with ttl_status table in sqlite db.
func (r *ClickHouseReader) SetTTL(ctx context.Context, orgID string, userEmail string, params *retentiontypes.TTLParams) (*retentiontypes.SetTTLResponseItem, *model.ApiError) {
func (r *ClickHouseReader) SetTTL(ctx context.Context, orgID string, params *retentiontypes.TTLParams) (*retentiontypes.SetTTLResponseItem, *model.ApiError) {
// Keep only latest 100 transactions/requests
r.deleteTtlTransactions(ctx, orgID, 100)
switch params.Type {
case retentiontypes.TraceTTL:
return r.setTTLTraces(ctx, orgID, userEmail, params)
return r.setTTLTraces(ctx, orgID, params)
case retentiontypes.MetricsTTL:
return r.setTTLMetrics(ctx, orgID, userEmail, params)
return r.setTTLMetrics(ctx, orgID, params)
case retentiontypes.LogsTTL:
return r.setTTLLogs(ctx, orgID, userEmail, params)
return r.setTTLLogs(ctx, orgID, params)
default:
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error while setting ttl. ttl type should be <metrics|traces>, got %v", params.Type)}
}
}
func (r *ClickHouseReader) setTTLMetrics(ctx context.Context, orgID string, userEmail string, params *retentiontypes.TTLParams) (*retentiontypes.SetTTLResponseItem, *model.ApiError) {
func (r *ClickHouseReader) setTTLMetrics(ctx context.Context, orgID string, params *retentiontypes.TTLParams) (*retentiontypes.SetTTLResponseItem, *model.ApiError) {
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
instrumentationtypes.CodeNamespace: "clickhouse-reader",
@@ -2256,10 +2244,6 @@ func (r *ClickHouseReader) setTTLMetrics(ctx context.Context, orgID string, user
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
UserAuditable: types.UserAuditable{
CreatedBy: userEmail,
UpdatedBy: userEmail,
},
TransactionID: uuid,
TableName: tableName,
TTL: int(params.DelDuration),

View File

@@ -1656,7 +1656,7 @@ func (aH *APIHandler) setTTL(w http.ResponseWriter, r *http.Request) {
}
// Context is not used here as TTL is long duration DB operation
result, apiErr := aH.reader.SetTTL(context.Background(), claims.OrgID, claims.Email, ttlParams)
result, apiErr := aH.reader.SetTTL(context.Background(), claims.OrgID, ttlParams)
if apiErr != nil {
if apiErr.Typ == model.ErrorConflict {
aH.HandleError(w, apiErr.Err, http.StatusConflict)
@@ -1685,7 +1685,7 @@ func (aH *APIHandler) setCustomRetentionTTL(w http.ResponseWriter, r *http.Reque
}
// Context is not used here as TTL is long duration DB operation
result, apiErr := aH.reader.SetTTLV2(context.Background(), claims.OrgID, claims.Email, &params)
result, apiErr := aH.reader.SetTTLV2(context.Background(), claims.OrgID, &params)
if apiErr != nil {
render.Error(w, errorsV2.New(errorsV2.TypeInvalidInput, errorsV2.CodeInternal, apiErr.Error()))
return

View File

@@ -47,8 +47,8 @@ type Reader interface {
GetFlamegraphSpansForTrace(ctx context.Context, orgID valuer.UUID, traceID string, req *model.GetFlamegraphSpansForTraceParams) (*model.GetFlamegraphSpansForTraceResponse, error)
// Setter Interfaces
SetTTL(ctx context.Context, orgID string, userEmail string, ttlParams *retentiontypes.TTLParams) (*retentiontypes.SetTTLResponseItem, *model.ApiError)
SetTTLV2(ctx context.Context, orgID string, userEmail string, params *retentiontypes.CustomRetentionTTLParams) (*retentiontypes.CustomRetentionTTLResponse, error)
SetTTL(ctx context.Context, orgID string, ttlParams *retentiontypes.TTLParams) (*retentiontypes.SetTTLResponseItem, *model.ApiError)
SetTTLV2(ctx context.Context, orgID string, params *retentiontypes.CustomRetentionTTLParams) (*retentiontypes.CustomRetentionTTLResponse, error)
FetchTemporality(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string]map[v3.Temporality]bool, error)
GetMetricAggregateAttributes(ctx context.Context, orgID valuer.UUID, req *v3.AggregateAttributeRequest, skipSignozMetrics bool) (*v3.AggregateAttributeResponse, error)

View File

@@ -198,7 +198,6 @@ func NewSQLMigrationProviderFactories(
sqlmigration.NewDropUserDeletedAtFactory(sqlstore, sqlschema),
sqlmigration.NewMigrateAWSAllRegionsFactory(sqlstore),
sqlmigration.NewAddServiceAccountManagedRoleTransactionsFactory(sqlstore),
sqlmigration.NewUpdateTTLSettingUserAuditFactory(sqlstore, sqlschema),
)
}

View File

@@ -1,84 +0,0 @@
package sqlmigration
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
)
type updateTTLSettingUserAudit struct {
sqlstore sqlstore.SQLStore
sqlschema sqlschema.SQLSchema
}
func NewUpdateTTLSettingUserAuditFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("update_ttl_setting_user_audit"), func(ctx context.Context, providerSettings factory.ProviderSettings, config Config) (SQLMigration, error) {
return newUpdateTTLSettingUserAudit(ctx, providerSettings, config, sqlstore, sqlschema)
})
}
func newUpdateTTLSettingUserAudit(_ context.Context, _ factory.ProviderSettings, _ Config, sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) (SQLMigration, error) {
return &updateTTLSettingUserAudit{
sqlstore: sqlstore,
sqlschema: sqlschema,
}, nil
}
func (migration *updateTTLSettingUserAudit) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *updateTTLSettingUserAudit) Up(ctx context.Context, db *bun.DB) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
table, uniqueConstraints, err := migration.sqlschema.GetTable(ctx, sqlschema.TableName("ttl_setting"))
if err != nil {
return err
}
columns := []*sqlschema.Column{
{
Name: sqlschema.ColumnName("created_by"),
DataType: sqlschema.DataTypeText,
Nullable: true,
},
{
Name: sqlschema.ColumnName("updated_by"),
DataType: sqlschema.DataTypeText,
Nullable: true,
},
}
for _, column := range columns {
sqls := migration.sqlschema.Operator().AddColumn(table, uniqueConstraints, column, nil)
for _, sql := range sqls {
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
return err
}
}
}
if err := tx.Commit(); err != nil {
return err
}
return nil
}
func (migration *updateTTLSettingUserAudit) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -140,7 +140,6 @@ type TTLSetting struct {
bun.BaseModel `bun:"table:ttl_setting"`
types.Identifiable
types.TimeAuditable
types.UserAuditable
TransactionID string `bun:"transaction_id,type:text,notnull"`
TableName string `bun:"table_name,type:text,notnull"`
TTL int `bun:"ttl,notnull,default:0"`