mirror of https://github.com/buster-so/buster.git
Merge branch 'staging' into wells-bus-1965-update-frontend-to-use-password-for-public-access-when-it-is
This commit is contained in:
commit
e36ad3f538
|
@ -3,7 +3,7 @@ import { getHealthcheck } from './requests';
|
||||||
|
|
||||||
export const useHealthcheck = () => {
|
export const useHealthcheck = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['healthcheck'],
|
queryKey: ['healthcheck'] as const,
|
||||||
queryFn: getHealthcheck,
|
queryFn: getHealthcheck,
|
||||||
refetchInterval: 1000 * 30, // 30 seconds
|
refetchInterval: 1000 * 30, // 30 seconds
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,6 @@ import type { AxiosRequestHeaders } from 'axios';
|
||||||
import axios, { type AxiosError, type InternalAxiosRequestConfig } from 'axios';
|
import axios, { type AxiosError, type InternalAxiosRequestConfig } from 'axios';
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import { getSupabaseSession } from '@/integrations/supabase/getSupabaseUserClient';
|
import { getSupabaseSession } from '@/integrations/supabase/getSupabaseUserClient';
|
||||||
import { Route as AuthRoute } from '@/routes/auth.login';
|
|
||||||
import { BASE_URL_V2 } from './config';
|
import { BASE_URL_V2 } from './config';
|
||||||
import { rustErrorHandler } from './errors';
|
import { rustErrorHandler } from './errors';
|
||||||
|
|
||||||
|
@ -50,7 +49,7 @@ export const defaultAxiosRequestHandler = async (config: InternalAxiosRequestCon
|
||||||
const { accessToken: token } = session;
|
const { accessToken: token } = session;
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
console.warn('No token found', config);
|
console.warn('No token found', config.url, session);
|
||||||
//embed route were having an issue with this...
|
//embed route were having an issue with this...
|
||||||
//window.location.href = AuthRoute.to;
|
//window.location.href = AuthRoute.to;
|
||||||
//return Promise.reject(new Error('No token found'));
|
//return Promise.reject(new Error('No token found'));
|
||||||
|
|
|
@ -59,6 +59,20 @@ export const ShareMenuContentEmbedFooter = ({
|
||||||
openSuccessMessage('Succuessfully published');
|
openSuccessMessage('Succuessfully published');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const text = useMemo(() => {
|
||||||
|
if (assetType === 'metric_file') {
|
||||||
|
return 'Your metric currently isn’t published.';
|
||||||
|
} else if (assetType === 'dashboard_file') {
|
||||||
|
return 'Your dashboard currently isn’t published.';
|
||||||
|
} else if (assetType === 'chat') {
|
||||||
|
return 'Your chat currently isn’t published.';
|
||||||
|
} else if (assetType === 'report_file') {
|
||||||
|
return 'Your report currently isn’t published.';
|
||||||
|
} else {
|
||||||
|
return 'Your item currently isn’t published.';
|
||||||
|
}
|
||||||
|
}, [assetType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-item-hover flex justify-start overflow-hidden rounded-b px-3 py-2.5">
|
<div className="bg-item-hover flex justify-start overflow-hidden rounded-b px-3 py-2.5">
|
||||||
<Text variant="secondary" className="text-xs!">
|
<Text variant="secondary" className="text-xs!">
|
||||||
|
|
|
@ -29,7 +29,7 @@ export interface InputTextAreaProps
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InputTextAreaRef extends HTMLTextAreaElement {
|
export interface InputTextAreaRef extends HTMLTextAreaElement {
|
||||||
forceRecalculateHeight: () => void;
|
forceRecalculateHeight?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InputTextArea = React.forwardRef<InputTextAreaRef, InputTextAreaProps>(
|
export const InputTextArea = React.forwardRef<InputTextAreaRef, InputTextAreaProps>(
|
||||||
|
@ -48,9 +48,11 @@ export const InputTextArea = React.forwardRef<InputTextAreaRef, InputTextAreaPro
|
||||||
) => {
|
) => {
|
||||||
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
ref,
|
ref,
|
||||||
() => {
|
() =>
|
||||||
|
{
|
||||||
if (!textareaRef.current) {
|
if (!textareaRef.current) {
|
||||||
return null as unknown as InputTextAreaRef;
|
return null as unknown as InputTextAreaRef;
|
||||||
}
|
}
|
||||||
|
@ -63,9 +65,28 @@ export const InputTextArea = React.forwardRef<InputTextAreaRef, InputTextAreaPro
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
,
|
||||||
[]
|
[]
|
||||||
);
|
)
|
||||||
|
=======
|
||||||
|
useImperativeHandle(ref, () =>
|
||||||
|
{
|
||||||
|
if (!textareaRef.current) {
|
||||||
|
return null as unknown as InputTextAreaRef;
|
||||||
|
}
|
||||||
|
return Object.assign(textareaRef.current, {
|
||||||
|
forceRecalculateHeight: () => {
|
||||||
|
if (textareaRef.current) {
|
||||||
|
// Force a recalculation by triggering an input event
|
||||||
|
const event = new Event('input', { bubbles: true });
|
||||||
|
textareaRef.current.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, [])
|
||||||
|
>>>>>>> staging
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
|
|
|
@ -44,7 +44,7 @@ export const DashboardEditTitles: React.FC<{
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debouncedWidth) {
|
if (debouncedWidth) {
|
||||||
inputTextAreaRef.current?.forceRecalculateHeight();
|
inputTextAreaRef.current?.forceRecalculateHeight?.();
|
||||||
}
|
}
|
||||||
}, [debouncedWidth]);
|
}, [debouncedWidth]);
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from '@/components/ui/card/CardBase';
|
} from '@/components/ui/card/CardBase';
|
||||||
|
@ -61,7 +60,7 @@ function RouteComponent() {
|
||||||
|
|
||||||
function LoadingState() {
|
function LoadingState() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-white flex items-center justify-center">
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
<Card className="text-center max-w-md mx-4">
|
<Card className="text-center max-w-md mx-4">
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6">
|
||||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-400 mx-auto mb-4"></div>
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-400 mx-auto mb-4"></div>
|
||||||
|
@ -76,7 +75,7 @@ function LoadingState() {
|
||||||
|
|
||||||
function ErrorState({ error }: { error: Error | RustApiError }) {
|
function ErrorState({ error }: { error: Error | RustApiError }) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-white flex items-center justify-center">
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
<Card className="max-w-md w-full mx-4">
|
<Card className="max-w-md w-full mx-4">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
@ -206,8 +205,8 @@ function HealthcheckDashboard({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-white p-6 flex flex-col gap-4">
|
<div className="min-h-screen p-6 flex flex-col gap-4 items-center">
|
||||||
<div className="max-w-6xl mx-auto flex flex-col gap-4">
|
<div className="w-full max-w-5xl flex flex-col gap-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-4 flex flex-col gap-1">
|
<div className="mb-4 flex flex-col gap-1">
|
||||||
<Text size="4xl" className="font-bold">
|
<Text size="4xl" className="font-bold">
|
||||||
|
@ -274,10 +273,16 @@ function HealthcheckDashboard({
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="bg-gray-50 rounded-md p-4">
|
<div className="bg-gray-50 rounded-md p-4 h-full">
|
||||||
|
{user ? (
|
||||||
<pre className="text-sm text-gray-800 whitespace-pre-wrap overflow-auto">
|
<pre className="text-sm text-gray-800 whitespace-pre-wrap overflow-auto">
|
||||||
{JSON.stringify(user, null, 2)}
|
{JSON.stringify(user, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
|
) : (
|
||||||
|
<Text variant="secondary" className="h-full" size="sm">
|
||||||
|
No user data available
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -473,6 +478,11 @@ function HealthcheckDashboard({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
|
{check.responseTime && (
|
||||||
|
<Text variant="tertiary" size="sm" className="mt-2 mr-3">
|
||||||
|
{check.responseTime}ms
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium capitalize ${getStatusColor(
|
className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium capitalize ${getStatusColor(
|
||||||
check.status
|
check.status
|
||||||
|
@ -480,11 +490,6 @@ function HealthcheckDashboard({
|
||||||
>
|
>
|
||||||
{check.status}
|
{check.status}
|
||||||
</span>
|
</span>
|
||||||
{check.responseTime && (
|
|
||||||
<Text variant="tertiary" size="sm" className="mt-2 ml-3">
|
|
||||||
{check.responseTime}ms
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -494,9 +499,9 @@ function HealthcheckDashboard({
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="mt-12 text-center">
|
<div className="mt-4 text-center">
|
||||||
<Text variant="tertiary" size="sm">
|
<Text variant="tertiary" size="sm">
|
||||||
System health is monitored continuously. Data refreshes automatically.
|
System health is monitored continuously. Data refreshes about every 30 seconds.
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { PermissionedDataset } from '@buster/access-controls';
|
import type { PermissionedDataset } from '@buster/access-controls';
|
||||||
|
import { waitForPendingUpdates } from '@buster/database/queries';
|
||||||
import { type ModelMessage, hasToolCall, stepCountIs, streamText } from 'ai';
|
import { type ModelMessage, hasToolCall, stepCountIs, streamText } from 'ai';
|
||||||
import { wrapTraced } from 'braintrust';
|
import { wrapTraced } from 'braintrust';
|
||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
|
@ -199,8 +200,10 @@ export function createAnalystAgent(analystAgentOptions: AnalystAgentOptions) {
|
||||||
hasToolResults: !!event.toolResults,
|
hasToolResults: !!event.toolResults,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onFinish: () => {
|
onFinish: async () => {
|
||||||
console.info('Analyst Agent finished');
|
console.info('Analyst Agent finished');
|
||||||
|
// Ensure all pending database updates complete before stream terminates
|
||||||
|
await waitForPendingUpdates(analystAgentOptions.messageId);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { PermissionedDataset } from '@buster/access-controls';
|
import type { PermissionedDataset } from '@buster/access-controls';
|
||||||
|
import { waitForPendingUpdates } from '@buster/database/queries';
|
||||||
import { type ModelMessage, hasToolCall, stepCountIs, streamText } from 'ai';
|
import { type ModelMessage, hasToolCall, stepCountIs, streamText } from 'ai';
|
||||||
import { wrapTraced } from 'braintrust';
|
import { wrapTraced } from 'braintrust';
|
||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
|
@ -230,6 +231,11 @@ export function createThinkAndPrepAgent(thinkAndPrepAgentSchema: ThinkAndPrepAge
|
||||||
hasToolResults: !!event.toolResults,
|
hasToolResults: !!event.toolResults,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onFinish: async () => {
|
||||||
|
console.info('Think and Prep Agent finished');
|
||||||
|
// Ensure all pending database updates complete before stream terminates
|
||||||
|
await waitForPendingUpdates(messageId);
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'Think and Prep Agent',
|
name: 'Think and Prep Agent',
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
import { updateMessage, updateMessageEntries } from '@buster/database/queries';
|
import {
|
||||||
|
updateMessage,
|
||||||
|
updateMessageEntries,
|
||||||
|
waitForPendingUpdates,
|
||||||
|
} from '@buster/database/queries';
|
||||||
import { wrapTraced } from 'braintrust';
|
import { wrapTraced } from 'braintrust';
|
||||||
import { cleanupState } from '../../shared/cleanup-state';
|
import { cleanupState } from '../../shared/cleanup-state';
|
||||||
import { createRawToolResultEntry } from '../../shared/create-raw-llm-tool-result-entry';
|
import { createRawToolResultEntry } from '../../shared/create-raw-llm-tool-result-entry';
|
||||||
|
@ -57,6 +61,10 @@ export function createDoneToolExecute(context: DoneToolContext, state: DoneToolS
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await processDone(state, state.toolCallId, context.messageId, context);
|
const result = await processDone(state, state.toolCallId, context.messageId, context);
|
||||||
|
|
||||||
|
// Wait for all pending updates from delta/finish to complete before returning
|
||||||
|
await waitForPendingUpdates(context.messageId);
|
||||||
|
|
||||||
cleanupState(state);
|
cleanupState(state);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,6 +24,17 @@ export type UpdateMessageEntriesParams = z.infer<typeof UpdateMessageEntriesSche
|
||||||
// Simple in-memory queue for each messageId
|
// Simple in-memory queue for each messageId
|
||||||
const updateQueues = new Map<string, Promise<{ success: boolean }>>();
|
const updateQueues = new Map<string, Promise<{ success: boolean }>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for all pending updates for a given messageId to complete.
|
||||||
|
* This ensures all queued updates are flushed to the database before proceeding.
|
||||||
|
*/
|
||||||
|
export async function waitForPendingUpdates(messageId: string): Promise<void> {
|
||||||
|
const pendingQueue = updateQueues.get(messageId);
|
||||||
|
if (pendingQueue) {
|
||||||
|
await pendingQueue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal function that performs the actual update logic.
|
* Internal function that performs the actual update logic.
|
||||||
* This is separated so it can be queued.
|
* This is separated so it can be queued.
|
||||||
|
|
Loading…
Reference in New Issue