Skip to content

Commit d277cc4

Browse files
feat: Update app kit template (#4043)
## Changes <!-- Brief summary of your changes that is easy to understand --> Update the AppKit template to improve the base css, remove some unnecessary code and add some examples of chart usage. Added a mocked query to show some data on the charts. Light mode <img width="1689" height="1179" alt="image" src="https://github.com/user-attachments/assets/c6ed133f-0925-4130-bfde-e7685d0fc666" /> Dark mode <img width="1535" height="1105" alt="image" src="https://github.com/user-attachments/assets/3fa87701-9e20-4b23-b7c2-1f8f0390ce7c" /> ## Why <!-- Why are these changes needed? Provide the context that the reviewer might be missing. For example, were there any decisions behind the change that are not reflected in the code itself? --> ## Tests Manually <!-- If your PR needs to be included in the release notes for next release, add a separate entry in NEXT_CHANGELOG.md as part of your PR. --> --------- Co-authored-by: MarioCadenas <[email protected]> Co-authored-by: Fabian Jakobs <[email protected]>
1 parent ca31089 commit d277cc4

File tree

16 files changed

+4461
-611
lines changed

16 files changed

+4461
-611
lines changed

experimental/apps-mcp/templates/appkit/template/{{.project_name}}/CLAUDE.md

Lines changed: 82 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ This template uses `@databricks/app-kit` which provides:
7777
- **Server setup**: `createApp()` with `server()` and `analytics()` plugins
7878
- **SQL queries**: Store SQL files in `config/queries/` directory
7979
- **React hooks**: `useAnalyticsQuery<T>()` for executing SQL queries from frontend
80+
- **Visualization Components**: AreaChart, BarChart, LineChart, PieChart, RadarChart, DataTable
8081
- **Authentication**: Automatic Databricks workspace authentication
8182

8283
### Server Setup Pattern:
@@ -87,13 +88,9 @@ import { createApp, server, analytics } from '@databricks/app-kit';
8788
const app = await createApp({
8889
plugins: [
8990
server({
90-
watch: process.env.NODE_ENV === 'development',
91-
staticPath,
9291
autoStart: false,
9392
}),
94-
analytics({
95-
timeout: 10000,
96-
}),
93+
analytics(),
9794
],
9895
});
9996

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

136+
### Frontend Visualization Pattern:
137+
138+
```typescript
139+
import { AreaChart } from '@databricks/app-kit/react';
140+
141+
function MyComponent() {
142+
return (
143+
<div>
144+
<AreaChart queryKey="query_name" parameters={{}} />
145+
</div>
146+
);
147+
}
148+
```
149+
139150
### SQL Query Files:
140151

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

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

@@ -164,6 +175,31 @@ WHERE column_value >= :min_value
164175
AND (:optional_filter = '' OR status = :optional_filter)
165176
```
166177

178+
### Query Types
179+
180+
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.
181+
182+
Example
183+
184+
```typescript
185+
import { z } from 'zod';
186+
187+
export const querySchemas = {
188+
mocked_sales: z.array(
189+
z.object({
190+
max_month_num: z.number().min(1).max(12),
191+
})
192+
),
193+
194+
hello_world: z.array(
195+
z.object({
196+
value: z.string(),
197+
})
198+
),
199+
};
200+
201+
```
202+
167203
**Key Points:**
168204

169205
- Parameters use colon prefix: `:parameter_name`
@@ -173,7 +209,7 @@ WHERE column_value >= :min_value
173209
#### Frontend Parameter Passing:
174210

175211
```typescript
176-
const { data } = useAnalyticsQuery<ResultType[]>('filtered_data', {
212+
const { data } = useAnalyticsQuery('filtered_data', {
177213
min_value: minValue,
178214
max_value: maxValue,
179215
category: category,
@@ -287,7 +323,7 @@ function MyComponent() {
287323

288324
**Decision Tree for Data Operations:**
289325

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

430-
## Data Visualization with Recharts
466+
## Data Visualization with App Kit UI
431467

432-
The template includes Recharts. Use Databricks brand colors: `['#40d1f5', '#4462c9', '#EB1600', '#0B2026', '#4A4A4A', '#353a4a']` (via `stroke` or `fill` props).
468+
App Kit UI provides an abstraction over Recharts.
469+
470+
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:
471+
472+
```typescript
473+
import { LineChart } from '@databricks/app-kit-ui/react';
474+
475+
function MyComponent() {
476+
return (
477+
<Card>
478+
<CardHeader>
479+
<CardTitle>My Data</CardTitle>
480+
</CardHeader>
481+
<CardContent>
482+
<LineChart queryKey="my_data" parameters={{}} />
483+
</CardContent>
484+
</Card>
485+
);
486+
}
487+
```
488+
Each component exports their props, so to know the props from `LineChart`, `LineChartProps` can be imported too.
489+
490+
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.
491+
Use Databricks brand colors: `['#40d1f5', '#4462c9', '#EB1600', '#0B2026', '#4A4A4A', '#353a4a']` (via `stroke` or `fill` props).
433492

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

438498
<Card>
439499
<CardHeader><CardTitle>My Metrics</CardTitle></CardHeader>
440500
<CardContent>
441-
{loading && <Skeleton className="h-[300px] w-full" />}
442-
{error && <div className="text-destructive">Error: {error}</div>}
443-
{data && (
444-
<ResponsiveContainer width="100%" height={300}>
445-
<LineChart data={data}>
446-
<CartesianGrid strokeDasharray="3 3" />
447-
<XAxis dataKey="name" />
448-
<YAxis />
449-
<Tooltip />
450-
<Line type="monotone" dataKey="value" stroke="#40d1f5" />
451-
</LineChart>
452-
</ResponsiveContainer>
453-
)}
501+
<LineChart queryKey="query_name" parameters={salesParameters}>
502+
<Line type="monotone" dataKey="revenue" stroke="#40d1f5" />
503+
<Line type="monotone" dataKey="expenses" stroke="#4462c9" />
504+
<Line type="monotone" dataKey="customers" stroke="#EB1600" />
505+
<XAxis dataKey="month" />
506+
<YAxis />
507+
<Tooltip />
508+
</LineChart>
454509
</CardContent>
455510
</Card>
456511
```
512+
513+
Every component handles loading, errors and data fetching internally, so the only thing needed is the `queryKey` and `parameters`.
514+
When rendering fully custom mode, it also needs the recharts components for that specific visualization component.

experimental/apps-mcp/templates/appkit/template/{{.project_name}}/postcss.config.js renamed to experimental/apps-mcp/templates/appkit/template/{{.project_name}}/client/postcss.config.js

File renamed without changes.

experimental/apps-mcp/templates/appkit/template/{{.project_name}}/client/src/App.tsx

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { useAnalyticsQuery } from '@databricks/app-kit/react';
1+
import { useAnalyticsQuery, AreaChart, LineChart, RadarChart } from '@databricks/app-kit-ui/react';
2+
import { Line, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts';
23
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
34
import { Skeleton } from '@/components/ui/skeleton';
4-
import type { QueryResult } from '../../shared/types';
5+
import { Label } from '@/components/ui/label';
6+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
57
import { trpc } from './lib/trpc';
68
import { useState, useEffect } from 'react';
79

810
function App() {
9-
const { data, loading, error } = useAnalyticsQuery<QueryResult[]>('hello_world', {
11+
const { data, loading, error } = useAnalyticsQuery('hello_world', {
1012
message: 'hello world',
1113
});
1214

@@ -44,16 +46,18 @@ function App() {
4446
});
4547
}, []);
4648

49+
const [maxMonthNum, setMaxMonthNum] = useState<number>(12);
50+
51+
const salesParameters = { max_month_num: maxMonthNum };
52+
4753
return (
48-
<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">
54+
<div className="min-h-screen bg-background flex flex-col items-center justify-center p-4 w-full">
4955
<div className="mb-8 text-center">
50-
<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">
51-
Minimal Databricks App
52-
</h1>
56+
<h1 className="text-4xl font-bold mb-2 text-foreground">Minimal Databricks App</h1>
5357
<p className="text-lg text-muted-foreground max-w-md">A minimal Databricks App powered by Databricks AppKit</p>
5458
</div>
5559

56-
<div className="flex flex-col gap-6 w-full max-w-md">
60+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-7xl">
5761
<Card className="shadow-lg">
5862
<CardHeader>
5963
<CardTitle>SQL Query Result</CardTitle>
@@ -69,9 +73,7 @@ function App() {
6973
{data && data.length > 0 && (
7074
<div className="space-y-2">
7175
<div className="text-sm text-muted-foreground">Query: SELECT :message AS value</div>
72-
<div className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
73-
{data[0].value}
74-
</div>
76+
<div className="text-2xl font-bold text-primary">{data[0].value}</div>
7577
</div>
7678
)}
7779
{data && data.length === 0 && <div className="text-muted-foreground">No results</div>}
@@ -95,10 +97,8 @@ function App() {
9597
{health && (
9698
<div className="space-y-2">
9799
<div className="flex items-center gap-2">
98-
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse"></div>
99-
<div className="text-lg font-semibold text-green-600 dark:text-green-400">
100-
{health.status.toUpperCase()}
101-
</div>
100+
<div className="w-2 h-2 rounded-full bg-[hsl(var(--success))] animate-pulse"></div>
101+
<div className="text-lg font-semibold text-[hsl(var(--success))]">{health.status.toUpperCase()}</div>
102102
</div>
103103
<div className="text-sm text-muted-foreground">
104104
Last checked: {new Date(health.timestamp).toLocaleString()}
@@ -116,7 +116,7 @@ function App() {
116116
{modelLoading && (
117117
<div className="space-y-2">
118118
<Skeleton className="h-4 w-48" />
119-
<div className="bg-slate-100 dark:bg-slate-800 p-3 rounded-md border border-slate-200 dark:border-slate-700 space-y-2">
119+
<div className="bg-muted p-3 rounded-md border border-border space-y-2">
120120
<Skeleton className="h-4 w-full" />
121121
<Skeleton className="h-4 w-5/6" />
122122
<Skeleton className="h-4 w-4/6" />
@@ -127,13 +127,69 @@ function App() {
127127
{modelResponse && (
128128
<div className="space-y-2">
129129
<div className="text-sm text-muted-foreground">Prompt: &quot;How are you today?&quot;</div>
130-
<div className="text-base bg-slate-100 dark:bg-slate-800 p-3 rounded-md border border-slate-200 dark:border-slate-700">
131-
{modelResponse}
132-
</div>
130+
<div className="text-base bg-muted p-3 rounded-md border border-border">{modelResponse}</div>
133131
</div>
134132
)}
135133
</CardContent>
136134
</Card>
135+
136+
<Card className="shadow-lg md:col-span-3">
137+
<CardHeader>
138+
<CardTitle>Sales Data Filter</CardTitle>
139+
</CardHeader>
140+
<CardContent>
141+
<div className="max-w-md">
142+
<div className="space-y-2">
143+
<Label htmlFor="max-month">Show data up to month</Label>
144+
<Select value={maxMonthNum.toString()} onValueChange={(value) => setMaxMonthNum(parseInt(value))}>
145+
<SelectTrigger id="max-month">
146+
<SelectValue placeholder="All months" />
147+
</SelectTrigger>
148+
<SelectContent>
149+
{[...Array(12)].map((_, i) => (
150+
<SelectItem key={i + 1} value={(i + 1).toString()}>
151+
{i + 1 === 12 ? 'All months (12)' : `Month ${i + 1}`}
152+
</SelectItem>
153+
))}
154+
</SelectContent>
155+
</Select>
156+
</div>
157+
</div>
158+
</CardContent>
159+
</Card>
160+
161+
<Card className="shadow-lg flex min-w-0">
162+
<CardHeader>
163+
<CardTitle>Sales Trend Area Chart</CardTitle>
164+
</CardHeader>
165+
<CardContent>
166+
<AreaChart queryKey="mocked_sales" parameters={salesParameters} />
167+
</CardContent>
168+
</Card>
169+
<Card className="shadow-lg flex min-w-0">
170+
<CardHeader>
171+
<CardTitle>Sales Trend Custom Line Chart</CardTitle>
172+
</CardHeader>
173+
<CardContent>
174+
<LineChart queryKey="mocked_sales" parameters={salesParameters}>
175+
<CartesianGrid strokeDasharray="3 3" />
176+
<Line type="monotone" dataKey="revenue" stroke="#40d1f5" />
177+
<Line type="monotone" dataKey="expenses" stroke="#4462c9" />
178+
<Line type="monotone" dataKey="customers" stroke="#EB1600" />
179+
<XAxis dataKey="month" />
180+
<YAxis />
181+
<Tooltip />
182+
</LineChart>
183+
</CardContent>
184+
</Card>
185+
<Card className="shadow-lg flex min-w-0">
186+
<CardHeader>
187+
<CardTitle>Sales Trend Radar Chart</CardTitle>
188+
</CardHeader>
189+
<CardContent>
190+
<RadarChart queryKey="mocked_sales" parameters={salesParameters} />
191+
</CardContent>
192+
</Card>
137193
</div>
138194
</div>
139195
);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Auto-generated by AppKit - DO NOT EDIT
2+
import "@databricks/app-kit-ui/react";
3+
4+
declare module "@databricks/app-kit-ui/react" {
5+
interface PluginRegistry {
6+
7+
}
8+
9+
interface QueryRegistry {
10+
mocked_sales: {
11+
max_month_num: number;
12+
}[];
13+
hello_world: {
14+
value: string;
15+
}[];
16+
}
17+
18+
}

experimental/apps-mcp/templates/appkit/template/{{.project_name}}/client/src/components/ui/select.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ function SelectContent({
5252
<SelectPrimitive.Portal>
5353
<SelectPrimitive.Content
5454
data-slot="select-content"
55+
style={{ backgroundColor: 'hsl(var(--card))', opacity: 1 }}
5556
className={cn(
5657
'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',
5758
position === 'popper' &&

0 commit comments

Comments
 (0)