ripped out terms provider

This commit is contained in:
Nate Kelley 2025-03-12 14:19:40 -06:00
parent 6f188111c6
commit 55ffb94fe2
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
15 changed files with 75 additions and 188 deletions

View File

@ -1,9 +1,11 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { createTerm, deleteTerms, getTerm, getTermsList, updateTerm } from './requests';
import { TermsListParams } from '@/api/request_interfaces/terms';
import { TermDeleteParams, TermsListParams } from '@/api/request_interfaces/terms';
import { queryKeys } from '@/api/query_keys';
import { BusterTerm } from '@/api/asset_interfaces/terms';
import { useMemo } from 'react';
import { useMemoizedFn } from '@/hooks';
import { useBusterNotifications } from '@/context/BusterNotifications';
export const useGetTermsList = (params?: Omit<TermsListParams, 'page' | 'page_size'>) => {
const compiledParams: TermsListParams = useMemo(
@ -17,10 +19,11 @@ export const useGetTermsList = (params?: Omit<TermsListParams, 'page' | 'page_si
});
};
export const useGetTerm = (id: string) => {
export const useGetTerm = (id: string | undefined) => {
return useQuery({
...queryKeys.termsGetTerm(id),
queryFn: () => getTerm(id)
...queryKeys.termsGetTerm(id!),
queryFn: () => getTerm(id!),
enabled: !!id
});
};
@ -50,9 +53,28 @@ export const useUpdateTerm = () => {
export const useDeleteTerm = () => {
const queryClient = useQueryClient();
const { openConfirmModal } = useBusterNotifications();
const mutationFn = useMemoizedFn(
({ ids, ignoreConfirm = false }: TermDeleteParams & { ignoreConfirm?: boolean }) => {
const method = async () => {
deleteTerms(ids);
};
if (ignoreConfirm) {
return method();
}
return openConfirmModal({
title: 'Delete term',
content: 'Are you sure you want to delete this term?',
onOk: method
});
}
);
return useMutation({
mutationFn: deleteTerms,
onSuccess: (data) => {
mutationFn,
onSuccess: () => {
const options = queryKeys.termsGetList;
queryClient.invalidateQueries({ queryKey: options.queryKey });
}

View File

@ -1,5 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useBusterTermsIndividualContextSelector } from '@/context/Terms';
import { Text } from '@/components/ui/typography';
import { AppModal } from '@/components/ui/modal';
import { useMemoizedFn } from '@/hooks';
@ -8,14 +7,14 @@ import { InputTextArea } from '@/components/ui/inputs/InputTextArea';
import { Input } from '@/components/ui/inputs/Input';
import { SelectMultiple } from '@/components/ui/select/SelectMultiple';
import { SelectItem } from '@/components/ui/select';
import { useCreateTerm } from '@/api/buster_rest/terms';
export const NewTermModal: React.FC<{
open: boolean;
onClose: () => void;
}> = React.memo(({ open, onClose }) => {
const titleRef = React.useRef<HTMLInputElement>(null);
const createTerm = useBusterTermsIndividualContextSelector((x) => x.createTerm);
const isCreatingTerm = useBusterTermsIndividualContextSelector((x) => x.isCreatingTerm);
const { mutateAsync: createTerm, isPending: isCreatingTerm } = useCreateTerm();
const [title, setTitle] = useState('');
const [definition, setDefinition] = useState('');
const [selectedDatasets, setSelectedDatasets] = useState<string[]>([]);

View File

@ -7,7 +7,6 @@ import { AppLayoutProvider } from './BusterAppLayout';
import { BusterDashboardProvider } from './Dashboards/DashboardProvider';
import { BusterUserConfigProvider } from './Users/UserConfigProvider';
import { BusterSQLProvider } from './SQL/useSQLProvider';
import { BusterTermsProvider } from './Terms/BusterTermsProvider';
import { BusterSearchProvider } from './Search';
import { BusterAssetsProvider } from './Assets/BusterAssetsProvider';
import { BusterPosthogProvider } from './Posthog/usePosthog';
@ -39,14 +38,12 @@ export const AppProviders: React.FC<
<BusterMetricsProvider>
<BusterDashboardProvider>
<BusterSQLProvider>
<BusterTermsProvider>
<BusterChatProvider>
<BusterPosthogProvider>
{children}
<RoutePrefetcher />
</BusterPosthogProvider>
</BusterChatProvider>
</BusterTermsProvider>
<BusterChatProvider>
<BusterPosthogProvider>
{children}
<RoutePrefetcher />
</BusterPosthogProvider>
</BusterChatProvider>
</BusterSQLProvider>
</BusterDashboardProvider>
</BusterMetricsProvider>

View File

@ -1,34 +0,0 @@
'use client';
import React from 'react';
import { createContext, useContextSelector } from 'use-context-selector';
import { useBusterTermsCreate } from './useBusterTermsCreate';
import { useBusterTermsUpdate } from './useBusterTermsUpdate';
const useBusterTermsIndividual = () => {
const createTerms = useBusterTermsCreate();
const updateTerms = useBusterTermsUpdate();
return {
...createTerms,
...updateTerms
};
};
const BusterTermsIndividualContext = createContext<ReturnType<typeof useBusterTermsIndividual>>(
{} as ReturnType<typeof useBusterTermsIndividual>
);
export const BusterTermsIndividualProvider: React.FC<{ children: React.ReactNode }> = ({
children
}) => {
return (
<BusterTermsIndividualContext.Provider value={useBusterTermsIndividual()}>
{children}
</BusterTermsIndividualContext.Provider>
);
};
export const useBusterTermsIndividualContextSelector = <T,>(
selector: (state: ReturnType<typeof useBusterTermsIndividual>) => T
) => useContextSelector(BusterTermsIndividualContext, selector);

View File

@ -1,2 +0,0 @@
export * from './BusterTermsIndividualProvider';
export * from './useBusterTermsIndividual';

View File

@ -1,35 +0,0 @@
import { useBusterNotifications } from '@/context/BusterNotifications';
import { useMemoizedFn } from '@/hooks';
import type { TermDeleteParams } from '@/api/request_interfaces/terms';
import { useCreateTerm, useDeleteTerm } from '@/api/buster_rest/terms';
export const useBusterTermsCreate = () => {
const { openConfirmModal } = useBusterNotifications();
const { mutateAsync: createTerm, isPending: isCreatingTerm } = useCreateTerm();
const { mutate: deleteTermMutation, isPending: isDeletingTerm } = useDeleteTerm();
const onDeleteTerm = useMemoizedFn(({ ids }: TermDeleteParams, ignoreConfirm = false) => {
const method = async () => {
deleteTermMutation(ids);
};
if (ignoreConfirm) {
return method();
}
return openConfirmModal({
title: 'Delete term',
content: 'Are you sure you want to delete this term?',
onOk: method
});
});
return {
createTerm,
onDeleteTerm,
isCreatingTerm,
isDeletingTerm
};
};

View File

@ -1,11 +0,0 @@
import { useGetTerm } from '@/api/buster_rest/terms';
export const useBusterTermsIndividual = ({ termId }: { termId: string }) => {
const { data: term, refetch: refetchTerm, isFetched: isFetchedTerm } = useGetTerm(termId);
return {
term,
refetchTerm,
isFetchedTerm
};
};

View File

@ -1,9 +0,0 @@
import { useUpdateTerm } from '@/api/buster_rest/terms';
export const useBusterTermsUpdate = () => {
const { mutate: updateTerm } = useUpdateTerm();
return {
updateTerm
};
};

View File

@ -1,10 +0,0 @@
import React from 'react';
import { BusterTermsIndividualProvider } from './BusterTermsIndividualProvider';
export const BusterTermsProvider: React.FC<{
children: React.ReactNode;
}> = React.memo(({ children }) => {
return <BusterTermsIndividualProvider>{children}</BusterTermsIndividualProvider>;
});
BusterTermsProvider.displayName = 'BusterTermsProvider';

View File

@ -1,9 +0,0 @@
export * from './interfaces';
export * from './BusterTermsProvider';
import {
useBusterTermsIndividualContextSelector,
useBusterTermsIndividual
} from './BusterTermsIndividualProvider';
export { useBusterTermsIndividualContextSelector, useBusterTermsIndividual };

View File

@ -1,18 +0,0 @@
import { BusterTerm, BusterTermListItem } from '@/api/asset_interfaces/terms';
export type UseTermsContextSelector = <T>(selector: (state: UseTermsHookReturn) => T) => T;
export interface UseTermsHookReturn {
getTermFromList: (termId: string) => BusterTermListItem | undefined;
createTerm: (params: any) => Promise<any>;
subscribeToTerm: ({ id }: { id: string }) => Promise<any>;
termsList: BusterTermListItem[];
loadedTermsList: boolean;
getInitialTerms: () => Promise<void>;
onSetOpenNewTermsModal: (value: boolean) => void;
updateTerm: (params: any) => Promise<any>;
deleteTerm: ({ id }: { id: string }, ignoreConfirm?: boolean) => Promise<any>;
openNewTermsModal: boolean;
unsubscribeFromTerm: (termId: string) => void;
terms: Record<string, BusterTerm>;
}

View File

@ -2,7 +2,6 @@
import React, { useEffect, useMemo, useState } from 'react';
import { AppPageLayoutContent } from '@/components/ui/layouts/AppPageLayoutContent';
import { useBusterTermsIndividualContextSelector, useBusterTermsIndividual } from '@/context/Terms';
import { Dropdown, DropdownItems } from '@/components/ui/dropdown';
import { Button } from '@/components/ui/buttons';
import { useDebounceFn } from '@/hooks';
@ -14,27 +13,21 @@ import clamp from 'lodash/clamp';
import { Text } from '@/components/ui/typography';
import { BusterRoutes } from '@/routes';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent
} from '@/components/ui/card/CardBase';
import { Card, CardHeader, CardContent } from '@/components/ui/card/CardBase';
import { InputTextArea } from '@/components/ui/inputs/InputTextArea';
import { useDeleteTerm, useGetTerm, useUpdateTerm } from '@/api/buster_rest/terms';
export const TermIndividualContent: React.FC<{
termId: string;
}> = ({ termId }) => {
const updateTerm = useBusterTermsIndividualContextSelector((x) => x.updateTerm);
const { term: selectedTerm } = useBusterTermsIndividual({ termId });
const loadingSelectedTerm = !selectedTerm?.id;
const { mutateAsync: updateTerm } = useUpdateTerm();
const { data: term } = useGetTerm(termId);
const loadingSelectedTerm = !term?.id;
const [editingTermName, setEditingTermName] = useState(false);
const [termName, setTermName] = useState(selectedTerm?.name || '');
const [termDefinition, setTermDefinition] = useState(selectedTerm?.definition || '');
const [termSQL, setTermSQL] = useState(selectedTerm?.sql_snippet || '');
const [termName, setTermName] = useState(term?.name || '');
const [termDefinition, setTermDefinition] = useState(term?.definition || '');
const [termSQL, setTermSQL] = useState(term?.sql_snippet || '');
const [sqlHeight, setSqlHeight] = useState(300);
const onSetTermName = (value: string) => {
@ -65,10 +58,10 @@ export const TermIndividualContent: React.FC<{
);
useEffect(() => {
setTermName(selectedTerm?.name || '');
setTermDefinition(selectedTerm?.definition || '');
setTermSQL(selectedTerm?.sql_snippet || '');
}, [selectedTerm?.name, selectedTerm?.definition]);
setTermName(term?.name || '');
setTermDefinition(term?.definition || '');
setTermSQL(term?.sql_snippet || '');
}, [term?.name, term?.definition]);
return (
<AppPageLayoutContent className="overflow-auto p-8">
@ -93,7 +86,7 @@ export const TermIndividualContent: React.FC<{
<Text variant="secondary">
Last updated:{' '}
{formatDate({
date: selectedTerm?.updated_at!,
date: term?.updated_at!,
format: 'lll'
})}
</Text>
@ -109,8 +102,8 @@ export const TermIndividualContent: React.FC<{
<ItemContainer title="Definition">
<div className={'overflow-hidden'}>
<InputTextArea
key={selectedTerm?.id || 'default'}
defaultValue={selectedTerm?.definition || termDefinition}
key={term?.id || 'default'}
defaultValue={term?.definition || termDefinition}
autoResize={{ minRows: 3, maxRows: 20 }}
placeholder={'Enter definition...'}
onBlur={(e) => {
@ -164,11 +157,11 @@ const MoreDropdown: React.FC<{ termId: string; setEditingTermName: (value: boole
termId,
setEditingTermName
}) => {
const onDeleteTerm = useBusterTermsIndividualContextSelector((x) => x.onDeleteTerm);
const { mutateAsync: deleteTerm, isPending: isPendingDeleteTerm } = useDeleteTerm();
const onChangePage = useAppLayoutContextSelector((s) => s.onChangePage);
const onDeleteTermsPreflight = async () => {
await onDeleteTerm({ ids: [termId] })
await deleteTerm({ ids: [termId] })
.then(() => {
onChangePage({
route: BusterRoutes.APP_TERMS
@ -193,6 +186,7 @@ const MoreDropdown: React.FC<{ termId: string; setEditingTermName: (value: boole
value: 'delete',
icon: <Trash />,
label: 'Delete term',
loading: isPendingDeleteTerm,
onClick: onDeleteTermsPreflight
}
],

View File

@ -1,26 +1,26 @@
'use client';
import React from 'react';
import { useBusterTermsIndividualContextSelector, useBusterTermsIndividual } from '@/context/Terms';
import { Avatar } from '@/components/ui/avatar';
import { formatDate } from '@/lib';
import { Text } from '@/components/ui/typography';
import { DatasetList } from './TermDatasetSelect';
import { useGetTerm, useUpdateTerm } from '@/api/buster_rest/terms';
export const TermIndividualContentSider: React.FC<{ termId: string }> = ({ termId }) => {
const updateTerm = useBusterTermsIndividualContextSelector((x) => x.updateTerm);
const { term: selectedTerm } = useBusterTermsIndividual({ termId });
const { mutateAsync: updateTerm } = useUpdateTerm();
const { data: term } = useGetTerm(termId);
const datasets = selectedTerm?.datasets || [];
const datasets = term?.datasets || [];
const onChangeDatasets = async (datasets: string[]) => {
const add_to_dataset = datasets.filter(
(item) => !selectedTerm?.datasets?.some((dataset) => dataset.id === item)
(item) => !term?.datasets?.some((dataset) => dataset.id === item)
);
const remove_from_dataset =
selectedTerm?.datasets
?.filter((dataset) => !datasets.includes(dataset.id))
.map((item) => item.id) || [];
term?.datasets?.filter((dataset) => !datasets.includes(dataset.id)).map((item) => item.id) ||
[];
await updateTerm({
id: termId,
@ -45,12 +45,12 @@ export const TermIndividualContentSider: React.FC<{ termId: string }> = ({ termI
</Text>
<div className="flex items-center space-x-1.5">
<Avatar size={24} name={selectedTerm?.created_by.name} />
<Text>{selectedTerm?.created_by.name}</Text>
<Avatar size={24} name={term?.created_by.name} />
<Text>{term?.created_by.name}</Text>
<Text variant="secondary">
(
{formatDate({
date: selectedTerm?.created_at!,
date: term?.created_at!,
format: 'LL'
})}
)

View File

@ -4,7 +4,7 @@ import React from 'react';
import { BusterListSelectedOptionPopupContainer } from '@/components/ui/list';
import { Button } from '@/components/ui/buttons';
import { useMemoizedFn } from '@/hooks';
import { useBusterTermsIndividualContextSelector } from '@/context/Terms';
import { useDeleteTerm } from '@/api/buster_rest/terms';
export const TermListSelectedOptionPopup: React.FC<{
selectedRowKeys: string[];
@ -30,12 +30,16 @@ const DeleteButton: React.FC<{
selectedRowKeys: string[];
onSelectChange: (selectedRowKeys: string[]) => void;
}> = ({ selectedRowKeys, onSelectChange }) => {
const onDeleteTerm = useBusterTermsIndividualContextSelector((x) => x.onDeleteTerm);
const { mutateAsync: onDeleteTerm, isPending: isPendingDeleteTerm } = useDeleteTerm();
const onDeleteClick = useMemoizedFn(async () => {
await onDeleteTerm({ ids: selectedRowKeys });
onSelectChange([]);
});
return <Button onClick={onDeleteClick}>Delete</Button>;
return (
<Button onClick={onDeleteClick} loading={isPendingDeleteTerm}>
Delete
</Button>
);
};

View File

@ -7,10 +7,10 @@ import { AppTooltip } from '@/components/ui/tooltip';
import { Plus } from '@/components/ui/icons';
import { useHotkeys } from 'react-hotkeys-hook';
import { useUserConfigContextSelector } from '@/context/Users';
import { useBusterTermsIndividual } from '@/context/Terms';
import { useMemoizedFn } from '@/hooks';
import { NewTermModal } from '@/components/features/modal/NewTermModal';
import { type BreadcrumbItem, Breadcrumb } from '@/components/ui/breadcrumb';
import { useGetTerm } from '@/api/buster_rest/terms';
export const TermsHeader: React.FC<{
termId?: string;
@ -18,8 +18,7 @@ export const TermsHeader: React.FC<{
setOpenNewTermsModal?: (open: boolean) => void;
}> = React.memo(({ termId, openNewTermsModal, setOpenNewTermsModal }) => {
const isAdmin = useUserConfigContextSelector((state) => state.isAdmin);
const { term: selectedTerm } = useBusterTermsIndividual({ termId: termId || '' });
const { data: term } = useGetTerm(termId);
const onOpenNewTermsModal = useMemoizedFn(() => {
setOpenNewTermsModal?.(true);
@ -33,7 +32,7 @@ export const TermsHeader: React.FC<{
return (
<>
<div className="flex w-full items-center justify-between space-x-1">
<TermsBreadcrumb termName={selectedTerm?.name} />
<TermsBreadcrumb termName={term?.name} />
<div className="flex items-center space-x-0">
{isAdmin && (