Dashboard filters built

This commit is contained in:
jacob-buster 2025-10-03 16:57:41 -06:00
parent 5df005581f
commit cd634843a2
11 changed files with 44 additions and 74 deletions

View File

@ -121,14 +121,9 @@ export async function getMetricDataHandler(
// Ensure limit is within bounds
const queryLimit = Math.min(Math.max(limit, 1), 5000);
console.log('Filter values received:', filterValues);
console.log('Metric filters:', metric.content.filters);
// Compile SQL with user-provided filter values or defaults
const compiledSql = compileSqlWithDefaults(metric.content, filterValues);
console.log('Compiled SQL:', compiledSql);
// Extract SQL query from metric content (for backwards compatibility if no filters)
const sql = compiledSql || extractSqlFromMetricContent(metric.content);

View File

@ -197,7 +197,6 @@ export async function buildMetricResponse(
processedData: ProcessedMetricData,
userId: string
): Promise<GetMetricResponse> {
console.log('buildMetricResponse called for metric:', processedData.resolvedName);
const {
metricFile,
resolvedContent,
@ -276,12 +275,6 @@ export async function buildMetricResponse(
filters: resolvedContent.filters,
};
console.log(`buildMetricResponse for ${resolvedName}:`, {
hasFilters: !!resolvedContent.filters,
filterCount: resolvedContent.filters?.length || 0,
filters: resolvedContent.filters,
});
return response;
}
@ -289,7 +282,6 @@ export async function getMetricsInAncestorAssetFromMetricIds(
metricIds: string[],
user: User
): Promise<Record<string, MetricWithFilters>> {
console.log('getMetricsInAncestorAssetFromMetricIds called with', metricIds.length, 'metrics');
const metricsObj: Record<string, MetricWithFilters> = {};
// Process metrics in chunks of 4 to manage concurrency

View File

@ -34,7 +34,7 @@ export const getDashboardById = async ({
/** The version number of the dashboard */
version_number?: number;
}) => {
return await mainApi
return await mainApiV2
.get<GetDashboardResponse>(`/dashboards/${id}`, {
params: { password, version_number },
})

View File

@ -179,7 +179,6 @@ export const useGetMetricData = <TData = BusterMetricDataExtended>(
const queryFn = async () => {
const chosenVersionNumber: number | undefined =
versionNumberProp === 'LATEST' ? undefined : versionNumberProp;
console.log('Fetching metric data with filter values:', filterValues);
const result = await getMetricData({
id,
version_number: chosenVersionNumber || undefined,
@ -187,7 +186,6 @@ export const useGetMetricData = <TData = BusterMetricDataExtended>(
report_file_id: cacheDataId,
filter_values: filterValues,
});
console.log('Received metric data:', result);
const latestVersionNumber = getLatestMetricVersion(id);
const isLatest =
versionNumberProp === 'LATEST' ||

View File

@ -15,8 +15,8 @@ const dashboardGetList = (
const dashboardGetDashboard = (dashboardId: string, version_number: number | 'LATEST') =>
queryOptions<GetDashboardResponse>({
queryKey: ['dashboard', 'get', dashboardId, version_number || 'LATEST'] as const,
staleTime: 60 * 1000,
queryKey: ['dashboard', 'get', 'v2', dashboardId, version_number || 'LATEST'] as const, // Added 'v2' to bust cache
staleTime: 0, // Temporarily set to 0 during filter development
});
export const dashboardQueryKeys = {

View File

@ -9,7 +9,7 @@ import type { listMetrics } from '../buster_rest/metrics';
export const metricsGetMetric = (metricId: string, version_number: number | 'LATEST') => {
return queryOptions<BusterMetric>({
queryKey: ['metrics', 'get', metricId, version_number || 'LATEST'] as const,
staleTime: 60 * 1000, // 60 seconds
staleTime: 0, // Temporarily set to 0 during filter development
});
};

View File

@ -9,7 +9,7 @@ import { useMemoizedFn } from '@/hooks/useMemoizedFn';
import { DashboardMetricItem } from '../../../../components/features/metrics/DashboardMetricItem';
import { DashboardContentControllerProvider } from './DashboardContentControllerContext';
import { DashboardEmptyState, DashboardNoContentReadOnly } from './DashboardEmptyState';
import { DashboardFilterProvider } from './DashboardFilterContext';
import { DashboardFilterProvider, useDashboardFilterValues } from './DashboardFilterContext';
import { getCommonFilters } from './helpers/getCommonFilters';
import { removeChildrenFromItems } from './helpers';
@ -17,7 +17,7 @@ const DEFAULT_EMPTY_ROWS: DashboardConfig['rows'] = [];
const DEFAULT_EMPTY_METRICS: Record<string, BusterMetric> = {};
const DEFAULT_EMPTY_CONFIG: DashboardConfig = {};
export const DashboardContentController: React.FC<{
const DashboardContentControllerInner: React.FC<{
readOnly?: boolean;
metrics: BusterDashboardResponse['metrics'] | undefined;
dashboard: BusterDashboardResponse['dashboard'] | undefined;
@ -34,13 +34,10 @@ export const DashboardContentController: React.FC<{
onUpdateDashboardConfig,
}) => {
const [draggingId, setDraggingId] = useState<string | null>(null);
const [dashboardFilterValues, setDashboardFilterValues] = useState<Record<string, unknown>>({});
const { setDashboardFilterValues } = useDashboardFilterValues();
const commonFilters = useMemo(() => {
const filters = getCommonFilters(metrics);
console.log('DashboardContentController - commonFilters:', filters);
console.log('DashboardContentController - readOnly:', readOnly);
console.log('DashboardContentController - Should show filters?', !readOnly && filters.length > 0);
return filters;
}, [metrics, readOnly]);
@ -120,24 +117,22 @@ export const DashboardContentController: React.FC<{
return (
<div className="dashboard-content-controller overflow-visible">
{hasMetrics && !!dashboardRows.length && !!dashboard ? (
<DashboardFilterProvider>
<DashboardContentControllerProvider dashboard={dashboard}>
{!readOnly && commonFilters.length > 0 && (
<DashboardFilters
commonFilters={commonFilters}
onFilterValuesChange={setDashboardFilterValues}
/>
)}
<BusterResizeableGrid
rows={dashboardRows}
readOnly={readOnly}
onRowLayoutChange={onRowLayoutChange}
onStartDrag={onStartDrag}
onEndDrag={onDragEnd}
overlayComponent={memoizedOverlayComponent}
<DashboardContentControllerProvider dashboard={dashboard}>
{!readOnly && commonFilters.length > 0 && (
<DashboardFilters
commonFilters={commonFilters}
onFilterValuesChange={setDashboardFilterValues}
/>
</DashboardContentControllerProvider>
</DashboardFilterProvider>
)}
<BusterResizeableGrid
rows={dashboardRows}
readOnly={readOnly}
onRowLayoutChange={onRowLayoutChange}
onStartDrag={onStartDrag}
onEndDrag={onDragEnd}
overlayComponent={memoizedOverlayComponent}
/>
</DashboardContentControllerProvider>
) : !readOnly ? (
<DashboardEmptyState onOpenAddContentModal={onOpenAddContentModal} />
) : (
@ -147,4 +142,19 @@ export const DashboardContentController: React.FC<{
);
}
);
DashboardContentController.displayName = 'DashboardIndividualDashboard';
DashboardContentControllerInner.displayName = 'DashboardContentControllerInner';
export const DashboardContentController: React.FC<{
readOnly?: boolean;
metrics: BusterDashboardResponse['metrics'] | undefined;
dashboard: BusterDashboardResponse['dashboard'] | undefined;
onUpdateDashboardConfig: ReturnType<typeof useUpdateDashboardConfig>['mutateAsync'];
onOpenAddContentModal: () => void;
animate?: boolean;
}> = (props) => {
return (
<DashboardFilterProvider>
<DashboardContentControllerInner {...props} />
</DashboardFilterProvider>
);
};

View File

@ -8,20 +8,13 @@ import type { BusterMetric } from '@/api/asset_interfaces';
export function getCommonFilters(metrics: Record<string, BusterMetric>): MetricFilter[] {
const metricsArray = Object.values(metrics);
console.log('getCommonFilters - Total metrics:', metricsArray.length);
metricsArray.forEach((m, i) => {
console.log(` Metric ${i} (${m.name}):`, m.filters?.length || 0, 'filters', m.filters);
});
if (metricsArray.length === 0) {
console.log('No metrics, returning empty');
return [];
}
// Get filters from first metric as baseline
const firstMetric = metricsArray[0];
if (!firstMetric?.filters || firstMetric.filters.length === 0) {
console.log('First metric has no filters');
return [];
}
@ -29,19 +22,15 @@ export function getCommonFilters(metrics: Record<string, BusterMetric>): MetricF
const commonFilters: MetricFilter[] = [];
for (const filter of firstMetric.filters) {
console.log(`\nChecking filter "${filter.key}"...`);
// Check if this filter exists in all other metrics
const isInAllMetrics = metricsArray.every((metric, idx) => {
const isInAllMetrics = metricsArray.every((metric) => {
if (!metric.filters) {
console.log(` ✗ Metric ${idx} has no filters`);
return false;
}
// Find matching filter by key
const matchingFilter = metric.filters.find((f) => f.key === filter.key);
if (!matchingFilter) {
console.log(` ✗ Metric ${idx} (${metric.name}) missing key "${filter.key}"`);
return false;
}
@ -50,23 +39,13 @@ export function getCommonFilters(metrics: Record<string, BusterMetric>): MetricF
const modeMatch = matchingFilter.mode === filter.mode;
const columnMatch = matchingFilter.column === filter.column;
if (!typeMatch || !modeMatch || !columnMatch) {
console.log(` ✗ Metric ${idx} (${metric.name}) config mismatch:`, {
type: typeMatch ? '✓' : `${matchingFilter.type} vs ${filter.type}`,
mode: modeMatch ? '✓' : `${matchingFilter.mode} vs ${filter.mode}`,
column: columnMatch ? '✓' : `${matchingFilter.column} vs ${filter.column}`,
});
}
return typeMatch && modeMatch && columnMatch;
});
if (isInAllMetrics) {
console.log(` ✓ "${filter.key}" is COMMON!`);
commonFilters.push(filter);
}
}
console.log('\nFinal common filters:', commonFilters.length, commonFilters);
return commonFilters;
}

View File

@ -25,10 +25,6 @@ import {
RESPOND_WITHOUT_ASSET_CREATION_TOOL_NAME,
createRespondWithoutAssetCreationTool,
} from '../../tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-tool';
import {
SUBMIT_THOUGHTS_TOOL_NAME,
createSubmitThoughtsTool,
} from '../../tools/communication-tools/submit-thoughts-tool/submit-thoughts-tool';
import { EXECUTE_SQL_TOOL_NAME } from '../../tools/database-tools/execute-sql/execute-sql';
import { SEQUENTIAL_THINKING_TOOL_NAME } from '../../tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool';
import { CREATE_DASHBOARDS_TOOL_NAME } from '../../tools/visualization-tools/dashboards/create-dashboards-tool/create-dashboards-tool';
@ -45,7 +41,7 @@ import { getAnalystAgentSystemPrompt } from './get-analyst-agent-system-prompt';
export const ANALYST_AGENT_NAME = 'analystAgent';
const STOP_CONDITIONS = [
stepCountIs(25),
stepCountIs(50),
hasToolCall(DONE_TOOL_NAME),
hasToolCall(RESPOND_WITHOUT_ASSET_CREATION_TOOL_NAME),
hasToolCall(MESSAGE_USER_CLARIFYING_QUESTION_TOOL_NAME),

View File

@ -1,10 +1,10 @@
import { z } from 'zod';
import { MetricSchema } from '../metrics';
import { MetricSchemaWithFilters } from '../metrics';
import { ShareConfigSchema, ShareRoleSchema } from '../share';
import { DashboardSchema } from './dashboard.types';
export const GetDashboardResponseSchema = z.object({
metrics: z.record(z.string(), MetricSchema),
metrics: z.record(z.string(), MetricSchemaWithFilters),
dashboard: DashboardSchema,
collections: z.array(
z.object({

View File

@ -1,6 +1,6 @@
import { z } from 'zod';
import { AssetCollectionsSchema } from '../collections/shared-asset-collections';
import { MetricSchema } from '../metrics';
import { MetricSchemaWithFilters } from '../metrics';
import { ShareConfigSchema } from '../share';
import { VersionsSchema } from '../version-shared';
@ -31,7 +31,7 @@ export const ReportResponseSchema = z.object({
collections: AssetCollectionsSchema,
content: z.string(),
...ShareConfigSchema.shape,
metrics: z.record(z.string(), MetricSchema),
metrics: z.record(z.string(), MetricSchemaWithFilters),
});
export type ReportListItem = z.infer<typeof ReportListItemSchema>;