Merge pull request #533 from buster-so/hot-fix/avatar-url-in-share

Hot fix - avatar url in share menu and assets
This commit is contained in:
Nate Kelley 2025-07-17 10:50:57 -06:00 committed by GitHub
commit c9467b713b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 45 additions and 52 deletions

View File

@ -53,6 +53,7 @@ struct AssetPermissionInfo {
role: AssetPermissionRole, role: AssetPermissionRole,
email: String, email: String,
name: Option<String>, name: Option<String>,
avatar_url: Option<String>,
} }
pub async fn get_chat_handler( pub async fn get_chat_handler(
@ -154,7 +155,7 @@ pub async fn get_chat_handler(
.filter(asset_permissions::asset_type.eq(AssetType::Chat)) .filter(asset_permissions::asset_type.eq(AssetType::Chat))
.filter(asset_permissions::identity_type.eq(IdentityType::User)) .filter(asset_permissions::identity_type.eq(IdentityType::User))
.filter(asset_permissions::deleted_at.is_null()) .filter(asset_permissions::deleted_at.is_null())
.select((asset_permissions::role, users::email, users::name)) .select((asset_permissions::role, users::email, users::name, users::avatar_url))
.load::<AssetPermissionInfo>(&mut conn) .load::<AssetPermissionInfo>(&mut conn)
.await; .await;
@ -302,6 +303,7 @@ pub async fn get_chat_handler(
email: p.email, email: p.email,
role: p.role, role: p.role,
name: p.name, name: p.name,
avatar_url: p.avatar_url,
}) })
.collect::<Vec<BusterShareIndividual>>(), .collect::<Vec<BusterShareIndividual>>(),
) )

View File

@ -12,6 +12,7 @@ pub struct BusterShareIndividual {
pub email: String, pub email: String,
pub role: AssetPermissionRole, pub role: AssetPermissionRole,
pub name: Option<String>, pub name: Option<String>,
pub avatar_url: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]

View File

@ -25,6 +25,7 @@ struct AssetPermissionInfo {
role: AssetPermissionRole, role: AssetPermissionRole,
email: String, email: String,
name: Option<String>, name: Option<String>,
avatar_url: Option<String>,
} }
/// Type for querying asset data from database /// Type for querying asset data from database
@ -128,6 +129,7 @@ pub async fn get_collection_handler(
asset_permissions::role, asset_permissions::role,
users::email, users::email,
users::name, users::name,
users::avatar_url,
)) ))
.load::<AssetPermissionInfo>(&mut conn) .load::<AssetPermissionInfo>(&mut conn)
.await; .await;
@ -150,6 +152,7 @@ pub async fn get_collection_handler(
email: p.email, email: p.email,
role: p.role, role: p.role,
name: p.name, name: p.name,
avatar_url: p.avatar_url,
}) })
.collect::<Vec<BusterShareIndividual>>(), .collect::<Vec<BusterShareIndividual>>(),
) )

View File

@ -12,6 +12,7 @@ pub struct BusterShareIndividual {
pub email: String, pub email: String,
pub role: AssetPermissionRole, pub role: AssetPermissionRole,
pub name: Option<String>, pub name: Option<String>,
pub avatar_url: Option<String>,
} }
// List collections types // List collections types

View File

