create dataset endpoints

This commit is contained in:
Nate Kelley 2025-01-14 12:53:54 -07:00
parent 3104cace3c
commit 2812df14a1
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
4 changed files with 64 additions and 163 deletions

View File

@ -78,28 +78,14 @@ export const prefetchGetDatasetMetadata = async (
export const useCreateDataset = () => { export const useCreateDataset = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const mutationFn = useMemoizedFn(() => createDataset());
const onSuccess = useMemoizedFn((newDataset: unknown) => { const onSuccess = useMemoizedFn(() => {
queryClient.setQueryData<BusterDatasetListItem[]>(['datasets', {}], (oldData) => { queryClient.invalidateQueries({ queryKey: ['datasets', {}] });
// const newListItem: BusterDatasetListItem = {
// ...newDataset,
// name: newDataset.name,
// created_at: newDataset.created_at,
// updated_at: newDataset.updated_at,
// definition: newDataset.definition,
// owner: '',
// };
return oldData;
});
});
const onError = useMemoizedFn((error: any) => {
console.error('Failed to create dataset:', error);
}); });
return useCreateReactMutation({ return useCreateReactMutation({
mutationFn, mutationFn: createDataset,
onSuccess, onSuccess
onError
}); });
}; };

View File

@ -30,8 +30,11 @@ export const getDatasetDataSample = async (datasetId: string): Promise<BusterDat
.then((res) => res.data); .then((res) => res.data);
}; };
export const createDataset = async (): Promise<BusterDataset> => { export const createDataset = async (params: {
return await mainApi.post<BusterDataset>(`/datasets`).then((res) => res.data); name: string;
data_source_id: string;
}): Promise<BusterDataset> => {
return await mainApi.post<BusterDataset>(`/datasets`, params).then((res) => res.data);
}; };
export const deleteDataset = async (datasetId: string): Promise<void> => { export const deleteDataset = async (datasetId: string): Promise<void> => {

View File

@ -1,22 +1,12 @@
import React, { useLayoutEffect, useMemo, useState } from 'react'; import React, { useLayoutEffect, useMemo, useState } from 'react';
import { Button, Select, SelectProps } from 'antd'; import { Input, Select, SelectProps } from 'antd';
import { useMemoizedFn, useMount } from 'ahooks'; import { useMemoizedFn, useMount } from 'ahooks';
import { useDatasetContextSelector } from '@/context/Datasets';
import { useDataSourceContextSelector } from '@/context/DataSources'; import { useDataSourceContextSelector } from '@/context/DataSources';
import { import { useCreateDataset } from '@/api/busterv2/datasets';
BusterDatasetListItem,
useCreateDataset,
useGetDatasets,
useUpdateDataset
} from '@/api/busterv2/datasets';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout'; import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import { BusterRoutes, createBusterRoute } from '@/routes'; import { BusterRoutes, createBusterRoute } from '@/routes';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { AppModal, Text } from '@/components'; import { AppModal, Text } from '@/components';
import { useAntToken } from '@/styles/useAntToken';
import { BusterList, BusterListColumn, BusterListRow } from '@/components/list';
import { formatDate } from '@/utils/date';
import { timeout } from '@/utils';
const headerConfig = { const headerConfig = {
title: 'Create a dataset', title: 'Create a dataset',
@ -35,21 +25,22 @@ export const NewDatasetModal: React.FC<{
const forceInitDataSourceList = useDataSourceContextSelector( const forceInitDataSourceList = useDataSourceContextSelector(
(state) => state.forceInitDataSourceList (state) => state.forceInitDataSourceList
); );
const { mutateAsync: createDataset } = useCreateDataset(); const { mutateAsync: createDataset, isPending: creatingDataset } = useCreateDataset();
const [creatingDataset, setCreatingDataset] = React.useState(false);
const [selectedDatasource, setSelectedDatasource] = React.useState<string | null>( const [selectedDatasource, setSelectedDatasource] = React.useState<string | null>(
datasourceId || null datasourceId || null
); );
const [datasetName, setDatasetName] = React.useState<string>('');
const disableSubmit = !selectedDatasource; const disableSubmit = !selectedDatasource || !datasetName;
const createNewDatasetPreflight = useMemoizedFn(async () => { const createNewDatasetPreflight = useMemoizedFn(async () => {
if (creatingDataset || disableSubmit) return; if (creatingDataset || disableSubmit || !selectedDatasource) return;
setCreatingDataset(true);
beforeCreate?.(); beforeCreate?.();
const res = await createDataset({ const res = await createDataset({
data_source_id: selectedDatasource! data_source_id: selectedDatasource,
name: datasetName
}); });
if (res.id) { if (res.id) {
onChangePage({ onChangePage({
@ -62,16 +53,13 @@ export const NewDatasetModal: React.FC<{
afterCreate?.(); afterCreate?.();
}, 150); }, 150);
} }
setTimeout(() => {
setCreatingDataset(false);
}, 500);
}); });
const onAddDataSourceClick = useMemoizedFn(() => { const onAddDataSourceClick = useMemoizedFn(() => {
onClose();
setTimeout(() => {
router.push(createBusterRoute({ route: BusterRoutes.SETTINGS_DATASOURCES_ADD })); router.push(createBusterRoute({ route: BusterRoutes.SETTINGS_DATASOURCES_ADD }));
}, 350); setTimeout(() => {
onClose();
}, 450);
}); });
useLayoutEffect(() => { useLayoutEffect(() => {
@ -98,14 +86,18 @@ export const NewDatasetModal: React.FC<{
return ( return (
<AppModal open={open} onClose={onClose} header={headerConfig} footer={footerConfig}> <AppModal open={open} onClose={onClose} header={headerConfig} footer={footerConfig}>
{open && ( {open && (
<div className="mt-2 flex flex-col gap-3">
<FormWrapper title="Dataset name">
<DatasetNameInput setDatasetName={setDatasetName} datasetName={datasetName} />
</FormWrapper>
<FormWrapper title="Datasource">
<SelectDataSourceDropdown <SelectDataSourceDropdown
setSelectedDatasource={setSelectedDatasource} setSelectedDatasource={setSelectedDatasource}
selectedDatasource={selectedDatasource} selectedDatasource={selectedDatasource}
/> />
)} </FormWrapper>
</div>
{open && selectedDatasource && (
<SelectFromExistingDataset selectedDatasource={selectedDatasource} />
)} )}
</AppModal> </AppModal>
); );
@ -132,6 +124,10 @@ const SelectDataSourceDropdown: React.FC<{
return selectOptions.find((option) => option.value === selectedDatasource); return selectOptions.find((option) => option.value === selectedDatasource);
}, [selectOptions, selectedDatasource]); }, [selectOptions, selectedDatasource]);
const onSelect = useMemoizedFn((value: unknown) => {
setSelectedDatasource(value as string);
});
useMount(() => { useMount(() => {
initDataSourceList(); initDataSourceList();
router.prefetch( router.prefetch(
@ -149,115 +145,41 @@ const SelectDataSourceDropdown: React.FC<{
value={selectedOption} value={selectedOption}
placeholder="Select datasources that this term pertains to" placeholder="Select datasources that this term pertains to"
popupMatchSelectWidth={true} popupMatchSelectWidth={true}
autoFocus={true} onChange={onSelect}
onChange={(value) => {
setSelectedDatasource(value as unknown as string);
}}
/> />
); );
}); });
SelectDataSourceDropdown.displayName = 'SelectDataSourceDropdown'; SelectDataSourceDropdown.displayName = 'SelectDataSourceDropdown';
const SelectFromExistingDataset: React.FC<{ const DatasetNameInput: React.FC<{
selectedDatasource: string; setDatasetName: (name: string) => void;
}> = React.memo(({ selectedDatasource }) => { datasetName: string;
const token = useAntToken(); }> = React.memo(
const { data: importedDatasets, isFetched: isFetchedDatasets } = useGetDatasets({ ({ setDatasetName, datasetName }) => {
imported: true
});
const { mutateAsync: onUpdateDataset } = useUpdateDataset();
const onChangePage = useAppLayoutContextSelector((s) => s.onChangePage);
const [submittingId, setSubmittingId] = useState<string | null>(null);
const columns: BusterListColumn[] = useMemo(() => {
return [
{
title: 'Name',
dataIndex: 'name'
},
{
title: 'Updated at',
dataIndex: 'updated_at',
render: (v) => formatDate({ date: v, format: 'lll' }),
width: 130
},
{
title: 'Actions',
dataIndex: 'actions',
width: 100,
render: (_, record: BusterDatasetListItem) => (
<div className="flex items-center justify-end">
<Button
loading={submittingId === record.id}
type="default"
onClick={() => onSelectDataset(record.id)}>
Use dataset
</Button>
</div>
)
}
];
}, [submittingId]);
const rows: BusterListRow[] = useMemo(() => {
return importedDatasets.map((dataset) => ({
id: dataset.id,
data: dataset
}));
}, [importedDatasets]);
const onSelectDataset = useMemoizedFn(async (datasetId: string) => {
setSubmittingId(datasetId);
// try {
// await onUpdateDataset({
// id: datasetId,
// name: 'test'
// });
// await timeout(500);
// onChangePage({
// route: BusterRoutes.APP_DATASETS_ID,
// datasetId
// });
// } catch (error) {
// setSubmittingId(null);
// }
});
return ( return (
<div <Input
className="mt-3 flex h-[250px] w-full flex-col" autoFocus
style={{ defaultValue={datasetName}
border: `0.5px solid ${token.colorBorder}`, placeholder="Enter a name for your dataset"
borderRadius: token.borderRadius onChange={(e) => setDatasetName(e.target.value)}
}}>
<div
className="flex"
style={{
padding: 12,
background: token.controlItemBgActive,
borderBottom: `0.5px solid ${token.colorBorder}`
}}>
<Text size="sm">Use an existing table or view as a dataset</Text>
</div>
<div className="h-full w-full">
<BusterList
columns={columns}
rows={rows}
showHeader={false}
emptyState={
!isFetchedDatasets ? (
<div className="flex h-full w-full items-center justify-center">
<Text>No datasets found</Text>
</div>
) : (
<></>
)
}
/> />
);
},
() => true
);
DatasetNameInput.displayName = 'DatasetNameInput';
const FormWrapper: React.FC<{
title: string;
children: React.ReactNode;
}> = React.memo(({ title, children }) => {
return (
<div className="grid grid-cols-[minmax(150px,auto)_1fr] gap-4">
<div>
<Text>{title}</Text>
</div> </div>
<div>{children}</div>
</div> </div>
); );
}); });
SelectFromExistingDataset.displayName = 'SelectFromExistingDataset'; FormWrapper.displayName = 'FormWrapper';

View File

@ -125,9 +125,7 @@ export const AppCodeEditor = forwardRef<AppCodeEditorHandle, AppCodeEditorProps>
]); ]);
if (language === 'yaml') { if (language === 'yaml') {
console.log('loading yaml');
await configureMonacoToUseYaml(monaco); await configureMonacoToUseYaml(monaco);
console.log('yaml loaded');
} }
monaco.editor.defineTheme('github-light', GithubLightTheme); monaco.editor.defineTheme('github-light', GithubLightTheme);
@ -149,14 +147,6 @@ export const AppCodeEditor = forwardRef<AppCodeEditorHandle, AppCodeEditorProps>
setIsLoading(false); setIsLoading(false);
hasLoadedDynamicEditor = true; hasLoadedDynamicEditor = true;
// Add this to see how Monaco is tokenizing the text
editor.onDidChangeModelContent(() => {
const model = editor.getModel();
if (model) {
console.log(monaco.editor.tokenize(model.getValue(), 'yaml'));
}
});
} }
); );