Skip to content

Commit

Permalink
Regression: Handle undefined values on useReactiveQuery's query f…
Browse files Browse the repository at this point in the history
…unction (#26988)
  • Loading branch information
tassoevan committed Oct 4, 2022
1 parent 26941d5 commit aa249a6
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 106 deletions.
1 change: 0 additions & 1 deletion apps/meteor/app/ui-utils/client/lib/openRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ export async function openRoom(type: RoomType, name: string, render = true) {
console.error(error);
}
}
Session.set('roomNotFound', { type, name, error });
appLayout.render(
<MainLayout>
<RoomNotFound />
Expand Down
70 changes: 24 additions & 46 deletions apps/meteor/client/hooks/useReactiveQuery.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IRole, IRoom, ISubscription, IUser } from '@rocket.chat/core-typings';
import { useQuery, UseQueryOptions, QueryKey, UseQueryResult, useQueryClient, QueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';
import { useQuery, UseQueryOptions, QueryKey, UseQueryResult, useQueryClient } from '@tanstack/react-query';
import { Tracker } from 'meteor/tracker';

import { Roles, RoomRoles, Rooms, Subscriptions, Users } from '../../app/models/client';
import { queueMicrotask } from '../lib/utils/queueMicrotask';

// For convenience as we want to minimize references to the old client models
const queryableCollections = {
Expand All @@ -13,58 +14,35 @@ const queryableCollections = {
roomRoles: RoomRoles as Mongo.Collection<Pick<ISubscription, 'rid' | 'u' | 'roles'>>,
} as const;

const dep = new Tracker.Dependency();
const reactiveSources = new Set<{
reactiveQueryFn: (collections: typeof queryableCollections) => unknown | undefined;
queryClient: QueryClient;
queryKey: QueryKey;
}>();

export const runReactiveFunctions = (): void => {
if (!Tracker.currentComputation) {
throw new Error('runReactiveFunctions must be called inside a Tracker.autorun');
}

dep.depend();

for (const { reactiveQueryFn, queryClient, queryKey } of reactiveSources) {
// This tracker will be invalidated when the query data changes
Tracker.autorun((c) => {
const data = reactiveQueryFn(queryableCollections);
if (!c.firstRun) queryClient.setQueryData(queryKey, data);
});
}
};

// While React Query handles all async stuff, we need to handle the reactive stuff ourselves using effects
export const useReactiveQuery = <TQueryFnData, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(
queryKey: TQueryKey,
reactiveQueryFn: (collections: typeof queryableCollections) => TQueryFnData | undefined,
reactiveQueryFn: (collections: typeof queryableCollections) => TQueryFnData,
options?: UseQueryOptions<TQueryFnData, Error, TData, TQueryKey>,
): UseQueryResult<TData, Error> => {
const queryClient = useQueryClient();

useEffect(() => {
const reactiveSource = { reactiveQueryFn, queryClient, queryKey };

reactiveSources.add(reactiveSource);
dep.changed();

return (): void => {
reactiveSources.delete(reactiveSource);
dep.changed();
};
});

return useQuery(
queryKey,
(): Promise<TQueryFnData> => {
const result = Tracker.nonreactive(() => reactiveQueryFn(queryableCollections));

if (result) return Promise.resolve(result);

return new Promise(() => undefined);
},
(): Promise<TQueryFnData> =>
new Promise((resolve, reject) => {
queueMicrotask(() => {
Tracker.autorun((c) => {
const data = reactiveQueryFn(queryableCollections);

if (c.firstRun) {
if (data === undefined) {
reject(new Error('Reactive query returned undefined'));
} else {
resolve(data);
}
return;
}

queryClient.refetchQueries(queryKey, { exact: true });
c.stop();
});
});
}),
{ staleTime: Infinity, ...options },
);
};
2 changes: 1 addition & 1 deletion apps/meteor/client/startup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import './incomingMessages';
import './ldap';
import './loadMissedMessages';
import './loginViaQuery';
import './mergeSubscriptionsAndRooms';
import './messageObserve';
import './messageTypes';
import './notifications';
import './oauth';
import './openedRoom';
import './otr';
import './queryCache';
import './readMessage';
import './readReceipt';
import './reloadRoomAfterLogin';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { IOmnichannelRoom, IRoom, IRoomWithRetentionPolicy, ISubscription } from '@rocket.chat/core-typings';
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { Tracker } from 'meteor/tracker';

import { Rooms, Subscriptions } from '../../app/models/client';
import { callbacks } from '../../lib/callbacks';
import { SubscriptionWithRoom } from '../definitions/SubscriptionWithRoom';
import { runReactiveFunctions } from '../hooks/useReactiveQuery';

const getLowerCaseNames = (
room: Pick<IRoom, 'name' | 'fname' | 'prid'>,
Expand Down Expand Up @@ -180,9 +177,3 @@ callbacks.add('cachedCollection-loadFromServer-rooms', mergeRoomSub);
callbacks.add('cachedCollection-received-subscriptions', mergeSubRoom);
callbacks.add('cachedCollection-sync-subscriptions', mergeSubRoom);
callbacks.add('cachedCollection-loadFromServer-subscriptions', mergeSubRoom);

Meteor.startup(() => {
Tracker.autorun(() => {
runReactiveFunctions();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { IRoom } from '@rocket.chat/core-typings';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useMutation, UseMutationOptions, UseMutationResult, useQueryClient } from '@tanstack/react-query';

export const usePutChatOnHoldMutation = (
options?: Omit<UseMutationOptions<void, Error, IRoom['_id']>, 'mutationFn'>,
): UseMutationResult<void, Error, IRoom['_id']> => {
const putChatOnHold = useEndpoint('POST', '/v1/livechat/room.onHold');

const queryClient = useQueryClient();

return useMutation(
async (rid) => {
await putChatOnHold({ roomId: rid });
},
{
...options,
onSuccess: async (data, rid, context) => {
await queryClient.invalidateQueries(['current-chats']);
await queryClient.invalidateQueries(['rooms', rid]);
await queryClient.invalidateQueries(['subscriptions', { rid }]);
return options?.onSuccess?.(data, rid, context);
},
},
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import TranscriptModal from '../../../../../../components/Omnichannel/modals/Tra
import { useOmnichannelRouteConfig } from '../../../../../../hooks/omnichannel/useOmnichannelRouteConfig';
import { QuickActionsActionConfig, QuickActionsEnum } from '../../../../lib/QuickActions';
import { useQuickActionsContext } from '../../../../lib/QuickActions/QuickActionsContext';
import { usePutChatOnHoldMutation } from './usePutChatOnHoldMutation';
import { useReturnChatToQueueMutation } from './useReturnChatToQueueMutation';

export const useQuickActions = (
room: IOmnichannelRoom,
Expand All @@ -33,7 +35,7 @@ export const useQuickActions = (
getAction: (id: string) => void;
} => {
const setModal = useSetModal();
const route = useRoute('home');
const homeRoute = useRoute('home');

const t = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
Expand Down Expand Up @@ -71,24 +73,6 @@ export const useQuickActions = (

const closeModal = useCallback(() => setModal(null), [setModal]);

const closeOnHoldModal = useCallback(() => {
closeModal();
setOnHoldModalActive(false);
}, [closeModal]);

const methodReturn = useMethod('livechat:returnAsInquiry');

const handleMoveChat = useCallback(async () => {
try {
await methodReturn(rid);
closeModal();
Session.set('openedRoom', null);
route.push();
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
}
}, [closeModal, dispatchToastMessage, methodReturn, rid, route]);

const requestTranscript = useMethod('livechat:requestTranscript');

const handleRequestTranscript = useCallback(
Expand Down Expand Up @@ -168,13 +152,13 @@ export const useQuickActions = (
throw new Error(departmentId ? t('error-no-agents-online-in-department') : t('error-forwarding-chat'));
}
dispatchToastMessage({ type: 'success', message: t('Transferred') });
route.push();
homeRoute.push();
closeModal();
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
}
},
[closeModal, dispatchToastMessage, forwardChat, rid, route, t],
[closeModal, dispatchToastMessage, forwardChat, rid, homeRoute, t],
);

const closeChat = useMethod('livechat:closeRoom');
Expand All @@ -192,22 +176,42 @@ export const useQuickActions = (
[closeChat, closeModal, dispatchToastMessage, rid, t],
);

const onHoldChat = useEndpoint('POST', '/v1/livechat/room.onHold');

const handleOnHoldChat = useCallback(async () => {
try {
await onHoldChat({ roomId: rid });
const returnChatToQueueMutation = useReturnChatToQueueMutation({
onSuccess: () => {
Session.set('openedRoom', null);
homeRoute.push();
},
onError: (error) => {
dispatchToastMessage({ type: 'error', message: error });
},
onSettled: () => {
closeModal();
},
});

const putChatOnHoldMutation = usePutChatOnHoldMutation({
onSuccess: () => {
dispatchToastMessage({ type: 'success', message: t('Chat_On_Hold_Successfully') });
} catch (error) {
},
onError: (error) => {
dispatchToastMessage({ type: 'error', message: error });
}
}, [onHoldChat, rid, closeModal, dispatchToastMessage, t]);
},
onSettled: () => {
closeModal();
},
});

const openModal = useMutableCallback(async (id: string) => {
switch (id) {
case QuickActionsEnum.MoveQueue:
setModal(<ReturnChatQueueModal onMoveChat={handleMoveChat} onCancel={closeModal} />);
setModal(
<ReturnChatQueueModal
onMoveChat={(): void => returnChatToQueueMutation.mutate(rid)}
onCancel={(): void => {
closeModal();
}}
/>,
);
break;
case QuickActionsEnum.Transcript:
const visitorEmail = await getVisitorEmail();
Expand Down Expand Up @@ -241,7 +245,15 @@ export const useQuickActions = (
);
break;
case QuickActionsEnum.OnHoldChat:
setModal(<PlaceChatOnHoldModal onOnHoldChat={handleOnHoldChat} onCancel={closeOnHoldModal} />);
setModal(
<PlaceChatOnHoldModal
onOnHoldChat={(): void => putChatOnHoldMutation.mutate(rid)}
onCancel={(): void => {
closeModal();
setOnHoldModalActive(false);
}}
/>,
);
setOnHoldModalActive(true);
break;
default:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { IRoom } from '@rocket.chat/core-typings';
import { useMethod } from '@rocket.chat/ui-contexts';
import { useMutation, UseMutationOptions, UseMutationResult, useQueryClient } from '@tanstack/react-query';

export const useReturnChatToQueueMutation = (
options?: Omit<UseMutationOptions<void, Error, IRoom['_id']>, 'mutationFn'>,
): UseMutationResult<void, Error, IRoom['_id']> => {
const returnChatToQueue = useMethod('livechat:returnAsInquiry');

const queryClient = useQueryClient();

return useMutation(
async (rid) => {
await returnChatToQueue(rid);
},
{
...options,
onSuccess: async (data, rid, context) => {
await queryClient.invalidateQueries(['current-chats']);
queryClient.removeQueries(['rooms', rid]);
queryClient.removeQueries(['subscriptions', { rid }]);
return options?.onSuccess?.(data, rid, context);
},
},
);
};
2 changes: 1 addition & 1 deletion apps/meteor/client/views/room/components/body/RoomBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ const RoomBody = (): ReactElement => {
});

if (!leaderRoomRole) {
return;
return null;
}

const leaderUser = users.findOne({ _id: leaderRoomRole.u._id }, { fields: { status: 1, statusText: 1 } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ComposerAnonymous } from './ComposerAnonymous';
import { ComposerBlocked } from './ComposerBlocked';
import { ComposerJoinWithPassword } from './ComposerJoinWithPassword';
import ComposerMessage, { ComposerMessageProps } from './ComposerMessage';
import { ComposerOmnichannel } from './ComposerOmnichannel/ComposerOmnichannel';
import ComposerOmnichannel from './ComposerOmnichannel/ComposerOmnichannel';
import { ComposerReadOnly } from './ComposerReadOnly';
import ComposerVoIP from './ComposerVoIP';
import { useMessageComposerIsAnonymous } from './hooks/useMessageComposerIsAnonymous';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ComposerOmnichannelInquiry } from './ComposerOmnichannelInquiry';
import { ComposerOmnichannelJoin } from './ComposerOmnichannelJoin';
import { ComposerOmnichannelOnHold } from './ComposerOmnichannelOnHold';

export const ComposerOmnichannel = (props: ComposerMessageProps): ReactElement => {
const ComposerOmnichannel = (props: ComposerMessageProps): ReactElement => {
const { queuedAt, servedBy, _id, open, onHold } = useOmnichannelRoom();

const isSubscribed = useUserIsSubscribed();
Expand All @@ -20,12 +20,14 @@ export const ComposerOmnichannel = (props: ComposerMessageProps): ReactElement =

const t = useTranslation();

useEffect(() => {
subscribeToRoom(_id, (entry: IOmnichannelRoom) => {
setIsInquired(!entry.servedBy && entry.queuedAt);
setIsOpen(entry.open);
});
}, [_id, subscribeToRoom]);
useEffect(
() =>
subscribeToRoom(_id, (entry: IOmnichannelRoom) => {
setIsInquired(!entry.servedBy && entry.queuedAt);
setIsOpen(entry.open);
}),
[_id, subscribeToRoom],
);

useEffect(() => {
setIsInquired(!servedBy && queuedAt);
Expand Down Expand Up @@ -57,3 +59,5 @@ export const ComposerOmnichannel = (props: ComposerMessageProps): ReactElement =
</>
);
};

export default ComposerOmnichannel;

0 comments on commit aa249a6

Please sign in to comment.