mirror of https://github.com/buster-so/buster.git
ripped out terms provider
This commit is contained in:
parent
6f188111c6
commit
55ffb94fe2
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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[]>([]);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
|
@ -1,2 +0,0 @@
|
|||
export * from './BusterTermsIndividualProvider';
|
||||
export * from './useBusterTermsIndividual';
|
|
@ -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
|
||||
};
|
||||
};
|
|
@ -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
|
||||
};
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
import { useUpdateTerm } from '@/api/buster_rest/terms';
|
||||
|
||||
export const useBusterTermsUpdate = () => {
|
||||
const { mutate: updateTerm } = useUpdateTerm();
|
||||
|
||||
return {
|
||||
updateTerm
|
||||
};
|
||||
};
|
|
@ -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';
|
|
@ -1,9 +0,0 @@
|
|||
export * from './interfaces';
|
||||
export * from './BusterTermsProvider';
|
||||
|
||||
import {
|
||||
useBusterTermsIndividualContextSelector,
|
||||
useBusterTermsIndividual
|
||||
} from './BusterTermsIndividualProvider';
|
||||
|
||||
export { useBusterTermsIndividualContextSelector, useBusterTermsIndividual };
|
|
@ -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>;
|
||||
}
|
|
@ -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
|
||||
}
|
||||
],
|
||||
|
|
|
@ -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'
|
||||
})}
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 && (
|
||||
|
|
Loading…
Reference in New Issue