Compare commits

..

6 Commits

Author SHA1 Message Date
nikhilmantri0902
16937b4093 chore: variable name update: 2026-06-03 12:37:52 +05:30
nikhilmantri0902
ec13802915 chore: combining two functions into a common flow 2026-06-03 11:51:30 +05:30
nikhilmantri0902
ca14a46471 chore: build fix 2026-06-03 11:14:17 +05:30
Nikhil Mantri
219f2965fe Merge branch 'main' into metricsExplorer/get_stats_fast_path 2026-06-03 10:51:25 +05:30
nikhilmantri0902
05e84b04cc chore: rearrange and readable 2026-05-26 18:16:35 +05:30
nikhilmantri0902
def414c7bb chore: added fastPath 2026-05-26 17:38:03 +05:30
19 changed files with 79 additions and 260 deletions

View File

@@ -8028,64 +8028,6 @@ paths:
summary: Update account
tags:
- cloudintegration
/api/v1/cloud_integrations/{cloud_provider}/accounts/{id}/services:
get:
deprecated: false
description: This endpoint lists the services metadata for the specified account
and cloud provider
operationId: ListAccountServicesMetadata
parameters:
- in: path
name: cloud_provider
required: true
schema:
type: string
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/CloudintegrationtypesGettableServicesMetadata'
status:
type: string
required:
- status
- data
type: object
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: List account services metadata
tags:
- cloudintegration
/api/v1/cloud_integrations/{cloud_provider}/accounts/{id}/services/{service_id}:
put:
deprecated: false

View File

