mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-03 08:33:26 +00:00
fix: do not sort in descending locally if the other is explicitly spe… (#10033)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}"
|
||||
)
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
1004
tests/integration/src/querier/06_order_by_table_querier.py
Normal file
1004
tests/integration/src/querier/06_order_by_table_querier.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user