diff --git a/apps/api/Cargo.toml b/apps/api/Cargo.toml index 35211c857..b52fea08c 100644 --- a/apps/api/Cargo.toml +++ b/apps/api/Cargo.toml @@ -47,6 +47,7 @@ mockito = "1.2.0" mockall = "0.12.1" bb8-redis = "0.18.0" indexmap = { version = "2.2.6", features = ["serde"] } +itertools = "0.14" once_cell = "1.20.2" rustls = { version = "0.23", features = ["ring"] } rustls-native-certs = "0.8" diff --git a/apps/api/libs/handlers/Cargo.toml b/apps/api/libs/handlers/Cargo.toml index 8b59a7f59..8726b17c7 100644 --- a/apps/api/libs/handlers/Cargo.toml +++ b/apps/api/libs/handlers/Cargo.toml @@ -37,6 +37,7 @@ semantic_layer = { path = "../semantic_layer" } # Add any handler-specific dependencies here dashmap = "5.5.3" +itertools = { workspace = true } # Add stored_values dependency stored_values = { path = "../stored_values" } diff --git a/apps/api/libs/handlers/src/collections/get_collection_handler.rs b/apps/api/libs/handlers/src/collections/get_collection_handler.rs index f8382661e..8fbc1a805 100644 --- a/apps/api/libs/handlers/src/collections/get_collection_handler.rs +++ b/apps/api/libs/handlers/src/collections/get_collection_handler.rs @@ -11,6 +11,7 @@ use database::{ }; use diesel::{ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl, Queryable}; use diesel_async::RunQueryDsl; +use itertools::Itertools; use middleware::AuthenticatedUser; use sharing::{check_permission_access, compute_effective_permission}; use tracing; @@ -156,7 +157,10 @@ pub async fn get_collection_handler( name: p.name, avatar_url: p.avatar_url, }) - .collect::>(), + .collect::>() + .into_iter() + .sorted_by(|a, b| a.email.to_lowercase().cmp(&b.email.to_lowercase())) + .collect(), ) } } diff --git a/apps/api/libs/handlers/src/dashboards/get_dashboard_handler.rs b/apps/api/libs/handlers/src/dashboards/get_dashboard_handler.rs index c83508a1d..ce184d626 100644 --- a/apps/api/libs/handlers/src/dashboards/get_dashboard_handler.rs +++ b/apps/api/libs/handlers/src/dashboards/get_dashboard_handler.rs @@ -5,6 +5,7 @@ use chrono::{DateTime, Utc}; use diesel::{BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl, Queryable, Selectable}; use diesel_async::RunQueryDsl; use futures::future::join_all; +use itertools::Itertools; use middleware::AuthenticatedUser; use serde_json::Value; use serde_yaml; @@ -390,7 +391,10 @@ pub async fn get_dashboard_handler( name: p.name, avatar_url: p.avatar_url, }) - .collect::>(), + .collect::>() + .into_iter() + .sorted_by(|a, b| a.email.to_lowercase().cmp(&b.email.to_lowercase())) + .collect(), ) } } diff --git a/apps/api/libs/handlers/src/metrics/get_metric_handler.rs b/apps/api/libs/handlers/src/metrics/get_metric_handler.rs index 40b4d75e1..0e27cf3ee 100644 --- a/apps/api/libs/handlers/src/metrics/get_metric_handler.rs +++ b/apps/api/libs/handlers/src/metrics/get_metric_handler.rs @@ -2,6 +2,7 @@ use anyhow::{anyhow, Result}; use diesel::{BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl, Queryable}; use diesel_async::RunQueryDsl; use futures::future::join; +use itertools::Itertools; use middleware::AuthenticatedUser; use serde_yaml; use sharing::asset_access_checks::check_metric_collection_access; @@ -464,7 +465,10 @@ pub async fn get_metric_handler( name: p.name, avatar_url: p.avatar_url, }) - .collect::>(), + .collect::>() + .into_iter() + .sorted_by(|a, b| a.email.to_lowercase().cmp(&b.email.to_lowercase())) + .collect(), ) } } diff --git a/apps/trigger/package.json b/apps/trigger/package.json index bfa465c42..cb364bfeb 100644 --- a/apps/trigger/package.json +++ b/apps/trigger/package.json @@ -38,4 +38,4 @@ "devDependencies": { "@trigger.dev/build": "4.0.0-v4-beta.24" } -} \ No newline at end of file +} diff --git a/apps/web/src/api/buster_rest/collections/queryRequests.ts b/apps/web/src/api/buster_rest/collections/queryRequests.ts index 7d9763943..0e5655549 100644 --- a/apps/web/src/api/buster_rest/collections/queryRequests.ts +++ b/apps/web/src/api/buster_rest/collections/queryRequests.ts @@ -182,7 +182,7 @@ export const useShareCollection = () => { draft.individual_permissions = [ ...params.map((p) => ({ ...p })), ...(draft.individual_permissions || []) - ]; + ].sort((a, b) => a.email.localeCompare(b.email)); }); }); }, @@ -208,7 +208,8 @@ export const useUnshareCollection = () => { if (!previousData) return previousData; return create(previousData, (draft: BusterCollection) => { draft.individual_permissions = - draft.individual_permissions?.filter((t) => !variables.data.includes(t.email)) || []; + (draft.individual_permissions?.filter((t) => !variables.data.includes(t.email)) || []) + .sort((a, b) => a.email.localeCompare(b.email)); }); }); }, @@ -231,11 +232,11 @@ export const useUpdateCollectionShare = () => { if (!previousData) return previousData; return create(previousData, (draft) => { draft.individual_permissions = - draft.individual_permissions?.map((t) => { + (draft.individual_permissions?.map((t) => { const found = params.users?.find((v) => v.email === t.email); if (found) return { ...t, ...found }; return t; - }) || []; + }) || []).sort((a, b) => a.email.localeCompare(b.email)); if (params.publicly_accessible !== undefined) { draft.publicly_accessible = params.publicly_accessible; diff --git a/apps/web/src/api/buster_rest/dashboards/queryRequests.ts b/apps/web/src/api/buster_rest/dashboards/queryRequests.ts index 4d2efdcf0..23640490e 100644 --- a/apps/web/src/api/buster_rest/dashboards/queryRequests.ts +++ b/apps/web/src/api/buster_rest/dashboards/queryRequests.ts @@ -368,7 +368,7 @@ export const useShareDashboard = () => { avatar_url: p.avatar_url || null })), ...(draft.individual_permissions || []) - ]; + ].sort((a, b) => a.email.localeCompare(b.email)); }); }); }, @@ -398,7 +398,8 @@ export const useUnshareDashboard = () => { if (!previousData) return previousData; return create(previousData, (draft) => { draft.individual_permissions = - draft.individual_permissions?.filter((t) => !variables.data.includes(t.email)) || []; + (draft.individual_permissions?.filter((t) => !variables.data.includes(t.email)) || []) + .sort((a, b) => a.email.localeCompare(b.email)); }); }); }, @@ -419,11 +420,11 @@ export const useUpdateDashboardShare = () => { if (!previousData) return previousData; return create(previousData, (draft) => { draft.individual_permissions = - draft.individual_permissions?.map((t) => { + (draft.individual_permissions?.map((t) => { const found = params.users?.find((v) => v.email === t.email); if (found) return { ...t, ...found }; return t; - }) || []; + }) || []).sort((a, b) => a.email.localeCompare(b.email)); if (params.publicly_accessible !== undefined) { draft.publicly_accessible = params.publicly_accessible; diff --git a/apps/web/src/api/buster_rest/metrics/updateMetricQueryRequests.ts b/apps/web/src/api/buster_rest/metrics/updateMetricQueryRequests.ts index b0ea68177..5bd233cfd 100644 --- a/apps/web/src/api/buster_rest/metrics/updateMetricQueryRequests.ts +++ b/apps/web/src/api/buster_rest/metrics/updateMetricQueryRequests.ts @@ -233,7 +233,7 @@ export const useShareMetric = () => { avatar_url: p.avatar_url || null })), ...(draft.individual_permissions || []) - ]; + ].sort((a, b) => a.email.localeCompare(b.email)); }); }); }, @@ -264,7 +264,8 @@ export const useUnshareMetric = () => { if (!previousData) return previousData; return create(previousData, (draft: BusterMetric) => { draft.individual_permissions = - draft.individual_permissions?.filter((t) => !variables.data.includes(t.email)) || []; + (draft.individual_permissions?.filter((t) => !variables.data.includes(t.email)) || []) + .sort((a, b) => a.email.localeCompare(b.email)); }); }); }, @@ -297,11 +298,11 @@ export const useUpdateMetricShare = () => { if (!previousData) return previousData; return create(previousData, (draft: BusterMetric) => { draft.individual_permissions = - draft.individual_permissions?.map((t) => { + (draft.individual_permissions?.map((t) => { const found = variables.params.users?.find((v) => v.email === t.email); if (found) return { ...t, ...found }; return t; - }) || []; + }) || []).sort((a, b) => a.email.localeCompare(b.email)); if (variables.params.publicly_accessible !== undefined) { draft.publicly_accessible = variables.params.publicly_accessible;