@@ -359,7 +359,8 @@ function CustomTimePickerPopoverContent({
<Clock
color={Color.BG_ROBIN_400}
className="timezone-container__clock-icon"
size={14}
height={12}
width={12}
/>
<span className="timezone__name">{timezone.name}</span>

View File

@@ -109,16 +109,6 @@ $custom-border-color: #2c3044;
color: color-mix(in srgb, var(--l2-foreground) 45%, transparent);
}
.ant-select-clear {
background-color: var(--l2-background);
color: var(--l2-foreground);
font-size: 12px;
&:hover {
color: var(--l1-foreground);
}
}
// Customize tags in multiselect (dark mode by default)
.ant-select-selection-item {
background-color: var(--l1-border);
@@ -402,9 +392,7 @@ $custom-border-color: #2c3044;
// Custom dropdown styles for multi-select
.custom-multiselect-dropdown {
padding: 8px 0 0 0;
// Tall enough to hold the react-virtuoso list (<=300px) + header/footer
// so only the list scrolls (avoids a second scrollbar on this container).
max-height: 410px;
max-height: 350px;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: thin;
@@ -495,12 +483,8 @@ $custom-border-color: #2c3044;
.option-checkbox {
width: 100%;
// @signozhq/ui Checkbox renders children inside a <label> that is
// content-sized by default. Make it fill the row (min-width: 0 lets it
// shrink) so the option text below can truncate instead of overflowing.
> label {
flex: 1 1 auto;
min-width: 0;
> span:not(.ant-checkbox) {
width: 100%;
}
.all-option-text {
@@ -517,12 +501,7 @@ $custom-border-color: #2c3044;
width: 100%;
.option-label-text {
flex: 1 1 auto;
min-width: 0;
margin-bottom: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.option-badge {
@@ -535,30 +514,26 @@ $custom-border-color: #2c3044;
}
}
// Size the buttons to the row's resting content height (20px) and fully
// override antd's default 32px Button box, so revealing them on hover
// never changes the row height.
.only-btn,
.only-btn {
display: none;
}
.toggle-btn {
display: none;
align-items: center;
justify-content: center;
height: 18px;
min-height: 0;
padding: 0 6px;
font-size: 12px;
line-height: 1;
border: none;
box-shadow: none;
}
&:hover {
background-color: unset;
}
.only-btn:hover {
background-color: unset;
}
.toggle-btn:hover {
background-color: unset;
}
.option-content:hover {
.only-btn {
display: flex;
align-items: center;
justify-content: center;
height: 21px;
}
.toggle-btn {
display: none;
@@ -573,6 +548,9 @@ $custom-border-color: #2c3044;
.option-checkbox:hover {
.toggle-btn {
display: flex;
align-items: center;
justify-content: center;
height: 21px;
}
.option-badge {
display: none;

View File

@@ -67,8 +67,8 @@
gap: 4px;
&--success {
background: color-mix(in srgb, var(--text-forest-500) 10%, transparent);
color: var(--text-forest-400);
background: color-mix(in srgb, var(--success-background) 10%, transparent);
color: var(--success-foreground);
}
&--error {

View File

@@ -153,7 +153,6 @@
font-size: 10px;
color: var(--l2-foreground);
margin-top: 4px;
display: block;
}
}

View File

@@ -47,10 +47,18 @@
}
.ant-tabs-tab-active {
.overview-btn,
.variables-btn,
.overview-btn {
border-radius: 2px 0px 0px 2px;
background: var(--l1-border);
}
.variables-btn {
border-radius: 2px 0px 0px 2px;
background: var(--l1-border);
}
.public-dashboard-btn {
color: var(--primary-background);
border-radius: 2px 0px 0px 2px;
background: var(--l1-border);
}
}

View File

@@ -127,15 +127,6 @@
align-items: center;
justify-content: center;
gap: 4px;
.sidenav-beta-tag {
margin-left: 4px;
}
div {
display: flex;
align-items: center;
}
}
.variable-type-btn + .variable-type-btn {
@@ -186,7 +177,6 @@
.multiple-values-section {
justify-content: space-between;
align-items: flex-start;
margin-bottom: 0;
.typography-variables {
@@ -203,7 +193,6 @@
.all-option-section {
justify-content: space-between;
align-items: flex-start;
margin-bottom: 0;
.typography-variables {

View File

@@ -518,6 +518,7 @@ function VariableItem({
size={14}
style={{
color: isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_500,
marginTop: 1,
}}
/>
}
@@ -613,6 +614,7 @@ function VariableItem({
size={14}
style={{
color: isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_500,
marginTop: 1,
}}
/>
}

View File

@@ -19,7 +19,6 @@
}
.ant-btn-default {
border-color: transparent;
box-shadow: none;
}
}
.ant-tabs-tab-active {

View File

@@ -72,20 +72,8 @@
.alert-rule-scope {
margin-bottom: 12px;
// `.createForm label` styles field labels (font-weight 500, 14px,
// 6px bottom padding). Those bleed into the @signozhq/ui RadioGroup
// option labels, making them bold and vertically misaligned with the
// radio control. Reset them back to plain option-text styling.
label {
padding: 0;
font-weight: 400;
line-height: normal;
}
// Loosen the design-system default (grid gap 0.5rem) between options.
.silence-alerts-radio-group {
margin-top: 8px;
gap: 12px;
.ant-radio-wrapper {
color: var(--l1-foreground);
}
}
@@ -156,7 +144,10 @@
display: flex;
align-items: center;
gap: 8px;
margin-top: 12px;
.ant-btn {
margin-top: 8px;
}
}
.schedule-created-at {

View File

@@ -54,7 +54,8 @@ import {
} from './PlannedDowntimeutils';
import './PlannedDowntime.styles.scss';
import { RadioGroupItem, RadioGroup } from '@signozhq/ui/radio-group';
import { RadioGroupItem } from '@signozhq/ui/radio-group';
import { RadioGroup } from '@signozhq/ui/radio-group';
dayjs.locale('en');
dayjs.extend(utc);
@@ -470,7 +471,7 @@ export function PlannedDowntimeForm(
initialValue="specific"
className="alert-rule-scope"
>
<RadioGroup className="silence-alerts-radio-group">
<RadioGroup>
<RadioGroupItem value="all">All alert rules</RadioGroupItem>
<RadioGroupItem value="specific">Specific alert rules</RadioGroupItem>
</RadioGroup>

View File

@@ -35,7 +35,10 @@
display: flex;
align-items: center;
gap: 8px;
margin-top: 12px;
.ant-btn {
margin-top: 8px;
}
}
.routing-policies-table {

View File

@@ -8,7 +8,7 @@
139deg,
color-mix(in srgb, var(--card) 80%, transparent) 0%,
color-mix(in srgb, var(--card) 90%, transparent) 98.68%
) !important;
);
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
padding: 0;
@@ -34,9 +34,9 @@
}
.refresh-interval-text {
padding: 12px 14px 8px 14px !important;
padding: 12px 14px 8px 14px;
color: var(--muted-foreground);
font-size: 13px;
font-size: 11px;
font-style: normal;
font-weight: 500;
line-height: 18px; /* 163.636% */

View File

@@ -9,7 +9,7 @@
}
.ant-tabs-nav {
padding-left: 16px;
padding: 0 8px;
margin-bottom: 0px;
&::before {

View File

@@ -151,26 +151,6 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts/{id}/services", handler.New(
provider.authzMiddleware.AdminAccess(provider.cloudIntegrationHandler.ListAccountServicesMetadata),
handler.OpenAPIDef{
ID: "ListAccountServicesMetadata",
Tags: []string{"cloudintegration"},
Summary: "List account services metadata",
Description: "This endpoint lists the services metadata for the specified account and cloud provider",
Request: nil,
RequestContentType: "",
Response: new(citypes.GettableServicesMetadata),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/services/{service_id}", handler.New(
provider.authzMiddleware.AdminAccess(provider.cloudIntegrationHandler.GetService),
handler.OpenAPIDef{

View File

@@ -75,7 +75,6 @@ type Handler interface {
UpdateAccount(http.ResponseWriter, *http.Request)
DisconnectAccount(http.ResponseWriter, *http.Request)
ListServicesMetadata(http.ResponseWriter, *http.Request)
ListAccountServicesMetadata(http.ResponseWriter, *http.Request)
GetService(http.ResponseWriter, *http.Request)
UpdateService(http.ResponseWriter, *http.Request)
AgentCheckIn(http.ResponseWriter, *http.Request)

View File

@@ -276,44 +276,6 @@ func (handler *handler) ListServicesMetadata(rw http.ResponseWriter, r *http.Req
render.Success(rw, http.StatusOK, cloudintegrationtypes.NewGettableServicesMetadata(services))
}
func (handler *handler) ListAccountServicesMetadata(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
provider, err := cloudintegrationtypes.NewCloudProvider(mux.Vars(r)["cloud_provider"])
if err != nil {
render.Error(rw, err)
return
}
accountID, err := valuer.NewUUID(mux.Vars(r)["id"])
if err != nil {
render.Error(rw, err)
return
}
// check if integration account exists and is not removed.
_, err = handler.module.GetConnectedAccount(ctx, valuer.MustNewUUID(claims.OrgID), accountID, provider)
if err != nil {
render.Error(rw, err)
return
}
services, err := handler.module.ListServicesMetadata(ctx, valuer.MustNewUUID(claims.OrgID), provider, accountID)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, cloudintegrationtypes.NewGettableServicesMetadata(services))
}
func (handler *handler) GetService(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -478,3 +440,4 @@ func (handler *handler) AgentCheckIn(rw http.ResponseWriter, r *http.Request) {
render.Success(rw, http.StatusOK, cloudintegrationtypes.NewGettableAgentCheckIn(provider, resp))
}

View File

@@ -219,19 +219,7 @@ func (m *module) GetStats(ctx context.Context, orgID valuer.UUID, req *metricsex
return nil, err
}
filterWhereClause, err := m.buildFilterClause(ctx, req.Filter, req.Start, req.End)
if err != nil {
return nil, err
}
// Single query to get stats with samples, timeseries counts in required sorting order
metricStats, total, err := m.fetchMetricsStatsWithSamples(
ctx,
req,
filterWhereClause,
false,
req.OrderBy,
)
metricStats, total, err := m.fetchMetricsStatsWithSamples(ctx, req, false)
if err != nil {
return nil, err
}
@@ -974,15 +962,23 @@ func (m *module) buildFilterClause(ctx context.Context, filter *qbtypes.Filter,
func (m *module) fetchMetricsStatsWithSamples(
ctx context.Context,
req *metricsexplorertypes.StatsRequest,
filterWhereClause *sqlbuilder.WhereClause,
normalized bool,
orderBy *qbtypes.OrderBy,
) ([]metricsexplorertypes.Stat, uint64, error) {
ctx = m.withMetricsExplorerContext(ctx, "fetchMetricsStatsWithSamples")
hasFilter := req.Filter != nil && strings.TrimSpace(req.Filter.Expression) != ""
var filterWhereClause *sqlbuilder.WhereClause
if hasFilter {
var err error
filterWhereClause, err = m.buildFilterClause(ctx, req.Filter, req.Start, req.End)
if err != nil {
return nil, 0, err
}
}
start, end, distributedTsTable, localTsTable := telemetrymetrics.WhichTSTableToUse(uint64(req.Start), uint64(req.End), nil)
samplesTable, _ := telemetrymetrics.WhichSamplesTableToUse(uint64(req.Start), uint64(req.End), metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil)
countExp := telemetrymetrics.CountExpressionForSamplesTable(samplesTable)
distributedSamplesTable, _ := telemetrymetrics.WhichSamplesTableToUse(uint64(req.Start), uint64(req.End), metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil)
countExp := telemetrymetrics.CountExpressionForSamplesTable(distributedSamplesTable)
// Timeseries counts per metric
tsSB := sqlbuilder.NewSelectBuilder()
@@ -1005,7 +1001,7 @@ func (m *module) fetchMetricsStatsWithSamples(
"metric_name",
fmt.Sprintf("%s AS samples", countExp),
)
samplesSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, samplesTable))
samplesSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, distributedSamplesTable))
samplesSB.Where(samplesSB.Between("unix_milli", req.Start, req.End))
samplesSB.Where("NOT startsWith(metric_name, 'signoz')")
@@ -1013,6 +1009,8 @@ func (m *module) fetchMetricsStatsWithSamples(
sqlbuilder.CTEQuery("__time_series_counts").As(tsSB),
}
// Narrow samples scan. With filter: fingerprint IN (per-fingerprint label preds can't fold to metric_name).
// No filter (fast path): metric_name IN — aligns with samples table's leading sort key, orders of magnitude cheaper.
if filterWhereClause != nil {
fingerprintSB := sqlbuilder.NewSelectBuilder()
fingerprintSB.Select("fingerprint")
@@ -1025,6 +1023,15 @@ func (m *module) fetchMetricsStatsWithSamples(
ctes = append(ctes, sqlbuilder.CTEQuery("__filtered_fingerprints").As(fingerprintSB))
samplesSB.Where("fingerprint IN (SELECT fingerprint FROM __filtered_fingerprints)")
} else {
metricNamesSB := sqlbuilder.NewSelectBuilder()
metricNamesSB.Select("DISTINCT metric_name")
metricNamesSB.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, localTsTable))
metricNamesSB.Where(metricNamesSB.Between("unix_milli", start, end))
metricNamesSB.Where("NOT startsWith(metric_name, 'signoz')")
metricNamesSB.Where(metricNamesSB.E("__normalized", normalized))
samplesSB.Where(fmt.Sprintf("metric_name IN (%s)", samplesSB.Var(metricNamesSB)))
}
samplesSB.GroupBy("metric_name")
@@ -1041,7 +1048,7 @@ func (m *module) fetchMetricsStatsWithSamples(
finalSB.JoinWithOption(sqlbuilder.FullOuterJoin, "__sample_counts s", "ts.metric_name = s.metric_name")
finalSB.Where("(COALESCE(ts.timeseries, 0) > 0 OR COALESCE(s.samples, 0) > 0)")
orderByColumn, orderDirection, err := getStatsOrderByColumn(orderBy)
orderByColumn, orderDirection, err := getStatsOrderByColumn(req.OrderBy)
if err != nil {
return nil, 0, err
}

View File

@@ -86,49 +86,6 @@ def test_list_services_with_account(
assert svc["enabled"] is False, f"Service {svc['id']} should be disabled before any config is set"
EC2_SERVICE_ID = "ec2"
def test_list_account_services(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""ListAccountServicesMetadata reflects enabled state after enabling a service."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
account = create_cloud_integration_account(admin_token, CLOUD_PROVIDER)
account_id = account["id"]
checkin = simulate_agent_checkin(signoz, admin_token, CLOUD_PROVIDER, account_id, str(uuid.uuid4()))
assert checkin.status_code == HTTPStatus.OK, f"Check-in failed: {checkin.text}"
put_response = requests.put(
signoz.self.host_configs["8080"].get(f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}/services/{EC2_SERVICE_ID}"),
headers={"Authorization": f"Bearer {admin_token}"},
json={"config": {"aws": {"metrics": {"enabled": True}, "logs": {"enabled": True}}}},
timeout=10,
)
assert put_response.status_code == HTTPStatus.NO_CONTENT, f"Enable ec2 failed: {put_response.status_code}: {put_response.text}"
list_response = requests.get(
signoz.self.host_configs["8080"].get(f"/api/v1/cloud_integrations/{CLOUD_PROVIDER}/accounts/{account_id}/services"),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert list_response.status_code == HTTPStatus.OK, f"Expected 200, got {list_response.status_code}"
data = list_response.json()["data"]
assert "services" in data, "Response should contain 'services' field"
assert isinstance(data["services"], list), "services should be a list"
assert len(data["services"]) > 0, "services list should be non-empty"
ec2_service = next((s for s in data["services"] if s["id"] == EC2_SERVICE_ID), None)
assert ec2_service is not None, f"EC2 service '{EC2_SERVICE_ID}' not found in services list"
assert ec2_service["enabled"] is True, f"EC2 service should be enabled, got: {ec2_service['enabled']}"
def test_get_service_details_without_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument