From f7f74d9e83d0d28227ac70650f0e91ea8dc8ddf9 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Mon, 21 Apr 2025 15:01:17 -0600 Subject: [PATCH 1/4] 402 redirect page --- .../api/buster_rest/users/queryRequests.ts | 2 +- web/src/app/app/layout.tsx | 12 ++++- web/src/app/info/getting-started/page.tsx | 50 +++++++++++++++++++ .../routes/busterRoutes/busterInfoRoutes.ts | 7 +++ web/src/routes/busterRoutes/busterRoutes.ts | 6 ++- web/src/routes/externalRoutes.ts | 1 + 6 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 web/src/app/info/getting-started/page.tsx create mode 100644 web/src/routes/busterRoutes/busterInfoRoutes.ts diff --git a/web/src/api/buster_rest/users/queryRequests.ts b/web/src/api/buster_rest/users/queryRequests.ts index 623e5694b..f218286b6 100644 --- a/web/src/api/buster_rest/users/queryRequests.ts +++ b/web/src/api/buster_rest/users/queryRequests.ts @@ -39,7 +39,7 @@ export const prefetchGetMyUserInfo = async ( queryFn: () => initialData!, initialData }); - return { queryClient, initialData }; + return queryClient; }; export const useGetUser = (params: Parameters[0]) => { diff --git a/web/src/app/app/layout.tsx b/web/src/app/app/layout.tsx index 322259af0..9e7ce7b94 100644 --- a/web/src/app/app/layout.tsx +++ b/web/src/app/app/layout.tsx @@ -8,6 +8,7 @@ import { prefetchGetMyUserInfo } from '@/api/buster_rest'; import { getSupabaseUserContext } from '@/lib/supabase'; import { AppProviders } from '@/context/AppProviders'; import { headers } from 'next/headers'; +import { queryKeys } from '@/api/query_keys'; export default async function Layout({ children @@ -18,10 +19,19 @@ export default async function Layout({ const pathname = headersList.get('x-pathname'); const supabaseContext = await getSupabaseUserContext(); const { accessToken } = supabaseContext; - const { initialData: userInfo, queryClient } = await prefetchGetMyUserInfo({ + const queryClient = await prefetchGetMyUserInfo({ jwtToken: accessToken }); + const userInfoState = queryClient.getQueryState(queryKeys.userGetUserMyself.queryKey); + + const is402Error = userInfoState?.status === 'error' && userInfoState?.error?.status === 402; + + if (is402Error) { + return ; + } + + const userInfo = queryClient.getQueryData(queryKeys.userGetUserMyself.queryKey); const newUserRoute = createBusterRoute({ route: BusterRoutes.NEW_USER }); if ( diff --git a/web/src/app/info/getting-started/page.tsx b/web/src/app/info/getting-started/page.tsx new file mode 100644 index 000000000..2029782ad --- /dev/null +++ b/web/src/app/info/getting-started/page.tsx @@ -0,0 +1,50 @@ +import { Paragraph, Text, Title } from '@/components/ui/typography'; +import { Button } from '@/components/ui/buttons'; +import { Card, CardContent, CardFooter } from '@/components/ui/card/CardBase'; +import { BUSTER_GETTING_STARTED_URL } from '@/routes/externalRoutes'; +import { BusterLogo } from '@/assets/svg/BusterLogo'; +import { ArrowUpRight } from '@/components/ui/icons'; + +export default function GettingStartedPage() { + return ( +
+ + + <> +
+ +
+ + + Welcome to Buster! + + + It looks like your organization hasn't been fully set up yet. To get started with + Buster, you'll need to complete the organization setup process or verify your + payment information. + + +
+ + + + + +
+
+ ); +} diff --git a/web/src/routes/busterRoutes/busterInfoRoutes.ts b/web/src/routes/busterRoutes/busterInfoRoutes.ts new file mode 100644 index 000000000..3509aaa56 --- /dev/null +++ b/web/src/routes/busterRoutes/busterInfoRoutes.ts @@ -0,0 +1,7 @@ +export enum BusterInfoRoutes { + INFO_GETTING_STARTED = '/info/getting-started' +} + +export type BusterInfoRoutesWithArgs = { + [BusterInfoRoutes.INFO_GETTING_STARTED]: { route: BusterInfoRoutes.INFO_GETTING_STARTED }; +}; diff --git a/web/src/routes/busterRoutes/busterRoutes.ts b/web/src/routes/busterRoutes/busterRoutes.ts index 1ccb050a9..872efbd4d 100644 --- a/web/src/routes/busterRoutes/busterRoutes.ts +++ b/web/src/routes/busterRoutes/busterRoutes.ts @@ -1,6 +1,7 @@ import { BusterAppRoutes, BusterAppRoutesWithArgs } from './busterAppRoutes'; import { BusterAuthRoutes, BusterAuthRoutesWithArgs } from './busterAuthRoutes'; import { BusterEmbedRoutes, BusterEmbedRoutesWithArgs } from './busterEmbedRoutes'; +import { BusterInfoRoutes, BusterInfoRoutesWithArgs } from './busterInfoRoutes'; import { BusterSettingsRoutes, BusterSettingsRoutesWithArgs } from './busterSettingsRoutes'; export enum BusterRootRoutes { @@ -16,6 +17,7 @@ export const BusterRoutes = { ...BusterAuthRoutes, ...BusterSettingsRoutes, ...BusterEmbedRoutes, + ...BusterInfoRoutes, ...BusterRootRoutes }; @@ -24,12 +26,14 @@ export type BusterRoutes = | BusterAuthRoutes | BusterSettingsRoutes | BusterEmbedRoutes + | BusterInfoRoutes | BusterRootRoutes; export type BusterRoutesWithArgs = BusterRootRoutesWithArgs & BusterAuthRoutesWithArgs & BusterAppRoutesWithArgs & BusterEmbedRoutesWithArgs & - BusterSettingsRoutesWithArgs; + BusterSettingsRoutesWithArgs & + BusterInfoRoutesWithArgs; export type BusterRoutesWithArgsRoute = BusterRoutesWithArgs[BusterRoutes]; diff --git a/web/src/routes/externalRoutes.ts b/web/src/routes/externalRoutes.ts index 2514f5cbc..ccd743274 100644 --- a/web/src/routes/externalRoutes.ts +++ b/web/src/routes/externalRoutes.ts @@ -1,2 +1,3 @@ export const BUSTER_HOME_PAGE = 'https://buster.so'; export const BUSTER_DOCS_URL = 'https://docs.buster.so'; +export const BUSTER_GETTING_STARTED_URL = 'https://www.buster.so/get-started'; From 462c2b745e08592a07e63d92708a38ccc0be2329 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Mon, 21 Apr 2025 15:14:08 -0600 Subject: [PATCH 2/4] fix logic for showing the bar --- .../DashboardEditTitle.tsx | 4 +-- .../ReasoningController.tsx | 11 +++++-- .../ReasoningBlackBoxMessage.tsx | 33 +++++++++---------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardEditTitle.tsx b/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardEditTitle.tsx index a1710cc32..6ed82e469 100644 --- a/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardEditTitle.tsx +++ b/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardEditTitle.tsx @@ -1,4 +1,4 @@ -import { useDebounceFn, useMemoizedFn } from '@/hooks'; +import { useMemoizedFn } from '@/hooks'; import { EditableTitle } from '@/components/ui/typography/EditableTitle'; import React from 'react'; import { useUpdateDashboard } from '@/api/buster_rest/dashboards'; @@ -50,7 +50,7 @@ export const DashboardEditTitles: React.FC<{ className={'py-0! pl-0!'} readOnly={readOnly} onChange={onChangeDashboardDescription} - defaultValue={description} + value={description} autoResize={descriptionAutoResize} placeholder="Add description..." /> diff --git a/web/src/controllers/ReasoningController/ReasoningController.tsx b/web/src/controllers/ReasoningController/ReasoningController.tsx index b6a324802..007558db6 100644 --- a/web/src/controllers/ReasoningController/ReasoningController.tsx +++ b/web/src/controllers/ReasoningController/ReasoningController.tsx @@ -9,6 +9,8 @@ import { ScrollArea } from '@/components/ui/scroll-area'; import { useAutoScroll } from '@/hooks/useAutoScroll'; import isEmpty from 'lodash/isEmpty'; import { ReasoningScrollToBottom } from './ReasoningScrollToBottom'; +import { useQuery } from '@tanstack/react-query'; +import { queryKeys } from '@/api/query_keys'; interface ReasoningControllerProps { chatId: string; @@ -23,6 +25,11 @@ export const ReasoningController: React.FC = ({ chatId const { data: isCompletedStream } = useGetChatMessage(messageId, { select: ({ isCompletedStream }) => isCompletedStream }); + const blackBoxMessage = useQuery({ + ...queryKeys.chatsBlackBoxMessages(messageId), + notifyOnChangeProps: ['data'] + }).data; + const viewportRef = useRef(null); const { isAutoScrollEnabled, scrollToBottom, enableAutoScroll } = useAutoScroll(viewportRef, { @@ -49,11 +56,11 @@ export const ReasoningController: React.FC = ({ chatId isCompletedStream={isCompletedStream ?? true} chatId={chatId} messageId={messageId} - isLastMessage={messageIndex === reasoningMessageIds.length - 1} + isLastMessage={messageIndex === reasoningMessageIds.length - 1 && !blackBoxMessage} /> ))} - + diff --git a/web/src/controllers/ReasoningController/ReasoningMessages/ReasoningBlackBoxMessage.tsx b/web/src/controllers/ReasoningController/ReasoningMessages/ReasoningBlackBoxMessage.tsx index d78243fd1..5233d6bd0 100644 --- a/web/src/controllers/ReasoningController/ReasoningMessages/ReasoningBlackBoxMessage.tsx +++ b/web/src/controllers/ReasoningController/ReasoningMessages/ReasoningBlackBoxMessage.tsx @@ -5,25 +5,22 @@ import React from 'react'; import { queryKeys } from '@/api/query_keys'; import { BarContainer } from './BarContainer'; -export const BlackBoxMessage: React.FC<{ messageId: string }> = React.memo(({ messageId }) => { - const blackBoxMessage = useQuery({ - ...queryKeys.chatsBlackBoxMessages(messageId), - notifyOnChangeProps: ['data'] - }).data; +export const BlackBoxMessage: React.FC<{ blackBoxMessage: string | undefined | null }> = React.memo( + ({ blackBoxMessage }) => { + if (blackBoxMessage) { + return ( + + ); + } - if (blackBoxMessage) { - return ( - - ); + return null; } - - return null; -}); +); BlackBoxMessage.displayName = 'BlackBoxMessage'; From 1819eb18596baf397a1fae7cfe4a57c6dfbbbbd6 Mon Sep 17 00:00:00 2001 From: dal Date: Mon, 21 Apr 2025 15:45:06 -0600 Subject: [PATCH 3/4] bugfix: duplicate file being sent back --- .../handlers/src/chats/post_chat_handler.rs | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/api/libs/handlers/src/chats/post_chat_handler.rs b/api/libs/handlers/src/chats/post_chat_handler.rs index 0da13553c..0f2bae669 100644 --- a/api/libs/handlers/src/chats/post_chat_handler.rs +++ b/api/libs/handlers/src/chats/post_chat_handler.rs @@ -3060,11 +3060,35 @@ async fn apply_file_filtering_rules( // Process current turn files let new_filtered_assets = process_current_turn_files(&metrics_this_turn, &dashboards_this_turn)?; - // Return context dashboard first, then the processed new assets - Ok(vec![context_dashboard_info] - .into_iter() - .chain(new_filtered_assets.into_iter()) - .collect()) + + // --- START REFACTOR --- + // Check if the context dashboard ID is already present in the assets processed this turn + let context_dashboard_modified_this_turn = new_filtered_assets + .iter() + .any(|asset| asset.id == context_dashboard_info.id); + + if context_dashboard_modified_this_turn { + // If the context dashboard was modified THIS turn, return only the processed assets for this turn + // (which already includes the updated dashboard) + tracing::debug!("Context dashboard {} was modified this turn. Returning processed assets directly.", ctx_id); + Ok(new_filtered_assets) + } else { + // If the context dashboard was NOT modified this turn (only its metrics were), + // return the context dashboard (unmodified info) followed by the other processed assets. + tracing::debug!("Context dashboard {} was NOT modified this turn (only metrics). Prepending context info.", ctx_id); + Ok(vec![context_dashboard_info] // Use the fetched (unmodified) context info + .into_iter() + .chain(new_filtered_assets.into_iter()) + .collect()) + } + // --- END REFACTOR --- + + // OLD CODE - REMOVED: + // // Return context dashboard first, then the processed new assets + // Ok(vec![context_dashboard_info] + // .into_iter() + // .chain(new_filtered_assets.into_iter()) + // .collect()) } else { // No context metric modified, or context parsing failed. Process current turn only. tracing::debug!("No context metric modified (or context parse failed). Processing current turn files only."); From 7e866c54b19a0035e95f1d33a835128d3e9e875f Mon Sep 17 00:00:00 2001 From: dal Date: Mon, 21 Apr 2025 15:53:03 -0600 Subject: [PATCH 4/4] bugfix: data metadata processing --- .../data_source_query_routes/query_engine.rs | 105 +++++++++--------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/api/libs/query_engine/src/data_source_query_routes/query_engine.rs b/api/libs/query_engine/src/data_source_query_routes/query_engine.rs index 73a4fcd5a..24d159e8e 100644 --- a/api/libs/query_engine/src/data_source_query_routes/query_engine.rs +++ b/api/libs/query_engine/src/data_source_query_routes/query_engine.rs @@ -99,98 +99,101 @@ fn compute_column_metadata(data: &[IndexMap]) -> Vec = None; // Use specific name + let mut max_value_numeric: Option = None; // Use specific name let mut min_value_str: Option = None; let mut max_value_str: Option = None; + let mut determined_type: Option<(SimpleType, ColumnType)> = None; for row in data { if let Some(value) = row.get(column_name) { // Track unique values (up to a reasonable limit) if value_map.len() < 100 { - value_map.insert(format!("{:?}", value)); + value_map.insert(format!("{:?}", value)); // format! handles nulls acceptably } - // Calculate min/max for appropriate types + // Determine type from first non-null value encountered + if determined_type.is_none() { + match value { + // Check for non-null variants using matches! for conciseness + DataType::Int2(Some(_)) | DataType::Int4(Some(_)) | DataType::Int8(Some(_)) | + DataType::Float4(Some(_)) | DataType::Float8(Some(_)) | DataType::Text(Some(_)) | + DataType::Bool(Some(_)) | DataType::Date(Some(_)) | DataType::Timestamp(Some(_)) | + DataType::Timestamptz(Some(_)) | DataType::Json(Some(_)) | DataType::Uuid(Some(_)) | + DataType::Decimal(Some(_)) | DataType::Time(Some(_)) => { + determined_type = Some(determine_types(value)); + } + // If it's a Null variant or Unknown, keep looking + _ => {} + } + } + + // Calculate min/max based on value's actual type in this row match value { DataType::Int2(Some(v)) => { let n = *v as f64; - min_value = Some(min_value.map_or(n, |min: f64| min.min(n))); - max_value = Some(max_value.map_or(n, |max: f64| max.max(n))); + min_value_numeric = Some(min_value_numeric.map_or(n, |min| min.min(n))); + max_value_numeric = Some(max_value_numeric.map_or(n, |max| max.max(n))); } DataType::Int4(Some(v)) => { let n = *v as f64; - min_value = Some(min_value.map_or(n, |min: f64| min.min(n))); - max_value = Some(max_value.map_or(n, |max: f64| max.max(n))); + min_value_numeric = Some(min_value_numeric.map_or(n, |min| min.min(n))); + max_value_numeric = Some(max_value_numeric.map_or(n, |max| max.max(n))); } DataType::Int8(Some(v)) => { let n = *v as f64; - min_value = Some(min_value.map_or(n, |min: f64| min.min(n))); - max_value = Some(max_value.map_or(n, |max: f64| max.max(n))); + min_value_numeric = Some(min_value_numeric.map_or(n, |min| min.min(n))); + max_value_numeric = Some(max_value_numeric.map_or(n, |max| max.max(n))); } DataType::Float4(Some(v)) => { let n = *v as f64; - min_value = Some(min_value.map_or(n, |min: f64| min.min(n))); - max_value = Some(max_value.map_or(n, |max: f64| max.max(n))); + min_value_numeric = Some(min_value_numeric.map_or(n, |min| min.min(n))); + max_value_numeric = Some(max_value_numeric.map_or(n, |max| max.max(n))); } DataType::Float8(Some(v)) => { let n = *v as f64; - min_value = Some(min_value.map_or(n, |min: f64| min.min(n))); - max_value = Some(max_value.map_or(n, |max: f64| max.max(n))); + min_value_numeric = Some(min_value_numeric.map_or(n, |min| min.min(n))); + max_value_numeric = Some(max_value_numeric.map_or(n, |max| max.max(n))); } DataType::Date(Some(date)) => { - is_date_type = true; - let date_str = date.to_string(); - update_date_min_max(&date_str, &mut min_value_str, &mut max_value_str); + update_date_min_max(&date.to_string(), &mut min_value_str, &mut max_value_str); } DataType::Timestamp(Some(ts)) => { - is_date_type = true; - let ts_str = ts.to_string(); - update_date_min_max(&ts_str, &mut min_value_str, &mut max_value_str); + update_date_min_max(&ts.to_string(), &mut min_value_str, &mut max_value_str); } DataType::Timestamptz(Some(ts)) => { - is_date_type = true; - let ts_str = ts.to_string(); - update_date_min_max(&ts_str, &mut min_value_str, &mut max_value_str); + update_date_min_max(&ts.to_string(), &mut min_value_str, &mut max_value_str); } + // Ignore nulls and non-comparable types for min/max calculation _ => {} } } } - // Determine the column type and simple type - let column_type = first_row.get(column_name).unwrap(); - let (simple_type, column_type) = determine_types(column_type); + // Finalize types - default if no non-null value was found + let (simple_type, column_type) = determined_type.unwrap_or((SimpleType::Other, ColumnType::Other)); - // Format min/max values appropriately based on type - let (min_value, max_value) = if is_date_type { - ( - min_value_str.map_or(serde_json::Value::Null, |v| serde_json::Value::String(v)), - max_value_str.map_or(serde_json::Value::Null, |v| serde_json::Value::String(v)), - ) - } else { - ( - min_value.map_or(serde_json::Value::Null, |v| { - match serde_json::Number::from_f64(v) { - Some(num) => serde_json::Value::Number(num), - None => serde_json::Value::Null, - } - }), - max_value.map_or(serde_json::Value::Null, |v| { - match serde_json::Number::from_f64(v) { - Some(num) => serde_json::Value::Number(num), - None => serde_json::Value::Null, - } - }), - ) + // Format min/max values appropriately based on determined simple_type + let (min_value_json, max_value_json) = match simple_type { + SimpleType::Number => ( + min_value_numeric.and_then(|v| serde_json::Number::from_f64(v).map(serde_json::Value::Number)) + .unwrap_or(serde_json::Value::Null), + max_value_numeric.and_then(|v| serde_json::Number::from_f64(v).map(serde_json::Value::Number)) + .unwrap_or(serde_json::Value::Null), + ), + SimpleType::Date => ( + min_value_str.map_or(serde_json::Value::Null, serde_json::Value::String), + max_value_str.map_or(serde_json::Value::Null, serde_json::Value::String), + ), + // Don't provide min/max for other types + _ => (serde_json::Value::Null, serde_json::Value::Null), }; ColumnMetaData { name: column_name.to_lowercase(), - min_value, - max_value, - unique_values: value_map.len() as i32, + min_value: min_value_json, + max_value: max_value_json, + unique_values: value_map.len() as i32, // Count includes distinct null representations simple_type, column_type, }