From 4fa7e92a38f51d50df417efdcb83c296868a53ae Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 13 Mar 2025 14:31:54 -0600 Subject: [PATCH] favorites updates --- .../asset_interfaces/share/shareInterfaces.ts | 3 +- .../api/asset_interfaces/users/interfaces.ts | 4 +-- .../api/buster_rest/users/queryRequests.ts | 31 +++++++++++++------ web/src/api/buster_rest/users/requests.ts | 31 ++++++++++--------- .../api/request_interfaces/user/interfaces.ts | 13 ++------ .../components/features/list/FavoriteStar.tsx | 19 ++++-------- .../sidebars/SidebarPrimary.stories.tsx | 6 ++-- .../features/sidebars/SidebarPrimary.tsx | 20 +++++++----- web/src/controllers/AppNoPageAccess.tsx | 4 +-- .../ChatItemsContainer.tsx | 2 +- .../ChatItemsSelectedPopup.tsx | 20 ++++++------ .../DashboardSelectedPopup.tsx | 22 ++++++------- .../MetricItemsSelectedPopup.tsx | 22 ++++++------- web/src/layouts/AppAssetCheckLayout.tsx | 6 +--- 14 files changed, 100 insertions(+), 103 deletions(-) diff --git a/web/src/api/asset_interfaces/share/shareInterfaces.ts b/web/src/api/asset_interfaces/share/shareInterfaces.ts index f32f2bdf4..ad4ead2bb 100644 --- a/web/src/api/asset_interfaces/share/shareInterfaces.ts +++ b/web/src/api/asset_interfaces/share/shareInterfaces.ts @@ -7,7 +7,8 @@ export enum ShareRole { export enum ShareAssetType { METRIC = 'metric', DASHBOARD = 'dashboard', - COLLECTION = 'collection' + COLLECTION = 'collection', + CHAT = 'chat' } export interface BusterShare { diff --git a/web/src/api/asset_interfaces/users/interfaces.ts b/web/src/api/asset_interfaces/users/interfaces.ts index d9dad72c4..9b133cd5a 100644 --- a/web/src/api/asset_interfaces/users/interfaces.ts +++ b/web/src/api/asset_interfaces/users/interfaces.ts @@ -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; }; diff --git a/web/src/api/buster_rest/users/queryRequests.ts b/web/src/api/buster_rest/users/queryRequests.ts index 5f63b68f4..239e8b5a5 100644 --- a/web/src/api/buster_rest/users/queryRequests.ts +++ b/web/src/api/buster_rest/users/queryRequests.ts @@ -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] + }); + } } }); }; diff --git a/web/src/api/buster_rest/users/requests.ts b/web/src/api/buster_rest/users/requests.ts index 6083f1ffa..f6f186185 100644 --- a/web/src/api/buster_rest/users/requests.ts +++ b/web/src/api/buster_rest/users/requests.ts @@ -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(`/users/invite`, { emails, team_ids }); }; +//USER FAVORITES + export const getUserFavorites = async () => { return mainApi.get(`/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(`/users/favorites`, { data: payload }) + .delete(`/users/favorites/${id}`) .then((response) => response.data); }; -export const deleteUserFavorite_server = async (payload: UserFavoriteDeletePayload) => { - return serverFetch(`/users/favorites`, { - method: 'DELETE', - body: JSON.stringify(payload) +export const deleteUserFavorite_server = async (id: string) => { + return serverFetch(`/users/favorites/${id}`, { + method: 'DELETE' }); }; -export const updateUserFavorites = async (payload: UserUpdateFavoritesPayload) => { +export const updateUserFavorites = async (payload: string[]) => { return mainApi .put(`/users/favorites`, payload) .then((response) => response.data); }; -export const updateUserFavorites_server = async (payload: UserUpdateFavoritesPayload) => { +export const updateUserFavorites_server = async (payload: string[]) => { return serverFetch(`/users/favorites`, { method: 'PUT', body: JSON.stringify(payload) }); }; +//USER LIST + export const getUserList = async (payload: UserRequestUserListPayload) => { return mainApi - .get(`/users/list`, { params: payload }) + .get(`/users`, { params: payload }) .then((response) => response.data); }; export const getUserList_server = async (payload: UserRequestUserListPayload) => { - return serverFetch(`/users/list`, { params: payload }); + return serverFetch(`/users`, { params: payload }); }; diff --git a/web/src/api/request_interfaces/user/interfaces.ts b/web/src/api/request_interfaces/user/interfaces.ts index 8f5652d19..dcec79e30 100644 --- a/web/src/api/request_interfaces/user/interfaces.ts +++ b/web/src/api/request_interfaces/user/interfaces.ts @@ -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; diff --git a/web/src/components/features/list/FavoriteStar.tsx b/web/src/components/features/list/FavoriteStar.tsx index 95ddd4d02..3bf29458b 100644 --- a/web/src/components/features/list/FavoriteStar.tsx +++ b/web/src/components/features/list/FavoriteStar.tsx @@ -48,20 +48,13 @@ export const FavoriteStar: React.FC<{ e.stopPropagation(); e.preventDefault(); if (!isFavorited) - return await addItemToFavorite([ - { - asset_type: type, - id, - name - } - ]); - - await removeItemFromFavorite([ - { + return await addItemToFavorite({ asset_type: type, - id - } - ]); + id, + name + }); + + await removeItemFromFavorite(id); }); const tooltipText = isFavorited ? 'Remove from favorites' : 'Add to favorites'; diff --git a/web/src/components/features/sidebars/SidebarPrimary.stories.tsx b/web/src/components/features/sidebars/SidebarPrimary.stories.tsx index 7aa405ffe..ea5b9f2a3 100644 --- a/web/src/components/features/sidebars/SidebarPrimary.stories.tsx +++ b/web/src/components/features/sidebars/SidebarPrimary.stories.tsx @@ -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() diff --git a/web/src/components/features/sidebars/SidebarPrimary.tsx b/web/src/components/features/sidebars/SidebarPrimary.tsx index 4c66a8c8b..52fe41086 100644 --- a/web/src/components/features/sidebars/SidebarPrimary.tsx +++ b/web/src/components/features/sidebars/SidebarPrimary.tsx @@ -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); diff --git a/web/src/controllers/AppNoPageAccess.tsx b/web/src/controllers/AppNoPageAccess.tsx index b2d5e2ebf..1604218d3 100644 --- a/web/src/controllers/AppNoPageAccess.tsx +++ b/web/src/controllers/AppNoPageAccess.tsx @@ -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 ( diff --git a/web/src/controllers/ChatsListController/ChatItemsContainer.tsx b/web/src/controllers/ChatsListController/ChatItemsContainer.tsx index fcd2789c0..f536309df 100644 --- a/web/src/controllers/ChatsListController/ChatItemsContainer.tsx +++ b/web/src/controllers/ChatsListController/ChatItemsContainer.tsx @@ -167,7 +167,7 @@ const TitleCell = React.memo<{ title: string; status: VerificationStatus; chatId
{ - await addUserFavorite( - selectedRowKeys.map((id) => ({ - id, - asset_type: ShareAssetType.METRIC, - name: 'Metric' - })) + await Promise.all( + selectedRowKeys.map((id) => { + const name = userFavorites?.find((f) => f.id === id)?.name || ''; + return addUserFavorite({ + id, + asset_type: ShareAssetType.METRIC, + name + }); + }) ); } }, @@ -168,10 +171,7 @@ const ThreeDotButton: React.FC<{ loading: removingFromFavorites, value: 'remove-from-favorites', onClick: async () => { - const allFavorites: Parameters[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))); } } ]; diff --git a/web/src/controllers/DashboardListController/DashboardSelectedPopup.tsx b/web/src/controllers/DashboardListController/DashboardSelectedPopup.tsx index 0a1950c2c..d12dde06f 100644 --- a/web/src/controllers/DashboardListController/DashboardSelectedPopup.tsx +++ b/web/src/controllers/DashboardListController/DashboardSelectedPopup.tsx @@ -141,12 +141,16 @@ const ThreeDotButton: React.FC<{ icon: , value: 'add-to-favorites', onClick: async () => { - const allFavorites: Parameters[0] = selectedRowKeys.map((id) => ({ - id, - asset_type: ShareAssetType.DASHBOARD, - name: 'Dashboard' - })); - await addUserFavorite(allFavorites); + await Promise.all( + selectedRowKeys.map((id) => { + const name = userFavorites?.find((f) => f.id === id)?.name || ''; + return addUserFavorite({ + id, + asset_type: ShareAssetType.DASHBOARD, + name + }); + }) + ); } }, { @@ -154,11 +158,7 @@ const ThreeDotButton: React.FC<{ icon: , value: 'remove-from-favorites', onClick: async () => { - const allFavorites: Parameters[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))); } } ]; diff --git a/web/src/controllers/MetricListContainer/MetricItemsSelectedPopup.tsx b/web/src/controllers/MetricListContainer/MetricItemsSelectedPopup.tsx index 5dc55d7ce..fada2b7f4 100644 --- a/web/src/controllers/MetricListContainer/MetricItemsSelectedPopup.tsx +++ b/web/src/controllers/MetricListContainer/MetricItemsSelectedPopup.tsx @@ -194,12 +194,16 @@ const ThreeDotButton: React.FC<{ icon: , value: 'add-to-favorites', onClick: async () => { - const allFavorites: Parameters[0] = selectedRowKeys.map((id) => ({ - id, - asset_type: ShareAssetType.METRIC, - name: 'Metric' - })); - await addUserFavorite(allFavorites); + await Promise.all( + selectedRowKeys.map((id) => { + const name = userFavorites?.find((f) => f.id === id)?.name || ''; + return addUserFavorite({ + id, + asset_type: ShareAssetType.METRIC, + name + }); + }) + ); } }, { @@ -207,11 +211,7 @@ const ThreeDotButton: React.FC<{ icon: , value: 'remove-from-favorites', onClick: async () => { - const allFavorites: Parameters[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))); } } ]; diff --git a/web/src/layouts/AppAssetCheckLayout.tsx b/web/src/layouts/AppAssetCheckLayout.tsx index 3ca5984d9..c39a84615 100644 --- a/web/src/layouts/AppAssetCheckLayout.tsx +++ b/web/src/layouts/AppAssetCheckLayout.tsx @@ -72,11 +72,7 @@ export const AppAssetCheckLayout: React.FC< if (!has_access && !pagePublic) { return ( - + ); }