@ -52,6 +52,7 @@ struct AssetPermissionInfo {
role: AssetPermissionRole, role: AssetPermissionRole,
email: String, email: String,
name: Option<String>, name: Option<String>,
avatar_url: Option<String>,
} }
/// Fetches collections that the dashboard belongs to, filtered by user permissions /// Fetches collections that the dashboard belongs to, filtered by user permissions
@ -328,7 +329,7 @@ pub async fn get_dashboard_handler(
.filter(asset_permissions::asset_type.eq(AssetType::DashboardFile)) .filter(asset_permissions::asset_type.eq(AssetType::DashboardFile))
.filter(asset_permissions::identity_type.eq(IdentityType::User)) .filter(asset_permissions::identity_type.eq(IdentityType::User))
.filter(asset_permissions::deleted_at.is_null()) .filter(asset_permissions::deleted_at.is_null())
.select((asset_permissions::role, users::email, users::name)) .select((asset_permissions::role, users::email, users::name, users::avatar_url))
.load::<AssetPermissionInfo>(&mut conn) .load::<AssetPermissionInfo>(&mut conn)
.await; .await;
@ -371,6 +372,7 @@ pub async fn get_dashboard_handler(
email: p.email, email: p.email,
role: p.role, role: p.role,
name: p.name, name: p.name,
avatar_url: p.avatar_url,
}) })
.collect::<Vec<BusterShareIndividual>>(), .collect::<Vec<BusterShareIndividual>>(),
) )

View File

@ -31,6 +31,7 @@ pub struct BusterShareIndividual {
pub email: String, pub email: String,
pub role: AssetPermissionRole, pub role: AssetPermissionRole,
pub name: Option<String>, pub name: Option<String>,
pub avatar_url: Option<String>,
} }
// Note: This extends BusterShare which needs to be defined // Note: This extends BusterShare which needs to be defined

View File

@ -32,6 +32,7 @@ struct AssetPermissionInfo {
role: AssetPermissionRole, role: AssetPermissionRole,
email: String, email: String,
name: Option<String>, name: Option<String>,
avatar_url: Option<String>,
} }
/// Fetch the dashboards associated with the given metric id, filtered by user permissions /// Fetch the dashboards associated with the given metric id, filtered by user permissions
@ -348,7 +349,7 @@ pub async fn get_metric_for_dashboard_handler(
.filter(asset_permissions::asset_type.eq(AssetType::MetricFile)) .filter(asset_permissions::asset_type.eq(AssetType::MetricFile))
.filter(asset_permissions::identity_type.eq(IdentityType::User)) .filter(asset_permissions::identity_type.eq(IdentityType::User))
.filter(asset_permissions::deleted_at.is_null()) .filter(asset_permissions::deleted_at.is_null())
.select((asset_permissions::role, users::email, users::name)) .select((asset_permissions::role, users::email, users::name, users::avatar_url))
.load::<AssetPermissionInfo>(&mut conn) .load::<AssetPermissionInfo>(&mut conn)
.await; .await;
@ -412,6 +413,7 @@ pub async fn get_metric_for_dashboard_handler(
email: p.email, email: p.email,
role: p.role, role: p.role,
name: p.name, name: p.name,
avatar_url: p.avatar_url,
}) })
.collect::<Vec<BusterShareIndividual>>(), .collect::<Vec<BusterShareIndividual>>(),
) )

View File

@ -30,6 +30,7 @@ struct AssetPermissionInfo {
role: AssetPermissionRole, role: AssetPermissionRole,
email: String, email: String,
name: Option<String>, name: Option<String>,
avatar_url: Option<String>,
} }
/// Fetch the dashboards associated with the given metric id, filtered by user permissions /// Fetch the dashboards associated with the given metric id, filtered by user permissions
@ -374,7 +375,7 @@ pub async fn get_metric_handler(
.filter(asset_permissions::asset_type.eq(AssetType::MetricFile)) .filter(asset_permissions::asset_type.eq(AssetType::MetricFile))
.filter(asset_permissions::identity_type.eq(IdentityType::User)) .filter(asset_permissions::identity_type.eq(IdentityType::User))
.filter(asset_permissions::deleted_at.is_null()) .filter(asset_permissions::deleted_at.is_null())
.select((asset_permissions::role, users::email, users::name)) .select((asset_permissions::role, users::email, users::name, users::avatar_url))
.load::<AssetPermissionInfo>(&mut conn) .load::<AssetPermissionInfo>(&mut conn)
.await; .await;
@ -438,6 +439,7 @@ pub async fn get_metric_handler(
email: p.email, email: p.email,
role: p.role, role: p.role,
name: p.name, name: p.name,
avatar_url: p.avatar_url,
}) })
.collect::<Vec<crate::metrics::types::BusterShareIndividual>>(), .collect::<Vec<crate::metrics::types::BusterShareIndividual>>(),
) )

