share update

This commit is contained in:
Nate Kelley 2025-07-16 23:44:08 -06:00
parent 80d2c88d90
commit 0be0744bd0
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
12 changed files with 59 additions and 54 deletions

View File

@ -8,6 +8,8 @@ import type { ShareRole } from '@buster/server-shared/share';
export type SharePostRequest = {
email: string;
role: ShareRole;
avatar_url?: string | null;
name?: string | undefined;
}[];
export type ShareDeleteRequest = string[];

View File

@ -180,17 +180,20 @@ export const useShareCollection = () => {
if (!previousData) return previousData;
return create(previousData, (draft: BusterCollection) => {
draft.individual_permissions = [
...params.map((p) => ({ ...p, avatar_url: null })),
...params.map((p) => ({ ...p })),
...(draft.individual_permissions || [])
];
});
});
},
onSuccess: (data) => {
queryClient.setQueryData(
collectionQueryKeys.collectionsGetCollection(data.id).queryKey,
data
);
const partialMatchedKey = collectionQueryKeys
.collectionsGetCollection(data)
.queryKey.slice(0, -1);
queryClient.invalidateQueries({
queryKey: partialMatchedKey,
refetchType: 'all'
});
}
});
};

View File

@ -80,9 +80,7 @@ export const collectionsDeleteCollection = async (data: {
// share collections
export const shareCollection = async ({ id, params }: { id: string; params: SharePostRequest }) => {
return mainApi
.post<BusterCollection>(`/collections/${id}/sharing`, params)
.then((res) => res.data);
return mainApi.post<string>(`/collections/${id}/sharing`, params).then((res) => res.data);
};
export const unshareCollection = async ({ id, data }: { id: string; data: ShareDeleteRequest }) => {

View File

@ -44,6 +44,7 @@ import {
unshareDashboard,
updateDashboardShare
} from './requests';
import { userQueryKeys } from '../../query_keys/users';
export const useGetDashboard = <TData = BusterDashboardResponse>(
{
@ -359,19 +360,28 @@ export const useShareDashboard = () => {
variables.id,
latestVersionNumber
).queryKey;
queryClient.setQueryData(queryKey, (previousData) => {
if (!previousData) return previousData;
return create(previousData, (draft) => {
draft.individual_permissions = [
...variables.params.map((p) => ({ ...p, avatar_url: null })),
...variables.params.map((p) => ({
...p,
name: p.name,
avatar_url: p.avatar_url || null
})),
...(draft.individual_permissions || [])
];
});
});
},
onSuccess: (data, variables) => {
const partialMatchedKey = dashboardQueryKeys
.dashboardGetDashboard(variables.id, null)
.queryKey.slice(0, -1);
queryClient.invalidateQueries({
queryKey: dashboardQueryKeys.dashboardGetDashboard(variables.id, null).queryKey
queryKey: partialMatchedKey,
refetchType: 'all'
});
}
});

View File

@ -56,7 +56,9 @@ export const listMetrics_server = async (params: Parameters<typeof listMetrics>[
};
export const updateMetric = async (params: UpdateMetricRequest) => {
return mainApi.put<UpdateMetricResponse>(`/metric_files/${params.id}`, params).then((res) => res.data);
return mainApi
.put<UpdateMetricResponse>(`/metric_files/${params.id}`, params)
.then((res) => res.data);
};
export const deleteMetrics = async (data: DeleteMetricRequest) => {
@ -84,9 +86,7 @@ export const bulkUpdateMetricVerificationStatus = async (
// share metrics
export const shareMetric = async ({ id, params }: { id: string; params: ShareMetricRequest }) => {
return mainApi
.post<ShareMetricResponse>(`/metric_files/${id}/sharing`, params)
.then((res) => res.data);
return mainApi.post<string>(`/metric_files/${id}/sharing`, params).then((res) => res.data);
};
export const unshareMetric = async ({ id, data }: { id: string; data: ShareDeleteRequest }) => {
@ -102,5 +102,7 @@ export const updateMetricShare = async ({
id: string;
params: ShareUpdateRequest;
}) => {
return mainApi.put<ShareUpdateResponse>(`/metric_files/${id}/sharing`, params).then((res) => res.data);
return mainApi
.put<ShareUpdateResponse>(`/metric_files/${id}/sharing`, params)
.then((res) => res.data);
};

View File

@ -22,6 +22,7 @@ import {
updateMetric,
updateMetricShare
} from './requests';
import { userQueryKeys } from '../../query_keys/users';
/**
* This is a mutation that saves a metric to the server.
@ -225,26 +226,29 @@ export const useShareMetric = () => {
variables.id,
selectedVersionNumber
).queryKey;
queryClient.setQueryData(queryKey, (previousData: BusterMetric | undefined) => {
if (!previousData) return previousData;
return create(previousData, (draft: BusterMetric) => {
draft.individual_permissions = [
...variables.params.map((p) => ({ ...p, avatar_url: null })),
...variables.params.map((p) => ({
...p,
name: p.name,
avatar_url: p.avatar_url || null
})),
...(draft.individual_permissions || [])
];
});
});
},
onSuccess: (data) => {
const oldMetric = queryClient.getQueryData(
metricsQueryKeys.metricsGetMetric(data.id, data.version_number).queryKey
);
const upgradedMetric = upgradeMetricToIMetric(data, oldMetric || null);
queryClient.setQueryData(
metricsQueryKeys.metricsGetMetric(data.id, data.version_number).queryKey,
upgradedMetric
);
onSuccess: (data, variables) => {
const partialMatchedKey = metricsQueryKeys
.metricsGetMetric(variables.id, null)
.queryKey.slice(0, -1);
queryClient.invalidateQueries({
queryKey: partialMatchedKey,
refetchType: 'all'
});
}
});
};

View File

@ -59,7 +59,7 @@ const userGetUserDatasetGroups = (userId: string) =>
const userGetUserToOrganization = (params: GetUserToOrganizationRequest) =>
queryOptions<GetUserToOrganizationResponse>({
queryKey: ['users', 'organization', params] as const,
staleTime: 10 * 1000
staleTime: 20 * 1000
});
export const userQueryKeys = {

View File

@ -1,6 +1,4 @@
import React from 'react';
import { Avatar } from '@/components/ui/avatar';
import { Text } from '@/components/ui/typography';
import { useMemoizedFn } from '@/hooks';
import { AccessDropdown } from './AccessDropdown';
import type { ShareAssetType, ShareRole } from '@buster/server-shared/share';
@ -10,13 +8,11 @@ export const IndividualSharePerson: React.FC<{
name?: string;
email: string;
role: ShareRole;
avatar_url: string | null;
avatar_url?: string | null;
onUpdateShareRole: (email: string, role: ShareRole | null) => void;
assetType: ShareAssetType;
disabled: boolean;
}> = React.memo(({ name, onUpdateShareRole, email, avatar_url, role, assetType, disabled }) => {
const isSameEmailName = name === email;
const onChangeShareLevel = useMemoizedFn((v: ShareRole | null) => {
onUpdateShareRole(email, v);
});

View File

@ -1,25 +1,10 @@
import React from 'react';
import type { ShareAssetType, ShareConfig, ShareRole } from '@buster/server-shared/share';
import { isValidEmail } from '@/lib/email';
import {
useShareCollection,
useUnshareCollection,
useUpdateCollectionShare
} from '@/api/buster_rest/collections';
import {
useShareDashboard,
useUnshareDashboard,
useUpdateDashboardShare
} from '@/api/buster_rest/dashboards';
import { useShareMetric, useUnshareMetric, useUpdateMetricShare } from '@/api/buster_rest/metrics';
import { Button } from '@/components/ui/buttons';
import { Input } from '@/components/ui/inputs';
import { InputSearchDropdown } from '@/components/ui/inputs/InputSearchDropdown';
import { useBusterNotifications } from '@/context/BusterNotifications';
import { useUnshareCollection, useUpdateCollectionShare } from '@/api/buster_rest/collections';
import { useUnshareDashboard, useUpdateDashboardShare } from '@/api/buster_rest/dashboards';
import { useUnshareMetric, useUpdateMetricShare } from '@/api/buster_rest/metrics';
import { useMemoizedFn } from '@/hooks';
import { cn } from '@/lib/classMerge';
import { inputHasText } from '@/lib/text';
import { AccessDropdown } from './AccessDropdown';
import { IndividualSharePerson } from './IndividualSharePerson';
import { ShareMenuContentEmbed } from './ShareMenuContentEmbed';
import { ShareMenuContentPublish } from './ShareMenuContentPublish';
@ -75,7 +60,6 @@ const ShareMenuContentShare: React.FC<ShareMenuContentBodyProps> = React.memo(
const { mutateAsync: onUnshareMetric } = useUnshareMetric();
const { mutateAsync: onUnshareDashboard } = useUnshareDashboard();
const { mutateAsync: onUnshareCollection } = useUnshareCollection();
const { openErrorMessage } = useBusterNotifications();
const hasIndividualPermissions = !!individual_permissions?.length;

View File

@ -34,7 +34,7 @@ export const ShareMenuInvite: React.FC<ShareMenuInviteProps> = React.memo(
const [defaultPermissionLevel, setDefaultPermissionLevel] =
React.useState<ShareRole>('canView');
const debouncedInputValue = useDebounce(inputValue, { wait: 350 });
const debouncedInputValue = useDebounce(inputValue, { wait: 150 });
const { data: usersData } = useGetUserToOrganization({
user_name: debouncedInputValue,
email: debouncedInputValue,
@ -82,12 +82,16 @@ export const ShareMenuInvite: React.FC<ShareMenuInviteProps> = React.memo(
return;
}
const user = usersData?.data.find((user) => user.email === inputValue);
const payload: Parameters<typeof onShareMetric>[0] = {
id: assetId,
params: [
{
email: inputValue,
role: defaultPermissionLevel
role: defaultPermissionLevel,
name: user?.name || '',
avatar_url: user?.avatarUrl || null
}
]
};

View File

@ -56,7 +56,9 @@ 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(),
})
);

View File

@ -14,7 +14,7 @@ export const ShareIndividualSchema = z.object({
email: z.string(),
role: ShareRoleSchema,
name: z.string().optional(),
avatar_url: z.string().nullable(),
avatar_url: z.string().nullable().optional(),
});
export const ShareConfigSchema = z.object({