Merge branch 'staging' into wells-bus-1965-update-frontend-to-use-password-for-public-access-when-it-is

This commit is contained in:
Nate Kelley 2025-09-26 13:20:41 -06:00
commit e36ad3f538
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
10 changed files with 105 additions and 38 deletions

View File

@ -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
}); });

View File

@ -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'));

View File

@ -59,6 +59,20 @@ export const ShareMenuContentEmbedFooter = ({
openSuccessMessage('Succuessfully published'); openSuccessMessage('Succuessfully published');
}; };
const text = useMemo(() => {
if (assetType === 'metric_file') {
return 'Your metric currently isnt published.';
} else if (assetType === 'dashboard_file') {
return 'Your dashboard currently isnt published.';
} else if (assetType === 'chat') {
return 'Your chat currently isnt published.';
} else if (assetType === 'report_file') {
return 'Your report currently isnt published.';
} else {
return 'Your item currently isnt 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!">

View File

@ -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') {

View File

@ -44,7 +44,7 @@ export const DashboardEditTitles: React.FC<{
useEffect(() => { useEffect(() => {
if (debouncedWidth) { if (debouncedWidth) {
inputTextAreaRef.current?.forceRecalculateHeight(); inputTextAreaRef.current?.forceRecalculateHeight?.();
} }
}, [debouncedWidth]); }, [debouncedWidth]);

View File

@ -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>

View File

@ -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);
}, },
}), }),
{ {

View File

@ -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',

View File

@ -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;
}, },

View File

@ -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.