favorites updates

This commit is contained in:
Nate Kelley 2025-03-13 14:31:54 -06:00
parent 8579aafd0a
commit 4fa7e92a38
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
14 changed files with 100 additions and 103 deletions

View File

@ -7,7 +7,8 @@ export enum ShareRole {
export enum ShareAssetType {
METRIC = 'metric',
DASHBOARD = 'dashboard',
COLLECTION = 'collection'
COLLECTION = 'collection',
CHAT = 'chat'
}
export interface BusterShare {

View File

@ -38,14 +38,14 @@ export interface BusterUserFavorite {
collection_id?: string;
assets?: {
id: string;
type: ShareAssetType;
asset_type: ShareAssetType;
name: string;
}[];
}
export type BusterUserFavoriteAsset = {
id: string;
asset_type: ShareAssetType;
ype: ShareAssetType;
index?: number;
title: string;
};

View File

@ -10,9 +10,9 @@ import {
createUserFavorite,
deleteUserFavorite,
updateUserFavorites,
inviteUser,
getUserList,
getUserList_server,
inviteUser
getUserList_server
} from './requests';
import { useMemoizedFn } from '@/hooks';
import { QueryClient, useQueryClient } from '@tanstack/react-query';
@ -103,11 +103,11 @@ export const useAddUserFavorite = () => {
mutationFn: createUserFavorite,
onMutate: (params) => {
queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, (prev) => {
return [...params, ...(prev || [])];
return [params, ...(prev || [])];
});
},
onSettled: () => {
queryClient.invalidateQueries(queryKeys.favoritesGetList);
onSuccess: (data) => {
queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, data);
}
});
};
@ -116,13 +116,13 @@ export const useDeleteUserFavorite = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: deleteUserFavorite,
onMutate: (params) => {
onMutate: (id) => {
queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, (prev) => {
return prev?.filter((fav) => !params.some((p) => p.id === fav.id));
return prev?.filter((fav) => fav.id !== id);
});
},
onSettled: () => {
queryClient.invalidateQueries(queryKeys.favoritesGetList);
onSuccess: (data) => {
queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, data);
}
});
};
@ -140,6 +140,10 @@ export const useUpdateUserFavorites = () => {
return { ...favorite, index };
});
});
},
onSuccess: (data) => {
console.log(data);
// queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, data);
}
});
};
@ -167,12 +171,19 @@ export const prefetchGetUserList = async (
export const useInviteUser = () => {
const { openSuccessMessage } = useBusterNotifications();
const queryClient = useQueryClient();
return useMutation({
mutationFn: inviteUser,
onSuccess: () => {
openSuccessMessage('Invites sent');
// queryClient.invalidateQueries(queryKeys.userGetUserList);
const user = queryClient.getQueryData(queryKeys.userGetUserMyself.queryKey);
const teamId = user?.organizations?.[0]?.id;
if (teamId) {
queryClient.invalidateQueries({
queryKey: [queryKeys.userGetUserList({ team_id: teamId }).queryKey]
});
}
}
});
};

View File

@ -6,10 +6,8 @@ import type {
BusterUserListItem
} from '@/api/asset_interfaces/users';
import type {
UsersFavoritePostPayload,
UserFavoriteDeletePayload,
UserUpdateFavoritesPayload,
UserRequestUserListPayload
UserRequestUserListPayload,
UsersFavoritePostPayload
} from '@/api/request_interfaces/user/interfaces';
import { mainApi } from '../instances';
import { serverFetch } from '../../createServerInstance';
@ -82,12 +80,14 @@ export const inviteUser = async ({
emails: string[];
team_ids?: string[];
}) => {
return mainApi.post(`/users/invite`, {
return mainApi.post<null>(`/users/invite`, {
emails,
team_ids
});
};
//USER FAVORITES
export const getUserFavorites = async () => {
return mainApi.get<BusterUserFavorite[]>(`/users/favorites`).then((response) => response.data);
};
@ -109,38 +109,39 @@ export const createUserFavorite_server = async (payload: UsersFavoritePostPayloa
});
};
export const deleteUserFavorite = async (payload: UserFavoriteDeletePayload) => {
export const deleteUserFavorite = async (id: string) => {
return mainApi
.delete<BusterUserFavorite[]>(`/users/favorites`, { data: payload })
.delete<BusterUserFavorite[]>(`/users/favorites/${id}`)
.then((response) => response.data);
};
export const deleteUserFavorite_server = async (payload: UserFavoriteDeletePayload) => {
return serverFetch<BusterUserFavorite[]>(`/users/favorites`, {
method: 'DELETE',
body: JSON.stringify(payload)
export const deleteUserFavorite_server = async (id: string) => {
return serverFetch<BusterUserFavorite[]>(`/users/favorites/${id}`, {
method: 'DELETE'
});
};
export const updateUserFavorites = async (payload: UserUpdateFavoritesPayload) => {
export const updateUserFavorites = async (payload: string[]) => {
return mainApi
.put<BusterUserFavorite[]>(`/users/favorites`, payload)
.then((response) => response.data);
};
export const updateUserFavorites_server = async (payload: UserUpdateFavoritesPayload) => {
export const updateUserFavorites_server = async (payload: string[]) => {
return serverFetch<BusterUserFavorite[]>(`/users/favorites`, {
method: 'PUT',
body: JSON.stringify(payload)
});
};
//USER LIST
export const getUserList = async (payload: UserRequestUserListPayload) => {
return mainApi
.get<BusterUserListItem[]>(`/users/list`, { params: payload })
.get<BusterUserListItem[]>(`/users`, { params: payload })
.then((response) => response.data);
};
export const getUserList_server = async (payload: UserRequestUserListPayload) => {
return serverFetch<BusterUserListItem[]>(`/users/list`, { params: payload });
return serverFetch<BusterUserListItem[]>(`/users`, { params: payload });
};

View File

@ -4,17 +4,8 @@ export type UsersFavoritePostPayload = {
id: string;
asset_type: ShareAssetType;
index?: number;
name: string;
}[];
export type UserFavoriteDeletePayload = {
id: string;
asset_type: ShareAssetType;
}[];
export interface UserUpdateFavoritesPayload {
favorites: string[]; // Array of favorite ids
}
name: string; //just used for the UI for optimistic update
};
export interface UserRequestUserListPayload {
team_id: string;

View File

@ -48,20 +48,13 @@ export const FavoriteStar: React.FC<{
e.stopPropagation();
e.preventDefault();
if (!isFavorited)
return await addItemToFavorite([
{
return await addItemToFavorite({
asset_type: type,
id,
name
}
]);
});
await removeItemFromFavorite([
{
asset_type: type,
id
}
]);
await removeItemFromFavorite(id);
});
const tooltipText = isFavorited ? 'Remove from favorites' : 'Add to favorites';

View File

@ -25,7 +25,7 @@ const mockFavorites = [
{
id: '123',
name: 'Favorite Dashboard',
asset_type: ShareAssetType.DASHBOARD,
ype: ShareAssetType.DASHBOARD,
asset_id: '123',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
@ -35,7 +35,7 @@ const mockFavorites = [
id: '456',
name: 'Important Metrics',
route: createBusterRoute({ route: BusterRoutes.APP_METRIC_ID, metricId: '456' }),
asset_type: ShareAssetType.METRIC,
ype: ShareAssetType.METRIC,
asset_id: '456',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
@ -44,7 +44,7 @@ const mockFavorites = [
id: '789',
name: 'Favorite Metric 3',
route: createBusterRoute({ route: BusterRoutes.APP_METRIC_ID, metricId: '789' }),
asset_type: ShareAssetType.METRIC,
ype: ShareAssetType.METRIC,
asset_id: '789',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()

View File

@ -18,7 +18,7 @@ import { SupportModal } from '../modal/SupportModal';
import { InvitePeopleModal } from '../modal/InvitePeopleModal';
import { useMemoizedFn } from '@/hooks';
import { SidebarUserFooter } from './SidebarUserFooter/SidebarUserFooter';
import { useGetUserFavorites } from '@/api/buster_rest';
import { useGetUserFavorites, useUpdateUserFavorites } from '@/api/buster_rest';
const topItems: ISidebarList = {
items: [
@ -114,6 +114,11 @@ export const SidebarPrimary = React.memo(() => {
const currentRoute = useAppLayoutContextSelector((x) => x.currentRoute);
const onToggleInviteModal = useAppLayoutContextSelector((s) => s.onToggleInviteModal);
const [openSupportModal, setOpenSupportModal] = useState(false);
const { mutateAsync: updateUserFavorites } = useUpdateUserFavorites();
const onFavoritesReorder = useMemoizedFn((itemIds: string[]) => {
updateUserFavorites(itemIds);
});
const sidebarItems: SidebarProps['content'] = useMemo(() => {
const items = [topItems];
@ -125,13 +130,13 @@ export const SidebarPrimary = React.memo(() => {
items.push(yourStuff);
if (favorites && favorites.length > 0) {
items.push(favoritesDropdown(favorites));
items.push(favoritesDropdown(favorites, onFavoritesReorder));
}
items.push(tryGroup(onToggleInviteModal, () => setOpenSupportModal(true)));
return items;
}, [isAdmin, favorites, currentRoute]);
}, [isAdmin, favorites, currentRoute, onFavoritesReorder]);
const onCloseSupportModal = useMemoizedFn(() => setOpenSupportModal(false));
@ -206,13 +211,14 @@ const GlobalModals = React.memo(
);
GlobalModals.displayName = 'GlobalModals';
const favoritesDropdown = (favorites: BusterUserFavorite[]): ISidebarGroup => {
const favoritesDropdown = (
favorites: BusterUserFavorite[],
onItemsReorder: (itemIds: string[]) => void
): ISidebarGroup => {
return {
label: 'Favorites',
isSortable: true,
onItemsReorder: (itemIds) => {
console.warn('onItemsReorder', itemIds);
},
onItemsReorder,
items: favorites.map((favorite) => {
const Icon = assetTypeToIcon(favorite.asset_type);
const route = assetTypeToRoute(favorite.asset_type, favorite.id);

View File

@ -1,7 +1,6 @@
'use client';
import React from 'react';
import { ShareAssetType } from '@/api/asset_interfaces';
import { BusterLogo } from '@/assets/svg/BusterLogo';
import { Title } from '@/components/ui/typography';
import { useBusterNotifications } from '@/context/BusterNotifications';
@ -10,10 +9,9 @@ import { Button } from '@/components/ui/buttons';
import Link from 'next/link';
export const AppNoPageAccess: React.FC<{
asset_type: ShareAssetType;
metricId?: string;
dashboardId?: string;
}> = React.memo(({ asset_type }) => {
}> = React.memo(({}) => {
const { openInfoMessage } = useBusterNotifications();
return (

View File

@ -167,7 +167,7 @@ const TitleCell = React.memo<{ title: string; status: VerificationStatus; chatId
<div className="flex items-center" onClick={onFavoriteDivClick}>
<FavoriteStar
id={chatId}
type={ShareAssetType.METRIC}
type={ShareAssetType.CHAT}
iconStyle="tertiary"
title={title}
className="hidden! group-hover:flex!"

View File

@ -153,12 +153,15 @@ const ThreeDotButton: React.FC<{
value: 'add-to-favorites',
loading: addingToFavorites,
onClick: async () => {
await addUserFavorite(
selectedRowKeys.map((id) => ({
await Promise.all(
selectedRowKeys.map((id) => {
const name = userFavorites?.find((f) => f.id === id)?.name || '';
return addUserFavorite({
id,
asset_type: ShareAssetType.METRIC,
name: 'Metric'
}))
name
});
})
);
}
},
@ -168,10 +171,7 @@ const ThreeDotButton: React.FC<{
loading: removingFromFavorites,
value: 'remove-from-favorites',
onClick: async () => {
const allFavorites: Parameters<typeof removeUserFavorite>[0] = userFavorites
.filter((f) => !selectedRowKeys.includes(f.id))
.map((f) => ({ id: f.id, asset_type: ShareAssetType.METRIC }));
await removeUserFavorite(allFavorites);
await Promise.all(selectedRowKeys.map((id) => removeUserFavorite(id)));
}
}
];

View File

@ -141,12 +141,16 @@ const ThreeDotButton: React.FC<{
icon: <Star />,
value: 'add-to-favorites',
onClick: async () => {
const allFavorites: Parameters<typeof addUserFavorite>[0] = selectedRowKeys.map((id) => ({
await Promise.all(
selectedRowKeys.map((id) => {
const name = userFavorites?.find((f) => f.id === id)?.name || '';
return addUserFavorite({
id,
asset_type: ShareAssetType.DASHBOARD,
name: 'Dashboard'
}));
await addUserFavorite(allFavorites);
name
});
})
);
}
},
{
@ -154,11 +158,7 @@ const ThreeDotButton: React.FC<{
icon: <Xmark />,
value: 'remove-from-favorites',
onClick: async () => {
const allFavorites: Parameters<typeof removeUserFavorite>[0] = userFavorites
.map((f) => f.id)
.filter((id) => !selectedRowKeys.includes(id))
.map((id) => ({ id, asset_type: ShareAssetType.DASHBOARD }));
await removeUserFavorite(allFavorites);
await Promise.all(selectedRowKeys.map((id) => removeUserFavorite(id)));
}
}
];

View File

@ -194,12 +194,16 @@ const ThreeDotButton: React.FC<{
icon: <Star />,
value: 'add-to-favorites',
onClick: async () => {
const allFavorites: Parameters<typeof addUserFavorite>[0] = selectedRowKeys.map((id) => ({
await Promise.all(
selectedRowKeys.map((id) => {
const name = userFavorites?.find((f) => f.id === id)?.name || '';
return addUserFavorite({
id,
asset_type: ShareAssetType.METRIC,
name: 'Metric'
}));
await addUserFavorite(allFavorites);
name
});
})
);
}
},
{
@ -207,11 +211,7 @@ const ThreeDotButton: React.FC<{
icon: <Xmark />,
value: 'remove-from-favorites',
onClick: async () => {
const allFavorites: Parameters<typeof removeUserFavorite>[0] = userFavorites
.map((f) => f.id)
.filter((id) => !selectedRowKeys.includes(id))
.map((id) => ({ id, asset_type: ShareAssetType.METRIC }));
await removeUserFavorite(allFavorites);
await Promise.all(selectedRowKeys.map((id) => removeUserFavorite(id)));
}
}
];

View File

@ -72,11 +72,7 @@ export const AppAssetCheckLayout: React.FC<
if (!has_access && !pagePublic) {
return (
<ClientSideAnonCheck jwtToken={jwtToken}>
<AppNoPageAccess
asset_type={type as ShareAssetType}
metricId={props.metricId}
dashboardId={props.dashboardId}
/>
<AppNoPageAccess metricId={props.metricId} dashboardId={props.dashboardId} />
</ClientSideAnonCheck>
);
}