Merge pull request #209 from buster-so/staging

Staging
This commit is contained in:
dal 2025-04-21 14:53:30 -07:00 committed by GitHub
commit dd9126bbb8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 184 additions and 81 deletions

View File

@ -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.");

View File

@ -99,98 +99,101 @@ fn compute_column_metadata(data: &[IndexMap<String, DataType>]) -> Vec<ColumnMet
columns.iter().map(|column_name| {
let mut value_map = HashSet::new();
let mut min_value = None;
let mut max_value = None;
let mut is_date_type = false;
let mut min_value_numeric: Option<f64> = None; // Use specific name
let mut max_value_numeric: Option<f64> = None; // Use specific name
let mut min_value_str: Option<String> = None;
let mut max_value_str: Option<String> = 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,
}

View File

@ -39,7 +39,7 @@ export const prefetchGetMyUserInfo = async (
queryFn: () => initialData!,
initialData
});
return { queryClient, initialData };
return queryClient;
};
export const useGetUser = (params: Parameters<typeof getUser>[0]) => {

View File

@ -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 <ClientRedirect to={createBusterRoute({ route: BusterRoutes.INFO_GETTING_STARTED })} />;
}
const userInfo = queryClient.getQueryData(queryKeys.userGetUserMyself.queryKey);
const newUserRoute = createBusterRoute({ route: BusterRoutes.NEW_USER });
if (

View File

@ -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 (
<div className="container flex min-h-screen w-full min-w-screen items-center justify-center py-10">
<Card className="w-full max-w-lg">
<CardContent className="space-y-4 pt-6 text-center">
<>
<div className="flex justify-center">
<BusterLogo className="h-10 w-10" />
</div>
<Title as={'h1'} variant="default">
Welcome to Buster!
</Title>
<Paragraph size={'md'}>
It looks like your organization hasn&apos;t been fully set up yet. To get started with
Buster, you&apos;ll need to complete the organization setup process or verify your
payment information.
</Paragraph>
</>
</CardContent>
<CardFooter className="w-full pt-0">
<a
href={BUSTER_GETTING_STARTED_URL}
target="_blank"
className="w-full"
rel="noopener noreferrer">
<Button
size="tall"
variant={'black'}
block
suffix={
<span className="text-base">
<ArrowUpRight />
</span>
}>
Get Started
</Button>
</a>
</CardFooter>
</Card>
</div>
);
}

View File

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

View File

@ -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<ReasoningControllerProps> = ({ chatId
const { data: isCompletedStream } = useGetChatMessage(messageId, {
select: ({ isCompletedStream }) => isCompletedStream
});
const blackBoxMessage = useQuery({
...queryKeys.chatsBlackBoxMessages(messageId),
notifyOnChangeProps: ['data']
}).data;
const viewportRef = useRef<HTMLDivElement>(null);
const { isAutoScrollEnabled, scrollToBottom, enableAutoScroll } = useAutoScroll(viewportRef, {
@ -49,11 +56,11 @@ export const ReasoningController: React.FC<ReasoningControllerProps> = ({ chatId
isCompletedStream={isCompletedStream ?? true}
chatId={chatId}
messageId={messageId}
isLastMessage={messageIndex === reasoningMessageIds.length - 1}
isLastMessage={messageIndex === reasoningMessageIds.length - 1 && !blackBoxMessage}
/>
))}
<BlackBoxMessage messageId={messageId} />
<BlackBoxMessage blackBoxMessage={blackBoxMessage} />
</div>
</ScrollArea>

View File

@ -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 (
<BarContainer
showBar={false}
status={'loading'}
isCompletedStream={false}
title={blackBoxMessage}
secondaryTitle={''}
/>
);
}
if (blackBoxMessage) {
return (
<BarContainer
showBar={false}
status={'loading'}
isCompletedStream={false}
title={blackBoxMessage}
secondaryTitle={''}
/>
);
return null;
}
return null;
});
);
BlackBoxMessage.displayName = 'BlackBoxMessage';

View File

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

View File

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

View File

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