View File

@ -23,6 +23,7 @@ pub struct BusterShareIndividual {
pub email: String, pub email: String,
pub role: AssetPermissionRole, pub role: AssetPermissionRole,
pub name: Option<String>, pub name: Option<String>,
pub avatar_url: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]

View File

@ -1,17 +1,5 @@
import type { ShareRole } from '@buster/server-shared/share'; import type { ShareRole } from '@buster/server-shared/share';
/**
* Type defining the sharing permissions and settings for a dashboard
*
* @interface ShareRequest
*/
export type SharePostRequest = {
email: string;
role: ShareRole;
avatar_url?: string | null;
name?: string | undefined;
}[];
export type ShareDeleteRequest = string[]; export type ShareDeleteRequest = string[];
export type ShareUpdateRequest = { export type ShareUpdateRequest = {

View File

@ -2,10 +2,10 @@ import type { ShareAssetType } from '@buster/server-shared/share';
import type { BusterCollection, BusterCollectionListItem } from '@/api/asset_interfaces/collection'; import type { BusterCollection, BusterCollectionListItem } from '@/api/asset_interfaces/collection';
import type { import type {
ShareDeleteRequest, ShareDeleteRequest,
SharePostRequest,
ShareUpdateRequest ShareUpdateRequest
} from '@/api/asset_interfaces/shared_interfaces'; } from '@/api/asset_interfaces/shared_interfaces';
import mainApi from '@/api/buster_rest/instances'; import mainApi from '@/api/buster_rest/instances';
import { SharePostRequest } from '@buster/server-shared/share';
export const collectionsGetList = async (params: { export const collectionsGetList = async (params: {
/** Current page number (1-based indexing) */ /** Current page number (1-based indexing) */

View File

@ -355,17 +355,14 @@ export const useShareDashboard = () => {
const { latestVersionNumber } = useGetDashboardVersionNumber(); const { latestVersionNumber } = useGetDashboardVersionNumber();
return useMutation({ return useMutation({
mutationFn: shareDashboard, mutationFn: shareDashboard,
onMutate: (variables) => { onMutate: ({ id, params }) => {
const queryKey = dashboardQueryKeys.dashboardGetDashboard( const queryKey = dashboardQueryKeys.dashboardGetDashboard(id, latestVersionNumber).queryKey;
variables.id,
latestVersionNumber
).queryKey;
queryClient.setQueryData(queryKey, (previousData) => { queryClient.setQueryData(queryKey, (previousData) => {
if (!previousData) return previousData; if (!previousData) return previousData;
return create(previousData, (draft) => { return create(previousData, (draft) => {
draft.individual_permissions = [ draft.individual_permissions = [
...variables.params.map((p) => ({ ...params.map((p) => ({
...p, ...p,
name: p.name, name: p.name,
avatar_url: p.avatar_url || null avatar_url: p.avatar_url || null

View File

@ -5,11 +5,11 @@ import type {
} from '@/api/asset_interfaces/dashboard'; } from '@/api/asset_interfaces/dashboard';
import type { import type {
ShareDeleteRequest, ShareDeleteRequest,
SharePostRequest,
ShareUpdateRequest ShareUpdateRequest
} from '@/api/asset_interfaces/shared_interfaces'; } from '@/api/asset_interfaces/shared_interfaces';
import mainApi from '@/api/buster_rest/instances'; import mainApi from '@/api/buster_rest/instances';
import { serverFetch } from '@/api/createServerInstance'; import { serverFetch } from '@/api/createServerInstance';
import { SharePostRequest } from '@buster/server-shared/share';
export const dashboardsGetList = async (params: { export const dashboardsGetList = async (params: {
/** The page number to fetch */ /** The page number to fetch */

View File

@ -9,8 +9,6 @@ import type {
DuplicateMetricResponse, DuplicateMetricResponse,
GetMetricDataRequest, GetMetricDataRequest,
ListMetricsResponse, ListMetricsResponse,
ShareMetricRequest,
ShareMetricResponse,
UpdateMetricRequest, UpdateMetricRequest,
GetMetricRequest, GetMetricRequest,
GetMetricListRequest, GetMetricListRequest,
@ -22,6 +20,7 @@ import type {
} from '@buster/server-shared/metrics'; } from '@buster/server-shared/metrics';
import { serverFetch } from '@/api/createServerInstance'; import { serverFetch } from '@/api/createServerInstance';
import { mainApi } from '../instances'; import { mainApi } from '../instances';
import { SharePostRequest } from '@buster/server-shared/share';
export const getMetric = async (params: GetMetricRequest): Promise<GetMetricResponse> => { export const getMetric = async (params: GetMetricRequest): Promise<GetMetricResponse> => {
return mainApi return mainApi
@ -85,7 +84,7 @@ export const bulkUpdateMetricVerificationStatus = async (
// share metrics // share metrics
export const shareMetric = async ({ id, params }: { id: string; params: ShareMetricRequest }) => { export const shareMetric = async ({ id, params }: { id: string; params: SharePostRequest }) => {
return mainApi.post<string>(`/metric_files/${id}/sharing`, params).then((res) => res.data); return mainApi.post<string>(`/metric_files/${id}/sharing`, params).then((res) => res.data);
}; };

View File

@ -221,17 +221,14 @@ export const useShareMetric = () => {
const { selectedVersionNumber } = useGetMetricVersionNumber(); const { selectedVersionNumber } = useGetMetricVersionNumber();
return useMutation({ return useMutation({
mutationFn: shareMetric, mutationFn: shareMetric,
onMutate: (variables) => { onMutate: ({ id, params }) => {
const queryKey = metricsQueryKeys.metricsGetMetric( const queryKey = metricsQueryKeys.metricsGetMetric(id, selectedVersionNumber).queryKey;
variables.id,
selectedVersionNumber
).queryKey;
queryClient.setQueryData(queryKey, (previousData: BusterMetric | undefined) => { queryClient.setQueryData(queryKey, (previousData: BusterMetric | undefined) => {
if (!previousData) return previousData; if (!previousData) return previousData;
return create(previousData, (draft: BusterMetric) => { return create(previousData, (draft: BusterMetric) => {
draft.individual_permissions = [ draft.individual_permissions = [
...variables.params.map((p) => ({ ...params.map((p) => ({
...p, ...p,
name: p.name, name: p.name,
avatar_url: p.avatar_url || null avatar_url: p.avatar_url || null

View File

@ -1,4 +1,5 @@
import { z } from 'zod'; import { z } from 'zod';
import { ShareIndividualSchema } from '../share';
import { ChatMessageSchema } from './chat-message.types'; import { ChatMessageSchema } from './chat-message.types';
const AssetType = z.enum(['metric_file', 'dashboard_file']); const AssetType = z.enum(['metric_file', 'dashboard_file']);
@ -6,13 +7,6 @@ const AssetType = z.enum(['metric_file', 'dashboard_file']);
// Asset Permission Role enum (matching database enum) // Asset Permission Role enum (matching database enum)
export const AssetPermissionRoleSchema = z.enum(['viewer', 'editor', 'owner']); export const AssetPermissionRoleSchema = z.enum(['viewer', 'editor', 'owner']);
// Individual permission schema
export const BusterShareIndividualSchema = z.object({
email: z.string().email(),
role: AssetPermissionRoleSchema,
name: z.string().optional(),
});
// Main ChatWithMessages schema // Main ChatWithMessages schema
export const ChatWithMessagesSchema = z.object({ export const ChatWithMessagesSchema = z.object({
id: z.string(), id: z.string(),
@ -27,7 +21,7 @@ export const ChatWithMessagesSchema = z.object({
created_by_name: z.string(), created_by_name: z.string(),
created_by_avatar: z.string().nullable(), created_by_avatar: z.string().nullable(),
// Sharing fields // Sharing fields
individual_permissions: z.array(BusterShareIndividualSchema).optional(), individual_permissions: z.array(ShareIndividualSchema).optional(),
publicly_accessible: z.boolean(), publicly_accessible: z.boolean(),
public_expiry_date: z.string().datetime().optional(), public_expiry_date: z.string().datetime().optional(),
public_enabled_by: z.string().optional(), public_enabled_by: z.string().optional(),
@ -67,7 +61,6 @@ export const CancelChatParamsSchema = z.object({
// Infer types from schemas // Infer types from schemas
export type AssetPermissionRole = z.infer<typeof AssetPermissionRoleSchema>; export type AssetPermissionRole = z.infer<typeof AssetPermissionRoleSchema>;
export type BusterShareIndividual = z.infer<typeof BusterShareIndividualSchema>;
export type ChatWithMessages = z.infer<typeof ChatWithMessagesSchema>; export type ChatWithMessages = z.infer<typeof ChatWithMessagesSchema>;
export type ChatCreateRequest = z.infer<typeof ChatCreateRequestSchema>; export type ChatCreateRequest = z.infer<typeof ChatCreateRequestSchema>;
export type ChatCreateHandlerRequest = z.infer<typeof ChatCreateHandlerRequestSchema>; export type ChatCreateHandlerRequest = z.infer<typeof ChatCreateHandlerRequestSchema>;

View File

@ -53,15 +53,6 @@ export const BulkUpdateMetricVerificationStatusRequestSchema = z.array(
}) })
); );
export const ShareMetricRequestSchema = z.array(
z.object({
email: z.string(),
name: z.string().optional(),
role: ShareRoleSchema,
avatar_url: z.string().nullable().optional(),
})
);
export const ShareDeleteRequestSchema = z.array(z.string()); export const ShareDeleteRequestSchema = z.array(z.string());
export const ShareUpdateRequestSchema = z.object({ export const ShareUpdateRequestSchema = z.object({
@ -87,6 +78,5 @@ export type DuplicateMetricRequest = z.infer<typeof DuplicateMetricRequestSchema
export type BulkUpdateMetricVerificationStatusRequest = z.infer< export type BulkUpdateMetricVerificationStatusRequest = z.infer<
typeof BulkUpdateMetricVerificationStatusRequestSchema typeof BulkUpdateMetricVerificationStatusRequestSchema
>; >;
export type ShareMetricRequest = z.infer<typeof ShareMetricRequestSchema>;
export type ShareDeleteRequest = z.infer<typeof ShareDeleteRequestSchema>; export type ShareDeleteRequest = z.infer<typeof ShareDeleteRequestSchema>;
export type ShareUpdateRequest = z.infer<typeof ShareUpdateRequestSchema>; export type ShareUpdateRequest = z.infer<typeof ShareUpdateRequestSchema>;

View File

@ -1,2 +1,3 @@
export * from './share-interfaces.types'; export * from './share-interfaces.types';
export * from './verification.types'; export * from './verification.types';
export * from './requests';

View File

@ -0,0 +1,13 @@
import { z } from 'zod';
import { ShareRoleSchema } from './share-interfaces.types';
export const SharePostRequestSchema = z.array(
z.object({
email: z.string().email(),
role: ShareRoleSchema,
avatar_url: z.string().nullable().optional(),
name: z.string().optional(),
})
);
export type SharePostRequest = z.infer<typeof SharePostRequestSchema>;