Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ This template uses `@databricks/app-kit` which provides:
- **Server setup**: `createApp()` with `server()` and `analytics()` plugins
- **SQL queries**: Store SQL files in `config/queries/` directory
- **React hooks**: `useAnalyticsQuery<T>()` for executing SQL queries from frontend
- **Visualization Components**: AreaChart, BarChart, LineChart, PieChart, RadarChart, DataTable
- **Authentication**: Automatic Databricks workspace authentication

### Server Setup Pattern:
Expand All @@ -55,13 +56,9 @@ import { createApp, server, analytics } from '@databricks/app-kit';
const app = await createApp({
plugins: [
server({
watch: process.env.NODE_ENV === 'development',
staticPath,
autoStart: false,
}),
analytics({
timeout: 10000,
}),
analytics(),
],
});

Expand Down Expand Up @@ -104,13 +101,27 @@ const { data, loading, error } = useAnalyticsQuery<T>(
- `enabled` - Query always executes on mount. Use conditional rendering instead: `{selectedId && <MyComponent id={selectedId} />}`
- `refetch` - Not available. Re-mount component to re-query.

### Frontend Visualization Pattern:

```typescript
import { AreaChart } from '@databricks/app-kit/react';

function MyComponent() {
return (
<div>
<AreaChart queryKey="query_name" parameters={{}} />
</div>
);
}
```

### SQL Query Files:

**IMPORTANT**: ALWAYS use SQL files in `config/queries/` for data retrieval. NEVER use tRPC for SQL queries.

- Store ALL SQL queries in `config/queries/` directory
- Name files descriptively: `trip_statistics.sql`, `user_metrics.sql`, `sales_by_region.sql`
- Reference by filename (without extension) in `useAnalyticsQuery`
- Reference by filename (without extension) in `useAnalyticsQuery` or directly in a visualization component passing it as `queryKey`
- App Kit automatically executes queries against configured Databricks warehouse
- Benefits: Built-in caching, proper connection pooling, better performance

Expand All @@ -132,6 +143,31 @@ WHERE column_value >= :min_value
AND (:optional_filter = '' OR status = :optional_filter)
```

### Query Types

Once the schema and the result of a query has been discovered, create its corresponding type in `config/queries/schema.ts` using a zod schema.

Example

```typescript
import { z } from 'zod';

export const querySchemas = {
mocked_sales: z.array(
z.object({
max_month_num: z.number().min(1).max(12),
})
),

hello_world: z.array(
z.object({
value: z.string(),
})
),
};

```

**Key Points:**

- Parameters use colon prefix: `:parameter_name`
Expand All @@ -141,7 +177,7 @@ WHERE column_value >= :min_value
#### Frontend Parameter Passing:

```typescript
const { data } = useAnalyticsQuery<ResultType[]>('filtered_data', {
const { data } = useAnalyticsQuery('filtered_data', {
min_value: minValue,
max_value: maxValue,
category: category,
Expand Down Expand Up @@ -255,7 +291,7 @@ function MyComponent() {

**Decision Tree for Data Operations:**

1. **Is it a SQL query?** → Use `config/queries/*.sql` + `useAnalyticsQuery`
1. **Is it a SQL query?** → Use `config/queries/*.sql` + a Visualization component or `useAnalyticsQuery` if no component is available
- SELECT statements
- Aggregations, JOINs, GROUP BY
- Analytics and reporting queries
Expand Down Expand Up @@ -395,30 +431,52 @@ npm run test:e2e:ui # Run with Playwright UI
- Forms should have loading states: `disabled={isLoading}`
- Show empty states with helpful text when no data exists

## Data Visualization with Recharts
## Data Visualization with App Kit UI

The template includes Recharts. Use Databricks brand colors: `['#40d1f5', '#4462c9', '#EB1600', '#0B2026', '#4A4A4A', '#353a4a']` (via `stroke` or `fill` props).
App Kit UI provides an abstraction over Recharts.

It exports a list of components where each component also exports its own props, so for example to use the `LineChart` it would be used as follows:

```typescript
import { LineChart } from '@databricks/app-kit-ui/react';

function MyComponent() {
return (
<Card>
<CardHeader>
<CardTitle>My Data</CardTitle>
</CardHeader>
<CardContent>
<LineChart queryKey="my_data" parameters={{}} />
</CardContent>
</Card>
);
}
```
Each component exports their props, so to know the props from `LineChart`, `LineChartProps` can be imported too.

The Visualization components provided by the App Kit UI library can also be used in full control mode combined with Recharts, which is included in the template.
Use Databricks brand colors: `['#40d1f5', '#4462c9', '#EB1600', '#0B2026', '#4A4A4A', '#353a4a']` (via `stroke` or `fill` props).

```tsx
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { LineChart } from '@databricks/app-kit-ui/react';
import { Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';

<Card>
<CardHeader><CardTitle>My Metrics</CardTitle></CardHeader>
<CardContent>
{loading && <Skeleton className="h-[300px] w-full" />}
{error && <div className="text-destructive">Error: {error}</div>}
{data && (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="value" stroke="#40d1f5" />
</LineChart>
</ResponsiveContainer>
)}
<LineChart queryKey="query_name" parameters={salesParameters}>
<Line type="monotone" dataKey="revenue" stroke="#40d1f5" />
<Line type="monotone" dataKey="expenses" stroke="#4462c9" />
<Line type="monotone" dataKey="customers" stroke="#EB1600" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
</LineChart>
</CardContent>
</Card>
```

Every component handles loading, errors and data fetching internally, so the only thing needed is the `queryKey` and `parameters`.
When rendering fully custom mode, it also needs the recharts components for that specific visualization component.
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useAnalyticsQuery } from '@databricks/app-kit/react';
import { useAnalyticsQuery, AreaChart, LineChart, RadarChart } from '@databricks/app-kit-ui/react';
import { Line, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import type { QueryResult } from '../../shared/types';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { trpc } from './lib/trpc';
import { useState, useEffect } from 'react';

function App() {
const { data, loading, error } = useAnalyticsQuery<QueryResult[]>('hello_world', {
const { data, loading, error } = useAnalyticsQuery('hello_world', {
message: 'hello world',
});

Expand Down Expand Up @@ -44,16 +46,18 @@ function App() {
});
}, []);

const [maxMonthNum, setMaxMonthNum] = useState<number>(12);

const salesParameters = { max_month_num: maxMonthNum };

return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 flex flex-col items-center justify-center p-4">
<div className="min-h-screen bg-background flex flex-col items-center justify-center p-4 w-full">
<div className="mb-8 text-center">
<h1 className="text-4xl font-bold mb-2 bg-gradient-to-r from-slate-900 to-slate-700 dark:from-slate-100 dark:to-slate-300 bg-clip-text text-transparent">
Minimal Databricks App
</h1>
<h1 className="text-4xl font-bold mb-2 text-foreground">Minimal Databricks App</h1>
<p className="text-lg text-muted-foreground max-w-md">A minimal Databricks App powered by Databricks AppKit</p>
</div>

<div className="flex flex-col gap-6 w-full max-w-md">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-7xl">
<Card className="shadow-lg">
<CardHeader>
<CardTitle>SQL Query Result</CardTitle>
Expand All @@ -69,9 +73,7 @@ function App() {
{data && data.length > 0 && (
<div className="space-y-2">
<div className="text-sm text-muted-foreground">Query: SELECT :message AS value</div>
<div className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
{data[0].value}
</div>
<div className="text-2xl font-bold text-primary">{data[0].value}</div>
</div>
)}
{data && data.length === 0 && <div className="text-muted-foreground">No results</div>}
Expand All @@ -95,10 +97,8 @@ function App() {
{health && (
<div className="space-y-2">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse"></div>
<div className="text-lg font-semibold text-green-600 dark:text-green-400">
{health.status.toUpperCase()}
</div>
<div className="w-2 h-2 rounded-full bg-[hsl(var(--success))] animate-pulse"></div>
<div className="text-lg font-semibold text-[hsl(var(--success))]">{health.status.toUpperCase()}</div>
</div>
<div className="text-sm text-muted-foreground">
Last checked: {new Date(health.timestamp).toLocaleString()}
Expand All @@ -116,7 +116,7 @@ function App() {
{modelLoading && (
<div className="space-y-2">
<Skeleton className="h-4 w-48" />
<div className="bg-slate-100 dark:bg-slate-800 p-3 rounded-md border border-slate-200 dark:border-slate-700 space-y-2">
<div className="bg-muted p-3 rounded-md border border-border space-y-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-5/6" />
<Skeleton className="h-4 w-4/6" />
Expand All @@ -127,13 +127,69 @@ function App() {
{modelResponse && (
<div className="space-y-2">
<div className="text-sm text-muted-foreground">Prompt: &quot;How are you today?&quot;</div>
<div className="text-base bg-slate-100 dark:bg-slate-800 p-3 rounded-md border border-slate-200 dark:border-slate-700">
{modelResponse}
</div>
<div className="text-base bg-muted p-3 rounded-md border border-border">{modelResponse}</div>
</div>
)}
</CardContent>
</Card>

<Card className="shadow-lg md:col-span-3">
<CardHeader>
<CardTitle>Sales Data Filter</CardTitle>
</CardHeader>
<CardContent>
<div className="max-w-md">
<div className="space-y-2">
<Label htmlFor="max-month">Show data up to month</Label>
<Select value={maxMonthNum.toString()} onValueChange={(value) => setMaxMonthNum(parseInt(value))}>
<SelectTrigger id="max-month">
<SelectValue placeholder="All months" />
</SelectTrigger>
<SelectContent>
{[...Array(12)].map((_, i) => (
<SelectItem key={i + 1} value={(i + 1).toString()}>
{i + 1 === 12 ? 'All months (12)' : `Month ${i + 1}`}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>

<Card className="shadow-lg flex min-w-0">
<CardHeader>
<CardTitle>Sales Trend Area Chart</CardTitle>
</CardHeader>
<CardContent>
<AreaChart queryKey="mocked_sales" parameters={salesParameters} />
</CardContent>
</Card>
<Card className="shadow-lg flex min-w-0">
<CardHeader>
<CardTitle>Sales Trend Custom Line Chart</CardTitle>
</CardHeader>
<CardContent>
<LineChart queryKey="mocked_sales" parameters={salesParameters}>
<CartesianGrid strokeDasharray="3 3" />
<Line type="monotone" dataKey="revenue" stroke="#40d1f5" />
<Line type="monotone" dataKey="expenses" stroke="#4462c9" />
<Line type="monotone" dataKey="customers" stroke="#EB1600" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
</LineChart>
</CardContent>
</Card>
<Card className="shadow-lg flex min-w-0">
<CardHeader>
<CardTitle>Sales Trend Radar Chart</CardTitle>
</CardHeader>
<CardContent>
<RadarChart queryKey="mocked_sales" parameters={salesParameters} />
</CardContent>
</Card>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Auto-generated by AppKit - DO NOT EDIT
import "@databricks/app-kit-ui/react";

declare module "@databricks/app-kit-ui/react" {
interface PluginRegistry {

}

interface QueryRegistry {
mocked_sales: {
max_month_num: number;
}[];
hello_world: {
value: string;
}[];
}

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated, basically we ask the agent to generate a schema for each of the queries in config/queries/schema.ts, and then the appKitTypes file will contain a query registry, so the useAnalyticsQuery will be automatically typed depending on the query.

The pluginRegistry types all the endpoints, we have a bug there that we are fixing, it doesn't generate them when having autoStart as false

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not blocking: we need to have an explicit build step as part of npm run build. Today if the agent deploys before running locally we will have an error.

Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function SelectContent({
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
style={{ backgroundColor: 'hsl(var(--card))', opacity: 1 }}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
position === 'popper' &&
Expand Down
Loading