fix: do not sort in descending locally if the other is explicitly spe… (#10033)

This commit is contained in:
Srikanth Chekuri
2026-01-20 16:47:08 +05:30
committed by GitHub
parent f17a332c23
commit 14011bc277
8 changed files with 1340 additions and 108 deletions

View File

@@ -318,13 +318,14 @@ func (q *querier) applyFormulas(ctx context.Context, results map[string]*qbtypes
}
// Check if we're dealing with time series or scalar data
if req.RequestType == qbtypes.RequestTypeTimeSeries {
switch req.RequestType {
case qbtypes.RequestTypeTimeSeries:
result := q.processTimeSeriesFormula(ctx, results, formula, req)
if result != nil {
result = q.applySeriesLimit(result, formula.Limit, formula.Order)
results[name] = result
}
} else if req.RequestType == qbtypes.RequestTypeScalar {
case qbtypes.RequestTypeScalar:
result := q.processScalarFormula(ctx, results, formula, req)
if result != nil {
result = q.applySeriesLimit(result, formula.Limit, formula.Order)
@@ -581,11 +582,14 @@ func (q *querier) filterDisabledQueries(results map[string]*qbtypes.Result, req
}
// formatScalarResultsAsTable formats scalar results as a unified table for UI display
func (q *querier) formatScalarResultsAsTable(results map[string]*qbtypes.Result, _ *qbtypes.QueryRangeRequest) map[string]any {
func (q *querier) formatScalarResultsAsTable(results map[string]*qbtypes.Result, req *qbtypes.QueryRangeRequest) map[string]any {
if len(results) == 0 {
return map[string]any{"table": &qbtypes.ScalarData{}}
}
// apply default sorting if no order specified
applyDefaultSort := !req.HasOrderSpecified()
// Convert all results to ScalarData first
scalarResults := make(map[string]*qbtypes.ScalarData)
for name, result := range results {
@@ -600,13 +604,13 @@ func (q *querier) formatScalarResultsAsTable(results map[string]*qbtypes.Result,
if len(scalarResults) == 1 {
for _, sd := range scalarResults {
if hasMultipleQueries(sd) {
return map[string]any{"table": deduplicateRows(sd)}
return map[string]any{"table": deduplicateRows(sd, applyDefaultSort)}
}
}
}
// Otherwise merge all results
merged := mergeScalarData(scalarResults)
merged := mergeScalarData(scalarResults, applyDefaultSort)
return map[string]any{"table": merged}
}
@@ -687,7 +691,7 @@ func hasMultipleQueries(sd *qbtypes.ScalarData) bool {
}
// deduplicateRows removes duplicate rows based on group columns
func deduplicateRows(sd *qbtypes.ScalarData) *qbtypes.ScalarData {
func deduplicateRows(sd *qbtypes.ScalarData, applyDefaultSort bool) *qbtypes.ScalarData {
// Find group column indices
groupIndices := []int{}
for i, col := range sd.Columns {
@@ -696,8 +700,9 @@ func deduplicateRows(sd *qbtypes.ScalarData) *qbtypes.ScalarData {
}
}
// Build unique rows map
// Build unique rows map, preserve order
uniqueRows := make(map[string][]any)
var keyOrder []string
for _, row := range sd.Data {
key := buildRowKey(row, groupIndices)
if existing, found := uniqueRows[key]; found {
@@ -711,17 +716,20 @@ func deduplicateRows(sd *qbtypes.ScalarData) *qbtypes.ScalarData {
rowCopy := make([]any, len(row))
copy(rowCopy, row)
uniqueRows[key] = rowCopy
keyOrder = append(keyOrder, key)
}
}
// Convert back to slice
// Convert back to slice, preserve the original order
data := make([][]any, 0, len(uniqueRows))
for _, row := range uniqueRows {
data = append(data, row)
for _, key := range keyOrder {
data = append(data, uniqueRows[key])
}
// Sort by first aggregation column
sortByFirstAggregation(data, sd.Columns)
// sort by first aggregation (descending) if no order was specified
if applyDefaultSort {
sortByFirstAggregation(data, sd.Columns)
}
return &qbtypes.ScalarData{
Columns: sd.Columns,
@@ -730,7 +738,7 @@ func deduplicateRows(sd *qbtypes.ScalarData) *qbtypes.ScalarData {
}
// mergeScalarData merges multiple scalar data results
func mergeScalarData(results map[string]*qbtypes.ScalarData) *qbtypes.ScalarData {
func mergeScalarData(results map[string]*qbtypes.ScalarData, applyDefaultSort bool) *qbtypes.ScalarData {
// Collect unique group columns
groupCols := []string{}
groupColMap := make(map[string]*qbtypes.ColumnDescriptor)
@@ -770,10 +778,12 @@ func mergeScalarData(results map[string]*qbtypes.ScalarData) *qbtypes.ScalarData
}
}
// Merge rows
// Merge rows, preserve order
rowMap := make(map[string][]any)
var keyOrder []string
for queryName, sd := range results {
for _, queryName := range queryNames {
sd := results[queryName]
// Create index mappings
groupMap := make(map[string]int)
for i, col := range sd.Columns {
@@ -802,6 +812,7 @@ func mergeScalarData(results map[string]*qbtypes.ScalarData) *qbtypes.ScalarData
newRow[i] = "n/a"
}
rowMap[key] = newRow
keyOrder = append(keyOrder, key)
}
// Set aggregation values for this query
@@ -825,14 +836,16 @@ func mergeScalarData(results map[string]*qbtypes.ScalarData) *qbtypes.ScalarData
}
}
// Convert to slice
// Convert to slice, preserving insertion order
data := make([][]any, 0, len(rowMap))
for _, row := range rowMap {
data = append(data, row)
for _, key := range keyOrder {
data = append(data, rowMap[key])
}
// Sort by first aggregation column
sortByFirstAggregation(data, columns)
// sort by first aggregation (descending) if no order was specified
if applyDefaultSort {
sortByFirstAggregation(data, columns)
}
return &qbtypes.ScalarData{
Columns: columns,
@@ -888,7 +901,7 @@ func sortByFirstAggregation(data [][]any, columns []*qbtypes.ColumnDescriptor) {
// compareValues compares two values for sorting (handles n/a and numeric types)
func compareValues(a, b any) int {
// Handle n/a values
// n/a values gets pushed to the end
if a == "n/a" && b == "n/a" {
return 0
}

View File

@@ -276,6 +276,31 @@ func (r *QueryRangeRequest) NumAggregationForQuery(name string) int64 {
return int64(numAgg)
}
// HasOrderSpecified returns true if any query has an explicit order provided.
func (r *QueryRangeRequest) HasOrderSpecified() bool {
for _, query := range r.CompositeQuery.Queries {
switch spec := query.Spec.(type) {
case QueryBuilderQuery[TraceAggregation]:
if len(spec.Order) > 0 {
return true
}
case QueryBuilderQuery[LogAggregation]:
if len(spec.Order) > 0 {
return true
}
case QueryBuilderQuery[MetricAggregation]:
if len(spec.Order) > 0 {
return true
}
case QueryBuilderFormula:
if len(spec.Order) > 0 {
return true
}
}
}
return false
}
func (r *QueryRangeRequest) FuncsForQuery(name string) []Function {
funcs := []Function{}
for _, query := range r.CompositeQuery.Queries {

View File

@@ -122,7 +122,7 @@ def create_saml_client(
"config": {
"full.path": "false",
"attribute.nameformat": "Basic",
"single": "true", # ! this was changed to true as we need the groups in the single attribute section
"single": "true", # ! this was changed to true as we need the groups in the single attribute section
"friendly.name": "groups",
"attribute.name": "groups",
},
@@ -322,7 +322,9 @@ def get_oidc_settings(idp: types.TestContainerIDP) -> dict:
@pytest.fixture(name="create_user_idp", scope="function")
def create_user_idp(idp: types.TestContainerIDP) -> Callable[[str, str, bool, str, str], None]:
def create_user_idp(
idp: types.TestContainerIDP,
) -> Callable[[str, str, bool, str, str], None]:
client = KeycloakAdmin(
server_url=idp.container.host_configs["6060"].base(),
username=IDP_ROOT_USERNAME,
@@ -332,7 +334,13 @@ def create_user_idp(idp: types.TestContainerIDP) -> Callable[[str, str, bool, st
created_users = []
def _create_user_idp(email: str, password: str, verified: bool = True, first_name: str = "", last_name: str = "") -> None:
def _create_user_idp(
email: str,
password: str,
verified: bool = True,
first_name: str = "",
last_name: str = "",
) -> None:
payload = {
"username": email,
"email": email,
@@ -400,14 +408,14 @@ def create_group_idp(idp: types.TestContainerIDP) -> Callable[[str], str]:
for group_id in created_groups:
try:
client.delete_group(group_id)
except Exception: # pylint: disable=broad-exception-caught
except Exception: # pylint: disable=broad-exception-caught
pass
@pytest.fixture(name="create_user_idp_with_groups", scope="function")
def create_user_idp_with_groups(
idp: types.TestContainerIDP,
create_group_idp: Callable[[str], str], # pylint: disable=redefined-outer-name
create_group_idp: Callable[[str], str], # pylint: disable=redefined-outer-name
) -> Callable[[str, str, bool, List[str]], None]:
"""Creates a user in Keycloak IDP with specified groups."""
client = KeycloakAdmin(
@@ -450,14 +458,14 @@ def create_user_idp_with_groups(
for user_id in created_users:
try:
client.delete_user(user_id)
except Exception: # pylint: disable=broad-exception-caught
except Exception: # pylint: disable=broad-exception-caught
pass
@pytest.fixture(name="add_user_to_group", scope="function")
def add_user_to_group(
idp: types.TestContainerIDP,
create_group_idp: Callable[[str], str], # pylint: disable=redefined-outer-name
create_group_idp: Callable[[str], str], # pylint: disable=redefined-outer-name
) -> Callable[[str, str], None]:
"""Adds an existing user to a group."""
client = KeycloakAdmin(
@@ -478,7 +486,7 @@ def add_user_to_group(
@pytest.fixture(name="create_user_idp_with_role", scope="function")
def create_user_idp_with_role(
idp: types.TestContainerIDP,
create_group_idp: Callable[[str], str], # pylint: disable=redefined-outer-name
create_group_idp: Callable[[str], str], # pylint: disable=redefined-outer-name
) -> Callable[[str, str, bool, str, List[str]], None]:
"""Creates a user in Keycloak IDP with a custom role attribute and optional groups."""
client = KeycloakAdmin(
@@ -524,13 +532,14 @@ def create_user_idp_with_role(
for user_id in created_users:
try:
client.delete_user(user_id)
except Exception: # pylint: disable=broad-exception-caught
except Exception: # pylint: disable=broad-exception-caught
pass
@pytest.fixture(name="setup_user_profile", scope="package")
def setup_user_profile(idp: types.TestContainerIDP) -> Callable[[], None]:
"""Setup Keycloak User Profile with signoz_role attribute."""
def _setup_user_profile() -> None:
client = KeycloakAdmin(
server_url=idp.container.host_configs["6060"].base(),
@@ -544,24 +553,25 @@ def setup_user_profile(idp: types.TestContainerIDP) -> Callable[[], None]:
# Check if signoz_role attribute already exists
attributes = profile.get("attributes", [])
signoz_role_exists = any(attr.get("name") == "signoz_role" for attr in attributes)
signoz_role_exists = any(
attr.get("name") == "signoz_role" for attr in attributes
)
if not signoz_role_exists:
# Add signoz_role attribute to user profile
attributes.append({
"name": "signoz_role",
"displayName": "SigNoz Role",
"validations": {},
"annotations": {},
# "required": {
# "roles": [] # Not required
# },
"permissions": {
"view": ["admin", "user"],
"edit": ["admin"]
},
"multivalued": False
})
attributes.append(
{
"name": "signoz_role",
"displayName": "SigNoz Role",
"validations": {},
"annotations": {},
# "required": {
# "roles": [] # Not required
# },
"permissions": {"view": ["admin", "user"], "edit": ["admin"]},
"multivalued": False,
}
)
profile["attributes"] = attributes
# Update the realm user profile
@@ -652,11 +662,11 @@ def get_user_by_email(signoz: types.SigNoz, admin_token: str, email: str) -> dic
def perform_oidc_login(
signoz: types.SigNoz, # pylint: disable=unused-argument
signoz: types.SigNoz, # pylint: disable=unused-argument
idp: types.TestContainerIDP,
driver: webdriver.Chrome,
get_session_context: Callable[[str], str],
idp_login: Callable[[str, str], None], # pylint: disable=redefined-outer-name
idp_login: Callable[[str, str], None], # pylint: disable=redefined-outer-name
email: str,
password: str,
) -> None:
@@ -688,10 +698,10 @@ def get_saml_domain(signoz: types.SigNoz, admin_token: str) -> dict:
def perform_saml_login(
signoz: types.SigNoz, # pylint: disable=unused-argument
signoz: types.SigNoz, # pylint: disable=unused-argument
driver: webdriver.Chrome,
get_session_context: Callable[[str], str],
idp_login: Callable[[str, str], None], # pylint: disable=redefined-outer-name
idp_login: Callable[[str, str], None], # pylint: disable=redefined-outer-name
email: str,
password: str,
) -> None:

View File

@@ -329,3 +329,130 @@ def find_named_result(
),
None,
)
def build_scalar_query(
name: str,
signal: str,
aggregations: List[Dict],
*,
group_by: Optional[List[Dict]] = None,
order: Optional[List[Dict]] = None,
limit: Optional[int] = None,
filter_expression: Optional[str] = None,
having_expression: Optional[str] = None,
step_interval: int = DEFAULT_STEP_INTERVAL,
disabled: bool = False,
) -> Dict:
spec: Dict[str, Any] = {
"name": name,
"signal": signal,
"stepInterval": step_interval,
"disabled": disabled,
"aggregations": aggregations,
}
if group_by:
spec["groupBy"] = group_by
if order:
spec["order"] = order
if limit is not None:
spec["limit"] = limit
if filter_expression:
spec["filter"] = {"expression": filter_expression}
if having_expression:
spec["having"] = {"expression": having_expression}
return {"type": "builder_query", "spec": spec}
def build_group_by_field(
name: str,
field_data_type: str = "string",
field_context: str = "resource",
) -> Dict:
return {
"name": name,
"fieldDataType": field_data_type,
"fieldContext": field_context,
}
def build_order_by(name: str, direction: str = "desc") -> Dict:
return {"key": {"name": name}, "direction": direction}
def build_logs_aggregation(expression: str, alias: Optional[str] = None) -> Dict:
agg: Dict[str, Any] = {"expression": expression}
if alias:
agg["alias"] = alias
return agg
def build_metrics_aggregation(
metric_name: str,
time_aggregation: str,
space_aggregation: str,
temporality: str = "cumulative",
) -> Dict:
return {
"metricName": metric_name,
"temporality": temporality,
"timeAggregation": time_aggregation,
"spaceAggregation": space_aggregation,
}
def get_scalar_table_data(response_json: Dict) -> List[List[Any]]:
results = response_json.get("data", {}).get("data", {}).get("results", [])
if not results:
return []
return results[0].get("data", [])
def get_scalar_columns(response_json: Dict) -> List[Dict]:
results = response_json.get("data", {}).get("data", {}).get("results", [])
if not results:
return []
return results[0].get("columns", [])
def assert_scalar_result_order(
data: List[List[Any]],
expected_order: List[tuple],
context: str = "",
) -> None:
assert len(data) == len(expected_order), (
f"{context}: Expected {len(expected_order)} rows, got {len(data)}. "
f"Data: {data}"
)
for i, (row, expected) in enumerate(zip(data, expected_order)):
for j, expected_val in enumerate(expected):
actual_val = row[j]
assert actual_val == expected_val, (
f"{context}: Row {i}, column {j} mismatch. "
f"Expected {expected_val}, got {actual_val}. "
f"Full row: {row}, expected: {expected}"
)
def assert_scalar_column_order(
data: List[List[Any]],
column_index: int,
expected_values: List[Any],
context: str = "",
) -> None:
assert len(data) == len(
expected_values
), f"{context}: Expected {len(expected_values)} rows, got {len(data)}"
actual_values = [row[column_index] for row in data]
assert actual_values == expected_values, (
f"{context}: Column {column_index} order mismatch. "
f"Expected {expected_values}, got {actual_values}"
)

View File

@@ -82,7 +82,10 @@ def test_create_and_get_domain(
assert len(data) == 2
for domain in data:
assert domain["name"] in ["domain-google.integration.test", "domain-saml.integration.test"]
assert domain["name"] in [
"domain-google.integration.test",
"domain-saml.integration.test",
]
assert domain["ssoType"] in ["google_auth", "saml"]

View File

@@ -1,17 +1,21 @@
import uuid
from http import HTTPStatus
from typing import Any, Callable, Dict, List
import requests
from selenium import webdriver
from wiremock.resources.mappings import Mapping
import uuid
from fixtures.auth import (
USER_ADMIN_EMAIL,
USER_ADMIN_PASSWORD,
add_license,
)
from fixtures.idputils import get_saml_domain, perform_saml_login, get_user_by_email, delete_keycloak_client
from fixtures.idputils import (
get_saml_domain,
get_user_by_email,
perform_saml_login,
)
from fixtures.types import Operation, SigNoz, TestContainerDocker, TestContainerIDP
@@ -258,7 +262,9 @@ def test_saml_role_mapping_single_group_admin(
email = "admin-group-user@saml.integration.test"
create_user_idp_with_groups(email, "password", True, ["signoz-admins"])
perform_saml_login(signoz, driver, get_session_context, idp_login, email, "password")
perform_saml_login(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -282,7 +288,9 @@ def test_saml_role_mapping_single_group_editor(
email = "editor-group-user@saml.integration.test"
create_user_idp_with_groups(email, "password", True, ["signoz-editors"])
perform_saml_login(signoz, driver, get_session_context, idp_login, email, "password")
perform_saml_login(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -306,9 +314,13 @@ def test_saml_role_mapping_multiple_groups_highest_wins(
Expected: User gets EDITOR (highest of VIEWER and EDITOR).
"""
email = f"multi-group-user-{uuid.uuid4().hex[:8]}@saml.integration.test"
create_user_idp_with_groups(email, "password", True, ["signoz-viewers", "signoz-editors"])
create_user_idp_with_groups(
email, "password", True, ["signoz-viewers", "signoz-editors"]
)
perform_saml_login(signoz, driver, get_session_context, idp_login, email, "password")
perform_saml_login(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -333,7 +345,9 @@ def test_saml_role_mapping_explicit_viewer_group(
email = "viewer-group-user@saml.integration.test"
create_user_idp_with_groups(email, "password", True, ["signoz-viewers"])
perform_saml_login(signoz, driver, get_session_context, idp_login, email, "password")
perform_saml_login(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -357,7 +371,9 @@ def test_saml_role_mapping_unmapped_group_uses_default(
email = "unmapped-group-user@saml.integration.test"
create_user_idp_with_groups(email, "password", True, ["some-other-group"])
perform_saml_login(signoz, driver, get_session_context, idp_login, email, "password")
perform_saml_login(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -432,7 +448,9 @@ def test_saml_role_mapping_role_claim_takes_precedence(
email = "role-claim-precedence@saml.integration.test"
create_user_idp_with_role(email, "password", True, "ADMIN", ["signoz-editors"])
perform_saml_login(signoz, driver, get_session_context, idp_login, email, "password")
perform_saml_login(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -460,7 +478,9 @@ def test_saml_role_mapping_invalid_role_claim_fallback(
email = "invalid-role-user@saml.integration.test"
create_user_idp_with_role(email, "password", True, "SUPERADMIN", ["signoz-editors"])
perform_saml_login(signoz, driver, get_session_context, idp_login, email, "password")
perform_saml_login(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -488,7 +508,9 @@ def test_saml_role_mapping_case_insensitive(
email = "lowercase-role-user@saml.integration.test"
create_user_idp_with_role(email, "password", True, "admin", [])
perform_saml_login(signoz, driver, get_session_context, idp_login, email, "password")
perform_saml_login(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -499,7 +521,7 @@ def test_saml_role_mapping_case_insensitive(
def test_saml_name_mapping(
signoz: SigNoz,
idp: TestContainerIDP, # pylint: disable=unused-argument
idp: TestContainerIDP, # pylint: disable=unused-argument
driver: webdriver.Chrome,
create_user_idp: Callable[[str, str, bool, str, str], None],
idp_login: Callable[[str, str], None],
@@ -511,19 +533,23 @@ def test_saml_name_mapping(
create_user_idp(email, "password", True, "Jane", "Smith")
perform_saml_login(signoz, driver, get_session_context, idp_login, email, "password")
perform_saml_login(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["displayName"] == "Jane" # We are only mapping the first name here
assert (
found_user["displayName"] == "Jane"
) # We are only mapping the first name here
assert found_user["role"] == "VIEWER"
def test_saml_empty_name_fallback(
signoz: SigNoz,
idp: TestContainerIDP, # pylint: disable=unused-argument
idp: TestContainerIDP, # pylint: disable=unused-argument
driver: webdriver.Chrome,
create_user_idp: Callable[[str, str, bool, str, str], None],
idp_login: Callable[[str, str], None],
@@ -535,7 +561,9 @@ def test_saml_empty_name_fallback(
create_user_idp(email, "password", True)
perform_saml_login(signoz, driver, get_session_context, idp_login, email, "password")
perform_saml_login(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)

View File

@@ -11,7 +11,11 @@ from fixtures.auth import (
USER_ADMIN_PASSWORD,
add_license,
)
from fixtures.idputils import get_oidc_domain, get_user_by_email, perform_oidc_login, delete_keycloak_client
from fixtures.idputils import (
get_oidc_domain,
get_user_by_email,
perform_oidc_login,
)
from fixtures.types import Operation, SigNoz, TestContainerDocker, TestContainerIDP
@@ -196,7 +200,9 @@ def test_oidc_role_mapping_single_group_admin(
email = "admin-group-user@oidc.integration.test"
create_user_idp_with_groups(email, "password123", True, ["signoz-admins"])
perform_oidc_login(signoz, idp, driver, get_session_context, idp_login, email, "password123")
perform_oidc_login(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -220,7 +226,9 @@ def test_oidc_role_mapping_single_group_editor(
email = "editor-group-user@oidc.integration.test"
create_user_idp_with_groups(email, "password123", True, ["signoz-editors"])
perform_oidc_login(signoz, idp, driver, get_session_context, idp_login, email, "password123")
perform_oidc_login(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -244,9 +252,13 @@ def test_oidc_role_mapping_multiple_groups_highest_wins(
Expected: User gets ADMIN (highest of the two).
"""
email = "multi-group-user@oidc.integration.test"
create_user_idp_with_groups(email, "password123", True, ["signoz-viewers", "signoz-admins"])
create_user_idp_with_groups(
email, "password123", True, ["signoz-viewers", "signoz-admins"]
)
perform_oidc_login(signoz, idp, driver, get_session_context, idp_login, email, "password123")
perform_oidc_login(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -271,7 +283,9 @@ def test_oidc_role_mapping_explicit_viewer_group(
email = "viewer-group-user@oidc.integration.test"
create_user_idp_with_groups(email, "password123", True, ["signoz-viewers"])
perform_oidc_login(signoz, idp, driver, get_session_context, idp_login, email, "password123")
perform_oidc_login(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -295,7 +309,9 @@ def test_oidc_role_mapping_unmapped_group_uses_default(
email = "unmapped-group-user@oidc.integration.test"
create_user_idp_with_groups(email, "password123", True, ["some-other-group"])
perform_oidc_login(signoz, idp, driver, get_session_context, idp_login, email, "password123")
perform_oidc_login(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -373,7 +389,9 @@ def test_oidc_role_mapping_role_claim_takes_precedence(
email = "role-claim-precedence@oidc.integration.test"
create_user_idp_with_role(email, "password123", True, "ADMIN", ["signoz-editors"])
perform_oidc_login(signoz, idp, driver, get_session_context, idp_login, email, "password123")
perform_oidc_login(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -399,9 +417,13 @@ def test_oidc_role_mapping_invalid_role_claim_fallback(
"""
setup_user_profile()
email = "invalid-role-user@oidc.integration.test"
create_user_idp_with_role(email, "password123", True, "SUPERADMIN", ["signoz-editors"])
create_user_idp_with_role(
email, "password123", True, "SUPERADMIN", ["signoz-editors"]
)
perform_oidc_login(signoz, idp, driver, get_session_context, idp_login, email, "password123")
perform_oidc_login(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -429,7 +451,9 @@ def test_oidc_role_mapping_case_insensitive(
email = "lowercase-role-user@oidc.integration.test"
create_user_idp_with_role(email, "password123", True, "editor", [])
perform_oidc_login(signoz, idp, driver, get_session_context, idp_login, email, "password123")
perform_oidc_login(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
@@ -451,15 +475,11 @@ def test_oidc_name_mapping(
email = "named-user@oidc.integration.test"
# Create user with explicit first/last name
create_user_idp(
email,
"password123",
True,
first_name="John",
last_name="Doe"
)
create_user_idp(email, "password123", True, first_name="John", last_name="Doe")
perform_oidc_login(signoz, idp, driver, get_session_context, idp_login, email, "password123")
perform_oidc_login(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
@@ -493,7 +513,9 @@ def test_oidc_empty_name_uses_fallback(
# Create user without first/last name
create_user_idp(email, "password123", True)
perform_oidc_login(signoz, idp, driver, get_session_context, idp_login, email, "password123")
perform_oidc_login(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(

File diff suppressed because it is too large Load Diff