started moving mutation to a stable handler

This commit is contained in:
Nate Kelley 2025-02-18 15:14:51 -07:00
parent d542862e7b
commit acac1ed700
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
15 changed files with 136 additions and 185 deletions

View File

@ -24,4 +24,5 @@ export interface BusterChatListItem {
created_by_id: string;
created_by_name: string;
created_by_avatar: string | null;
last_edited: string;
}

View File

@ -8,7 +8,7 @@ import {
} from '@tanstack/react-query';
import type { InferBusterSocketResponseData } from './types';
import { useBusterWebSocket } from '@/context/BusterWebSocket';
import { useEffect } from 'react';
import { useEffect, useMemo } from 'react';
import { useSocketQueryOn } from './useSocketQueryOn';
import { timeout } from '@/utils';
@ -36,9 +36,15 @@ export const useSocketQueryEmitOn = <
const busterSocket = useBusterWebSocket();
const enabledTrigger = enabledTriggerProp ?? true;
const mutationKey = useMemo(() => {
return ['socket-emit', ...options.queryKey];
}, [options.queryKey]);
console.log(mutationKey);
// Use mutation for deduped socket emissions
const { mutate: emitQueryFn } = useMutation({
mutationKey: ['socket-emit', ...options.queryKey],
const { mutateAsync: emitQueryFn } = useMutation({
mutationKey,
mutationFn: async () => {
busterSocket.emit(emitEvent);
await timeout(250);
@ -54,6 +60,8 @@ export const useSocketQueryEmitOn = <
const isStale =
!queryState?.dataUpdatedAt || Date.now() - queryState.dataUpdatedAt >= staleTime;
console.log(isStale, queryState);
if (enabledTrigger && (isStale || !queryState)) {
emitQueryFn();
}

View File

@ -9,7 +9,7 @@ import {
type BusterSearchResult,
ShareAssetType
} from '@/api/asset_interfaces';
import { asset_typeToIcon } from '@/app/_helpers';
import { asset_typeToIcon } from '@/app/app/_helpers';
import { CircleSpinnerLoaderContainer } from '@/components/loaders';
import { BusterCollection } from '@/api/asset_interfaces';
import { useBusterSearchContextSelector } from '@/context/Search';

View File

@ -33,17 +33,6 @@ export const NewCollectionModal: React.FC<{
};
}, []);
const memoizedFooter = useMemo(() => {
return {
primaryButton: {
text: 'Create a collection',
onClick: onCreateNewCollection,
loading: isCreatingCollection,
disabled: disableSubmit
}
};
}, [isCreatingCollection, disableSubmit]);
const onCreateNewCollection = useMemoizedFn(async () => {
if (isCreatingCollection || disableSubmit) return;
const res = await createNewCollection({ name: title, onCollectionCreated });
@ -58,6 +47,17 @@ export const NewCollectionModal: React.FC<{
}, 200);
});
const memoizedFooter = useMemo(() => {
return {
primaryButton: {
text: 'Create a collection',
onClick: onCreateNewCollection,
loading: isCreatingCollection,
disabled: disableSubmit
}
};
}, [isCreatingCollection, disableSubmit]);
useEffect(() => {
if (open) {
setTimeout(() => {

View File

@ -1,7 +1,7 @@
'use client';
import { ShareAssetType } from '@/api/asset_interfaces';
import { asset_typeToTranslation } from '@/app/_helpers';
import { asset_typeToTranslation } from '@/app/app/_helpers';
import { BusterLogo } from '@/assets/svg/BusterLogo';
import { Title } from '@/components/text';
import { useBusterNotifications } from '@/context/BusterNotifications';

View File

@ -8,7 +8,7 @@ import Link from 'next/link';
import { useParams } from 'next/navigation';
import React, { useMemo, useState } from 'react';
import isEmpty from 'lodash/isEmpty';
import { asset_typeToIcon } from '@/app/_helpers';
import { asset_typeToIcon } from '@/app/app/_helpers';
import { useMemoizedFn } from 'ahooks';
export const FavoritesDropdown: React.FC<{}> = React.memo(() => {

View File

@ -68,7 +68,7 @@ export const CollectionListHeader: React.FC<{
[collectionId, collectionTitle]
);
useHotkeys('c', () => {
useHotkeys('n', () => {
setOpenNewCollectionModal(true);
});
@ -86,7 +86,7 @@ export const CollectionListHeader: React.FC<{
</div>
<div className="flex items-center">
<AppTooltip title={'Create new collection'} shortcuts={['C']}>
<AppTooltip title={'Create new collection'} shortcuts={['N']}>
<Button
type="default"
icon={<AppMaterialIcons icon="add" />}

View File

@ -1,12 +1,5 @@
import { ShareAssetType, VerificationStatus, BusterMetricListItem } from '@/api/asset_interfaces';
import {
getNow,
isDateSame,
isDateBefore,
isDateAfter,
makeHumanReadble,
formatDate
} from '@/utils';
import { ShareAssetType, VerificationStatus, BusterChatListItem } from '@/api/asset_interfaces';
import { makeHumanReadble, formatDate } from '@/utils';
import React, { memo, useMemo, useRef, useState } from 'react';
import { StatusBadgeIndicator, getShareStatus } from '../../_components/Lists';
import { BusterUserAvatar, Text } from '@/components';
@ -16,62 +9,14 @@ import { BusterListColumn, BusterListRow } from '@/components/list';
import { ChatSelectedOptionPopup } from './ChatItemsSelectedPopup';
import { BusterList, ListEmptyStateWithButton } from '@/components/list';
import { FavoriteStar } from '../../_components/Lists';
const createLogRecord = (
data: BusterMetricListItem[]
): {
TODAY: BusterMetricListItem[];
YESTERDAY: BusterMetricListItem[];
LAST_WEEK: BusterMetricListItem[];
ALL_OTHERS: BusterMetricListItem[];
} => {
const today = getNow();
const TODAY = data.filter((d) =>
isDateSame({
date: d.last_edited,
compareDate: today,
interval: 'day'
})
);
const YESTERDAY = data.filter((d) =>
isDateSame({
date: d.last_edited,
compareDate: today.subtract(1, 'day'),
interval: 'day'
})
);
const LAST_WEEK = data.filter(
(d) =>
isDateBefore({
date: d.last_edited,
compareDate: today.subtract(2, 'day').startOf('day'),
interval: 'day'
}) &&
isDateAfter({
date: d.last_edited,
compareDate: today.subtract(8, 'day').startOf('day'),
interval: 'day'
})
);
const ALL_OTHERS = data.filter(
(d) => !TODAY.includes(d) && !YESTERDAY.includes(d) && !LAST_WEEK.includes(d)
);
return {
TODAY,
YESTERDAY,
LAST_WEEK,
ALL_OTHERS
};
};
import { useCreateListByDate } from '@/components/list/useCreateListByDate';
export const ChatItemsContainer: React.FC<{
metrics: BusterMetricListItem[];
chats: BusterChatListItem[];
className?: string;
openNewMetricModal: () => void;
type: 'logs' | 'metrics';
loading: boolean;
}> = ({ type, metrics = [], className = '', loading, openNewMetricModal }) => {
}> = ({ chats = [], className = '', loading, openNewMetricModal }) => {
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
const renderedDates = useRef<Record<string, string>>({});
const renderedOwners = useRef<Record<string, React.ReactNode>>({});
@ -82,9 +27,9 @@ export const ChatItemsContainer: React.FC<{
});
const hasSelected = selectedRowKeys.length > 0;
const logsRecord = useMemo(() => createLogRecord(metrics), [metrics]);
const logsRecord = useCreateListByDate({ data: chats });
const metricsByDate: BusterListRow[] = useMemo(() => {
const chatsByDate: BusterListRow[] = useMemo(() => {
return Object.entries(logsRecord).flatMap(([key, metrics]) => {
const records = metrics.map((metric) => ({
id: metric.id,
@ -95,9 +40,9 @@ export const ChatItemsContainer: React.FC<{
})
}));
const hasRecords = records.length > 0;
if (!hasRecords) {
return [];
}
if (!hasRecords) return [];
return [
{
id: key,
@ -160,18 +105,18 @@ export const ChatItemsContainer: React.FC<{
[]
);
console.log(chatsByDate, loading);
return (
<div
ref={tableContainerRef}
className={`${className} relative flex h-full flex-col items-center`}>
<BusterList
rows={metricsByDate}
rows={chatsByDate}
columns={columns}
onSelectChange={onSelectChange}
selectedRowKeys={selectedRowKeys}
emptyState={
<EmptyState loading={loading} type={type} openNewMetricModal={openNewMetricModal} />
}
emptyState={<EmptyState loading={loading} openNewMetricModal={openNewMetricModal} />}
/>
<ChatSelectedOptionPopup
@ -185,36 +130,23 @@ export const ChatItemsContainer: React.FC<{
const EmptyState: React.FC<{
loading: boolean;
type: 'logs' | 'metrics';
openNewMetricModal: () => void;
}> = React.memo(({ loading, type, openNewMetricModal }) => {
}> = React.memo(({ loading, openNewMetricModal }) => {
if (loading) {
return <></>;
}
return <ChatsEmptyState openNewMetricModal={openNewMetricModal} type={type} />;
return <ChatsEmptyState openNewMetricModal={openNewMetricModal} />;
});
EmptyState.displayName = 'EmptyState';
const ChatsEmptyState: React.FC<{
openNewMetricModal: () => void;
type: 'logs' | 'metrics';
}> = ({ openNewMetricModal, type }) => {
if (type === 'logs') {
return (
<ListEmptyStateWithButton
title="You don't have any logs yet."
description="You don't have any logs. As soon as you do, they will start to appear here."
buttonText="New chat"
onClick={openNewMetricModal}
/>
);
}
}> = ({ openNewMetricModal }) => {
return (
<ListEmptyStateWithButton
title="You don't have any metrics yet."
description="You don't have any metrics. As soon as you do, they will start to appear here."
title="You don't have any chats yet."
description="You don't have any chats. As soon as you do, they will start to appear here."
buttonText="New chat"
onClick={openNewMetricModal}
/>

View File

@ -4,8 +4,7 @@ import React, { useState } from 'react';
import { AppContent } from '../../../../components/layout/AppContent';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import { useMemoizedFn, useMount } from 'ahooks';
import { VerificationStatus } from '@/api/asset_interfaces';
import { useBusterMetricListByFilter } from '@/context/Metrics';
import { useBusterChatListByFilter } from '@/context/Chats';
import { ChatListHeader } from './ChatListHeader';
import { ChatItemsContainer } from './ChatItemsContainer';
@ -13,21 +12,20 @@ export const ChatListContainer: React.FC<{
className?: string;
}> = ({ className = '' }) => {
const onToggleChatsModal = useAppLayoutContextSelector((s) => s.onToggleChatsModal);
const [filters, setFilters] = useState<VerificationStatus[]>([]);
const type = 'logs';
const adminView = type === 'logs';
// const { list, fetched } = useBusterMetricListByFilter({
// filters,
// admin_view: adminView
// });
const onSetFilters = useMemoizedFn((newFilters: VerificationStatus[]) => {
setFilters(newFilters);
const [filters, setFilters] = useState<Parameters<typeof useBusterChatListByFilter>[0]>({
admin_view: false
});
const { list, isFetched } = useBusterChatListByFilter(filters);
const onSetFilters = useMemoizedFn(
(newFilters: Parameters<typeof useBusterChatListByFilter>[0]) => {
setFilters(newFilters);
}
);
useMount(async () => {
onSetFilters([]);
onSetFilters({ admin_view: false });
});
return (
@ -35,9 +33,8 @@ export const ChatListContainer: React.FC<{
<ChatListHeader />
<AppContent>
<ChatItemsContainer
type={type}
metrics={[]}
loading={false}
chats={list}
loading={!isFetched}
openNewMetricModal={onToggleChatsModal}
className="flex-col overflow-hidden"
/>

View File

@ -5,8 +5,6 @@ import { AppContentHeader } from '../../../../components/layout/AppContentHeader
import { Text } from '@/components/text';
export const ChatListHeader: React.FC<{}> = ({}) => {
const showFilters: boolean = true;
return (
<AppContentHeader>
<div className="flex w-full items-center justify-between">

View File

@ -1,12 +1,5 @@
import { ShareAssetType, VerificationStatus, BusterMetricListItem } from '@/api/asset_interfaces';
import {
getNow,
isDateSame,
isDateBefore,
isDateAfter,
makeHumanReadble,
formatDate
} from '@/utils';
import { makeHumanReadble, formatDate } from '@/utils';
import React, { memo, useMemo, useRef, useState } from 'react';
import { StatusBadgeIndicator, getShareStatus } from '../../_components/Lists';
import { BusterUserAvatar, Text } from '@/components';
@ -16,54 +9,7 @@ import { BusterListColumn, BusterListRow } from '@/components/list';
import { MetricSelectedOptionPopup } from './MetricItemsSelectedPopup';
import { BusterList, ListEmptyStateWithButton } from '@/components/list';
import { FavoriteStar } from '../../_components/Lists';
const createLogRecord = (
data: BusterMetricListItem[]
): {
TODAY: BusterMetricListItem[];
YESTERDAY: BusterMetricListItem[];
LAST_WEEK: BusterMetricListItem[];
ALL_OTHERS: BusterMetricListItem[];
} => {
const today = getNow();
const TODAY = data.filter((d) =>
isDateSame({
date: d.last_edited,
compareDate: today,
interval: 'day'
})
);
const YESTERDAY = data.filter((d) =>
isDateSame({
date: d.last_edited,
compareDate: today.subtract(1, 'day'),
interval: 'day'
})
);
const LAST_WEEK = data.filter(
(d) =>
isDateBefore({
date: d.last_edited,
compareDate: today.subtract(2, 'day').startOf('day'),
interval: 'day'
}) &&
isDateAfter({
date: d.last_edited,
compareDate: today.subtract(8, 'day').startOf('day'),
interval: 'day'
})
);
const ALL_OTHERS = data.filter(
(d) => !TODAY.includes(d) && !YESTERDAY.includes(d) && !LAST_WEEK.includes(d)
);
return {
TODAY,
YESTERDAY,
LAST_WEEK,
ALL_OTHERS
};
};
import { useCreateListByDate } from '@/components/list/useCreateListByDate';
export const MetricItemsContainer: React.FC<{
metrics: BusterMetricListItem[];
@ -82,7 +28,7 @@ export const MetricItemsContainer: React.FC<{
});
const hasSelected = selectedRowKeys.length > 0;
const logsRecord = useMemo(() => createLogRecord(metrics), [metrics]);
const logsRecord = useCreateListByDate({ data: metrics });
const metricsByDate: BusterListRow[] = useMemo(() => {
return Object.entries(logsRecord).flatMap(([key, metrics]) => {

View File

@ -0,0 +1,61 @@
import { getNow, isDateAfter, isDateBefore, isDateSame } from '@/utils/date';
import { useMemo } from 'react';
type ListItem = {
id: string;
last_edited: string;
};
const createChatRecord = <T extends ListItem>(
data: T[]
): {
TODAY: T[];
YESTERDAY: T[];
LAST_WEEK: T[];
ALL_OTHERS: T[];
} => {
const today = getNow();
const TODAY = data.filter((d) =>
isDateSame({
date: d.last_edited,
compareDate: today,
interval: 'day'
})
);
const YESTERDAY = data.filter((d) =>
isDateSame({
date: d.last_edited,
compareDate: today.subtract(1, 'day'),
interval: 'day'
})
);
const LAST_WEEK = data.filter(
(d) =>
isDateBefore({
date: d.last_edited,
compareDate: today.subtract(2, 'day').startOf('day'),
interval: 'day'
}) &&
isDateAfter({
date: d.last_edited,
compareDate: today.subtract(8, 'day').startOf('day'),
interval: 'day'
})
);
const ALL_OTHERS = data.filter(
(d) => !TODAY.includes(d) && !YESTERDAY.includes(d) && !LAST_WEEK.includes(d)
);
return {
TODAY,
YESTERDAY,
LAST_WEEK,
ALL_OTHERS
};
};
export const useCreateListByDate = <T extends ListItem>({ data }: { data: T[] }) => {
const listRecord = useMemo(() => createChatRecord(data), [data]);
return listRecord;
};

View File

@ -1,12 +1,20 @@
import { useSocketQueryEmitOn } from '@/api/buster_socket_query';
import { queryKeys } from '@/api/query_keys';
import { GetChatListParams } from '@/api/request_interfaces/chats';
import { useMemo } from 'react';
export const useBusterChatListByFilter = (
filtersProp: Omit<GetChatListParams, 'page_token' | 'page_size'>
) => {
const filters = useMemo(
() => ({ ...filtersProp, page_token: 0, page_size: 3000 }),
[filtersProp]
);
export const useBusterChatListByFilter = (filters: GetChatListParams) => {
const { data: chatsList, isFetched: isFetchedChatsList } = useSocketQueryEmitOn({
emitEvent: {
route: '/chats/list',
payload: { page_token: 0, page_size: 3000, admin_view: false }
payload: filters
},
responseEvent: '/chats/list:getChatsList',
options: queryKeys['chatsGetList'](filters)
@ -15,7 +23,7 @@ export const useBusterChatListByFilter = (filters: GetChatListParams) => {
//ACTIONS
return {
list: chatsList,
list: chatsList || [],
isFetched: isFetchedChatsList
};
};