add loading indicator for fetching temp data

This commit is contained in:
Nate Kelley 2025-01-10 11:03:14 -07:00
parent adc0e4e57e
commit ab3d205f6d
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
8 changed files with 74 additions and 28 deletions

View File

@ -54,11 +54,12 @@ export const useGetDatasetData = (datasetId: string) => {
export const useGetDatasetMetadata = (datasetId: string) => {
const queryFn = useMemoizedFn(() => getDatasetMetadata(datasetId));
return useCreateReactQuery<BusterDataset>({
const res = useCreateReactQuery<BusterDataset>({
queryKey: ['datasetMetadata', datasetId],
queryFn,
enabled: !!datasetId
});
return res;
};
export const prefetchGetDatasetMetadata = async (

View File

@ -1,4 +1,4 @@
import { DataSource } from '../datasources';
import { DataSource, DataSourceTypes } from '../datasources';
export interface BusterDatasetListItem {
id: string;
@ -28,6 +28,9 @@ export type BusterDataset = {
name: string;
sql: string;
yml_file: string;
data_source_id: string;
data_source_name: string;
data_source_type: DataSourceTypes;
};
export interface BusterDatasetColumn {

View File

@ -2,7 +2,7 @@
import { useIndividualDataset } from '@/context/Datasets';
import { useSelectedLayoutSegment } from 'next/navigation';
import React, { PropsWithChildren, useEffect, useLayoutEffect, useState } from 'react';
import React, { PropsWithChildren, useEffect, useState } from 'react';
import { DatasetApps } from './_config';
import {
createContext,
@ -13,8 +13,8 @@ import {
export const useDatasetPageContext = ({ datasetId }: { datasetId: string }) => {
const segments = useSelectedLayoutSegment() as DatasetApps;
const { dataset, datasetData } = useIndividualDataset({ datasetId });
const originalDatasetSQL = dataset.data?.sql;
const datasetYmlFile = dataset.data?.yml_file;
const originalDatasetSQL = dataset?.data?.sql;
const datasetYmlFile = dataset?.data?.yml_file;
const [sql, setSQL] = useState<string>(originalDatasetSQL || '');
const [ymlFile, setYmlFile] = useState<string>(datasetYmlFile || '');

View File

@ -3,6 +3,7 @@ import { createStyles } from 'antd-style';
import React from 'react';
import isEmpty from 'lodash/isEmpty';
import AppDataGrid from '@/components/table/AppDataGrid';
import { IndeterminateLinearLoader } from '@/components/loaders';
export const DataContainer: React.FC<{
data: BusterDatasetData;
@ -13,7 +14,14 @@ export const DataContainer: React.FC<{
const hasData = !isEmpty(data);
return (
<div className={cx(styles.container, 'h-full w-full overflow-hidden', className)}>
<div className={cx(styles.container, 'relative h-full w-full overflow-hidden', className)}>
<IndeterminateLinearLoader
className={cx(
'absolute left-0 top-0 z-10 w-full',
fetchingData && hasData ? 'block' : '!hidden'
)}
/>
{hasData ? (
<AppDataGrid rows={data} />
) : (

View File

@ -1,6 +1,6 @@
'use client';
import React, { useRef, useState } from 'react';
import React, { useMemo, useRef, useState } from 'react';
import { useDatasetPageContextSelector } from '../_DatasetPageContext';
import { AppSplitter, AppSplitterRef } from '@/components';
import { SQLContainer } from './SQLContainer';
@ -12,6 +12,9 @@ import { EditorApps, EditorContainerSubHeader } from './EditorContainerSubHeader
import { createStyles } from 'antd-style';
import { MetadataContainer } from './MetadataContainer';
import { runSQL } from '@/api/busterv2';
import { RustApiError } from '@/api/buster/errors';
import isEmpty from 'lodash/isEmpty';
export const EditorContent: React.FC<{
defaultLayout: [string, string];
}> = ({ defaultLayout }) => {
@ -20,35 +23,51 @@ export const EditorContent: React.FC<{
const splitterRef = useRef<AppSplitterRef>(null);
const [selectedApp, setSelectedApp] = useState<EditorApps>(EditorApps.PREVIEW);
const datasetData = useDatasetPageContextSelector((state) => state.datasetData);
const { data: dataset } = useDatasetPageContextSelector((state) => state.dataset);
const sql = useDatasetPageContextSelector((state) => state.sql);
const setSQL = useDatasetPageContextSelector((state) => state.setSQL);
const ymlFile = useDatasetPageContextSelector((state) => state.ymlFile);
const setYmlFile = useDatasetPageContextSelector((state) => state.setYmlFile);
const [tempData, setTempData] = useState<BusterDatasetData>(datasetData.data || []);
const [fetchingTempData, setFetchingTempData] = useState(false);
const [runSQLError, setRunSQLError] = useState<string>('');
const { runAsync: runQuery } = useRequest(
const shownData = useMemo(() => {
return isEmpty(tempData) ? datasetData.data || [] : tempData;
}, [tempData, datasetData.data]);
const { runAsync: runQuery, loading: fetchingTempData } = useRequest(
async () => {
await timeout(1000);
const res = await runSQL({ data_source_id: '123', sql });
console.log(res);
try {
console.log('dataset', sql);
const res = await runSQL({ data_source_id: dataset?.data_source_id!, sql });
const data = res.data.data;
setTempData(data);
return data;
} catch (error) {
setRunSQLError((error as unknown as RustApiError)?.message || 'Something went wrong');
}
},
{ manual: true }
);
const fetchingData = fetchingTempData || datasetData.isFetching;
const error = '';
const fetchingInitialData = datasetData.isFetching;
const onRunQuery = useMemoizedFn(async () => {
await runQuery();
const heightOfRow = 36;
const heightOfDataContainer = heightOfRow * (datasetData.data?.length || 0);
const containerHeight = ref.current?.clientHeight || 0;
const maxHeight = Math.floor(containerHeight * 0.6);
const finalHeight = Math.min(heightOfDataContainer, maxHeight);
splitterRef.current?.setSplitSizes(['auto', `${finalHeight}px`]);
try {
const result = await runQuery();
if (result && result.length > 0) {
const headerHeight = 50;
const heightOfRow = 36;
const heightOfDataContainer = headerHeight + heightOfRow * (result.length || 0);
const containerHeight = ref.current?.clientHeight || 0;
const maxHeight = Math.floor(containerHeight * 0.6);
const finalHeight = Math.min(heightOfDataContainer, maxHeight);
splitterRef.current?.setSplitSizes(['auto', `${finalHeight}px`]);
}
} catch (error) {
//
}
});
return (
@ -63,15 +82,15 @@ export const EditorContent: React.FC<{
className="mb-3"
datasetSQL={sql}
setDatasetSQL={setSQL}
error={error}
error={runSQLError}
onRunQuery={onRunQuery}
/>
}
rightChildren={
<DataContainer
className="mt-3"
data={datasetData.data!}
fetchingData={fetchingData}
data={shownData}
fetchingData={fetchingInitialData || fetchingTempData}
/>
}
split="horizontal"

View File

@ -36,7 +36,12 @@ export const SQLContainer: React.FC<{
return (
<div className={cx(styles.container, 'flex h-full w-full flex-col overflow-hidden', className)}>
<AppCodeEditor className="overflow-hidden" value={datasetSQL} onChange={setDatasetSQL} />
<AppCodeEditor
className="overflow-hidden"
value={datasetSQL}
onChange={setDatasetSQL}
onMetaEnter={onRunQueryPreflight}
/>
<Divider className="!my-0" />
<div className="relative flex items-center justify-between px-4 py-2.5">
<Button type="default" onClick={onCopySQL}>

View File

@ -1,3 +1,4 @@
import { createStyles } from 'antd-style';
import React from 'react';
export const IndeterminateLinearLoader: React.FC<{
@ -7,15 +8,24 @@ export const IndeterminateLinearLoader: React.FC<{
trackColor?: string;
valueColor?: string;
}> = ({ className = '', trackColor, valueColor, style, height = 2 }) => {
const { styles, cx } = useStyles();
return (
<div
className={`indeterminate-progress-bar ${className}`}
style={{ ...style, height, backgroundColor: trackColor }}>
<div
className="indeterminate-progress-bar-value bg-buster-purple"
className={cx(styles.track, 'indeterminate-progress-bar-value')}
style={{
backgroundColor: valueColor
}}></div>
</div>
);
};
const useStyles = createStyles(({ css, token }) => ({
track: css`
background: ${token.colorPrimary};
opacity: 0.6;
`
}));

View File

@ -35,7 +35,7 @@ export const createBusterRoute = ({ route, ...args }: BusterRoutesWithArgsRoute)
return Object.entries(args).reduce<string>((acc, [key, value]) => {
acc.replace(`[${key}]`, value as string);
return acc.replace(`:${key}`, value as string);
}, route);
}, route || '');
};
const routeToRegex = (route: string): RegExp => {