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 { export enum ShareAssetType {
METRIC = 'metric', METRIC = 'metric',
DASHBOARD = 'dashboard', DASHBOARD = 'dashboard',
COLLECTION = 'collection' COLLECTION = 'collection',
CHAT = 'chat'
} }
export interface BusterShare { export interface BusterShare {

View File

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

View File

@ -10,9 +10,9 @@ import {
createUserFavorite, createUserFavorite,
deleteUserFavorite, deleteUserFavorite,
updateUserFavorites, updateUserFavorites,
inviteUser,
getUserList, getUserList,
getUserList_server, getUserList_server
inviteUser
} from './requests'; } from './requests';
import { useMemoizedFn } from '@/hooks'; import { useMemoizedFn } from '@/hooks';
import { QueryClient, useQueryClient } from '@tanstack/react-query'; import { QueryClient, useQueryClient } from '@tanstack/react-query';
@ -103,11 +103,11 @@ export const useAddUserFavorite = () => {
mutationFn: createUserFavorite, mutationFn: createUserFavorite,
onMutate: (params) => { onMutate: (params) => {
queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, (prev) => { queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, (prev) => {
return [...params, ...(prev || [])]; return [params, ...(prev || [])];
}); });
}, },
onSettled: () => { onSuccess: (data) => {
queryClient.invalidateQueries(queryKeys.favoritesGetList); queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, data);
} }
}); });
}; };
@ -116,13 +116,13 @@ export const useDeleteUserFavorite = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: deleteUserFavorite, mutationFn: deleteUserFavorite,
onMutate: (params) => { onMutate: (id) => {
queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, (prev) => { 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: () => { onSuccess: (data) => {
queryClient.invalidateQueries(queryKeys.favoritesGetList); queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, data);
} }
}); });
}; };
@ -140,6 +140,10 @@ export const useUpdateUserFavorites = () => {
return { ...favorite, index }; 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 = () => { export const useInviteUser = () => {
const { openSuccessMessage } = useBusterNotifications(); const { openSuccessMessage } = useBusterNotifications();
const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: inviteUser, mutationFn: inviteUser,
onSuccess: () => { onSuccess: () => {
openSuccessMessage('Invites sent'); 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 BusterUserListItem
} from '@/api/asset_interfaces/users'; } from '@/api/asset_interfaces/users';
import type { import type {
UsersFavoritePostPayload, UserRequestUserListPayload,
UserFavoriteDeletePayload, UsersFavoritePostPayload
UserUpdateFavoritesPayload,
UserRequestUserListPayload
} from '@/api/request_interfaces/user/interfaces'; } from '@/api/request_interfaces/user/interfaces';
import { mainApi } from '../instances'; import { mainApi } from '../instances';
import { serverFetch } from '../../createServerInstance'; import { serverFetch } from '../../createServerInstance';
@ -82,12 +80,14 @@ export const inviteUser = async ({
emails: string[]; emails: string[];
team_ids?: string[]; team_ids?: string[];
}) => { }) => {
return mainApi.post(`/users/invite`, { return mainApi.post<null>(`/users/invite`, {
emails, emails,
team_ids team_ids
}); });
}; };
//USER FAVORITES
export const getUserFavorites = async () => { export const getUserFavorites = async () => {
return mainApi.get<BusterUserFavorite[]>(`/users/favorites`).then((response) => response.data); 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 return mainApi
.delete<BusterUserFavorite[]>(`/users/favorites`, { data: payload }) .delete<BusterUserFavorite[]>(`/users/favorites/${id}`)
.then((response) => response.data); .then((response) => response.data);
}; };
export const deleteUserFavorite_server = async (payload: UserFavoriteDeletePayload) => { export const deleteUserFavorite_server = async (id: string) => {
return serverFetch<BusterUserFavorite[]>(`/users/favorites`, { return serverFetch<BusterUserFavorite[]>(`/users/favorites/${id}`, {
method: 'DELETE', method: 'DELETE'
body: JSON.stringify(payload)
}); });
}; };
export const updateUserFavorites = async (payload: UserUpdateFavoritesPayload) => { export const updateUserFavorites = async (payload: string[]) => {
return mainApi return mainApi
.put<BusterUserFavorite[]>(`/users/favorites`, payload) .put<BusterUserFavorite[]>(`/users/favorites`, payload)
.then((response) => response.data); .then((response) => response.data);
}; };
export const updateUserFavorites_server = async (payload: UserUpdateFavoritesPayload) => { export const updateUserFavorites_server = async (payload: string[]) => {
return serverFetch<BusterUserFavorite[]>(`/users/favorites`, { return serverFetch<BusterUserFavorite[]>(`/users/favorites`, {
method: 'PUT', method: 'PUT',
body: JSON.stringify(payload) body: JSON.stringify(payload)
}); });
}; };
//USER LIST
export const getUserList = async (payload: UserRequestUserListPayload) => { export const getUserList = async (payload: UserRequestUserListPayload) => {
return mainApi return mainApi
.get<BusterUserListItem[]>(`/users/list`, { params: payload }) .get<BusterUserListItem[]>(`/users`, { params: payload })
.then((response) => response.data); .then((response) => response.data);
}; };
export const getUserList_server = async (payload: UserRequestUserListPayload) => { 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; id: string;
asset_type: ShareAssetType; asset_type: ShareAssetType;
index?: number; index?: number;
name: string; name: string; //just used for the UI for optimistic update
}[]; };
export type UserFavoriteDeletePayload = {
id: string;
asset_type: ShareAssetType;
}[];
export interface UserUpdateFavoritesPayload {
favorites: string[]; // Array of favorite ids
}
export interface UserRequestUserListPayload { export interface UserRequestUserListPayload {
team_id: string; team_id: string;

View File

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

View File

@ -25,7 +25,7 @@ const mockFavorites = [
{ {
id: '123', id: '123',
name: 'Favorite Dashboard', name: 'Favorite Dashboard',
asset_type: ShareAssetType.DASHBOARD, ype: ShareAssetType.DASHBOARD,
asset_id: '123', asset_id: '123',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
@ -35,7 +35,7 @@ const mockFavorites = [
id: '456', id: '456',
name: 'Important Metrics', name: 'Important Metrics',
route: createBusterRoute({ route: BusterRoutes.APP_METRIC_ID, metricId: '456' }), route: createBusterRoute({ route: BusterRoutes.APP_METRIC_ID, metricId: '456' }),
asset_type: ShareAssetType.METRIC, ype: ShareAssetType.METRIC,
asset_id: '456', asset_id: '456',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString() updated_at: new Date().toISOString()
@ -44,7 +44,7 @@ const mockFavorites = [
id: '789', id: '789',
name: 'Favorite Metric 3', name: 'Favorite Metric 3',
route: createBusterRoute({ route: BusterRoutes.APP_METRIC_ID, metricId: '789' }), route: createBusterRoute({ route: BusterRoutes.APP_METRIC_ID, metricId: '789' }),
asset_type: ShareAssetType.METRIC, ype: ShareAssetType.METRIC,
asset_id: '789', asset_id: '789',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_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 { InvitePeopleModal } from '../modal/InvitePeopleModal';
import { useMemoizedFn } from '@/hooks'; import { useMemoizedFn } from '@/hooks';
import { SidebarUserFooter } from './SidebarUserFooter/SidebarUserFooter'; import { SidebarUserFooter } from './SidebarUserFooter/SidebarUserFooter';
import { useGetUserFavorites } from '@/api/buster_rest'; import { useGetUserFavorites, useUpdateUserFavorites } from '@/api/buster_rest';
const topItems: ISidebarList = { const topItems: ISidebarList = {
items: [ items: [
@ -114,6 +114,11 @@ export const SidebarPrimary = React.memo(() => {
const currentRoute = useAppLayoutContextSelector((x) => x.currentRoute); const currentRoute = useAppLayoutContextSelector((x) => x.currentRoute);
const onToggleInviteModal = useAppLayoutContextSelector((s) => s.onToggleInviteModal); const onToggleInviteModal = useAppLayoutContextSelector((s) => s.onToggleInviteModal);
const [openSupportModal, setOpenSupportModal] = useState(false); const [openSupportModal, setOpenSupportModal] = useState(false);
const { mutateAsync: updateUserFavorites } = useUpdateUserFavorites();
const onFavoritesReorder = useMemoizedFn((itemIds: string[]) => {
updateUserFavorites(itemIds);
});
const sidebarItems: SidebarProps['content'] = useMemo(() => { const sidebarItems: SidebarProps['content'] = useMemo(() => {
const items = [topItems]; const items = [topItems];
@ -125,13 +130,13 @@ export const SidebarPrimary = React.memo(() => {
items.push(yourStuff); items.push(yourStuff);
if (favorites && favorites.length > 0) { if (favorites && favorites.length > 0) {
items.push(favoritesDropdown(favorites)); items.push(favoritesDropdown(favorites, onFavoritesReorder));
} }
items.push(tryGroup(onToggleInviteModal, () => setOpenSupportModal(true))); items.push(tryGroup(onToggleInviteModal, () => setOpenSupportModal(true)));
return items; return items;
}, [isAdmin, favorites, currentRoute]); }, [isAdmin, favorites, currentRoute, onFavoritesReorder]);
const onCloseSupportModal = useMemoizedFn(() => setOpenSupportModal(false)); const onCloseSupportModal = useMemoizedFn(() => setOpenSupportModal(false));
@ -206,13 +211,14 @@ const GlobalModals = React.memo(
); );
GlobalModals.displayName = 'GlobalModals'; GlobalModals.displayName = 'GlobalModals';
const favoritesDropdown = (favorites: BusterUserFavorite[]): ISidebarGroup => { const favoritesDropdown = (
favorites: BusterUserFavorite[],
onItemsReorder: (itemIds: string[]) => void
): ISidebarGroup => {
return { return {
label: 'Favorites', label: 'Favorites',
isSortable: true, isSortable: true,
onItemsReorder: (itemIds) => { onItemsReorder,
console.warn('onItemsReorder', itemIds);
},
items: favorites.map((favorite) => { items: favorites.map((favorite) => {
const Icon = assetTypeToIcon(favorite.asset_type); const Icon = assetTypeToIcon(favorite.asset_type);
const route = assetTypeToRoute(favorite.asset_type, favorite.id); const route = assetTypeToRoute(favorite.asset_type, favorite.id);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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