Skip to content
Open
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
14 changes: 11 additions & 3 deletions template/.env.example.tmpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
DATABRICKS_HOST=https://...
{{- if .dotenv_example}}
{{.dotenv_example}}
{{- if .dotEnv.example}}
{{.dotEnv.example}}
{{- end}}
{{- if .plugins.lakebase}}
PGHOST=your-lakebase-host.databricks.com
PGDATABASE=databricks_postgres
# Run: databricks postgres list-endpoints projects/{project-id}/branches/{branch-id}
LAKEBASE_ENDPOINT=projects/<project-id>/branches/<branch-id>/endpoints/<endpoint-id>
# PGUSER=your_user # optional, defaults to DATABRICKS_CLIENT_ID
PGSSLMODE=require
{{- end}}
DATABRICKS_APP_PORT=8000
DATABRICKS_APP_NAME={{.project_name}}
DATABRICKS_APP_NAME={{.projectName}}
FLASK_RUN_HOST=0.0.0.0
19 changes: 15 additions & 4 deletions template/.env.tmpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
{{if ne .profile ""}}DATABRICKS_CONFIG_PROFILE={{.profile}}{{else}}DATABRICKS_HOST={{.workspace_host}}{{end}}
{{- if .dotenv}}
{{.dotenv}}
{{- if ne .profile ""}}
DATABRICKS_CONFIG_PROFILE={{.profile}}
{{- else}}
DATABRICKS_HOST={{.workspaceHost}}
{{- end}}
{{- if .dotEnv.content}}
{{.dotEnv.content}}
{{- end}}
{{- if .plugins.lakebase}}
PGHOST='' # Copy from the Lakebase Postgres UI
PGDATABASE='databricks_postgres' # Copy from the Lakebase Postgres UI
LAKEBASE_ENDPOINT='' # Run: databricks postgres list-endpoints projects/{project-id}/branches/{branch-id}
# PGUSER='' # optional, defaults to DATABRICKS_CLIENT_ID
PGSSLMODE=require
{{- end}}
DATABRICKS_APP_PORT=8000
DATABRICKS_APP_NAME={{.project_name}}
DATABRICKS_APP_NAME={{.projectName}}
FLASK_RUN_HOST=0.0.0.0
16 changes: 14 additions & 2 deletions template/app.yaml.tmpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
command: ['npm', 'run', 'start']
{{- if .app_env}}
env:
{{.app_env}}
{{- if .appEnv}}
{{.appEnv}}
{{- end}}
{{- if .plugins.lakebase}}
- name: PGHOST
value: "" # Copy from the Lakebase Postgres UI
- name: PGDATABASE
value: "databricks_postgres" # Copy from the Lakebase Postgres UI
- name: LAKEBASE_ENDPOINT
value: "" # Run: databricks postgres list-endpoints projects/{project-id}/branches/{branch-id}
- name: PGSSLMODE
value: "require"
# - name: PGUSER
# value: "" # optional, defaults to DATABRICKS_CLIENT_ID
Comment on lines 1 to 16
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is all very coupled to each plugin, right now the cli populates the env vars based on the resources from the manifest so we wouldn't need this for analytics.

I wonder if we can make it in a different way for lakebase so we can keep what we had instead of putting ifs all around checking for each plugin

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry, you're right, we should use .appEnv. I'll change it 👍

env:
{{- if .appEnv}}
{{.appEnv}}
{{- end}}
{{- if .plugins.lakebase}}
  ... # additional, not resource-specific envs, until there is no resource we can use
{{- end}}

But for Lakebase, it is still be manual as I want to ensure there are comments near those envs for guidance. I don't see any better way to define this, unless there's a way on plugin manifest to provide env with additional comment - I don't think so? On the other hand, I don't think it makes sense to build it into the plugin manifest 🤔

Instead of putting ifs all around checking for each plugin

Please keep in mind that we'll do the ifs in the template anyway - at least for now. The more "dynamic" the template is, the more complex the structure and syntax is. In the current scale, IMO it's better to add routes / menu links based on if with a given plugin name, instead of having a slice and registering them dynamically which adds significant amount of complexity.

{{- end}}
2 changes: 1 addition & 1 deletion template/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{.project_name}}</title>
<title>{{.projectName}}</title>
</head>
<body>
<div id="root"></div>
Expand Down
4 changes: 2 additions & 2 deletions template/client/public/site.webmanifest
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "{{.project_name}}",
"short_name": "{{.project_name}}",
"name": "{{.projectName}}",
"short_name": "{{.projectName}}",
"icons": [
{
"src": "/favicon-192x192.png",
Expand Down
245 changes: 96 additions & 149 deletions template/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,167 +1,114 @@
/**
* ⚠️ BEFORE MODIFYING THIS FILE:
*
* 1. Create SQL files in config/queries/
* 2. Run `npm run typegen` to generate query types
* 3. Check appKitTypes.d.ts for available types
*
* Common Mistakes:
* - DataTable does NOT accept `data` or `columns` props
* - Charts use `xKey` and `yKey`, NOT `seriesKey`/`nameKey`/`valueKey`
* - useAnalyticsQuery has no `enabled` option - use conditional rendering
*/
import { createBrowserRouter, RouterProvider, NavLink, Outlet } from 'react-router';
import {
useAnalyticsQuery,
AreaChart,
LineChart,
RadarChart,
Card,
CardContent,
CardHeader,
CardTitle,
Skeleton,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@databricks/appkit-ui/react';
import { sql } from "@databricks/appkit-ui/js";
import { useState, useEffect } from 'react';
{{- if .plugins.analytics}}
import { AnalyticsPage } from './pages/analytics/AnalyticsPage';
{{- end}}
{{- if .plugins.lakebase}}
import { LakebasePage } from './pages/lakebase/LakebasePage';
{{- end}}

function App() {
const { data, loading, error } = useAnalyticsQuery('hello_world', {
message: sql.string('hello world'),
});
const navLinkClass = ({ isActive }: { isActive: boolean }) =>
`px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
isActive
? 'bg-primary text-primary-foreground'
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
}`;

const [health, setHealth] = useState<{
status: string;
timestamp: string;
} | null>(null);
const [healthError, setHealthError] = useState<string | null>(null);
function Layout() {
return (
<div className="min-h-screen bg-background flex flex-col">
<header className="border-b px-6 py-3 flex items-center gap-4">
<h1 className="text-lg font-semibold text-foreground">{{.projectName}}</h1>
<nav className="flex gap-1">
<NavLink to="/" end className={navLinkClass}>
Home
</NavLink>
{{- if .plugins.analytics}}
<NavLink to="/analytics" className={navLinkClass}>
Analytics
</NavLink>
{{- end}}
{{- if .plugins.lakebase}}
<NavLink to="/lakebase" className={navLinkClass}>
Lakebase
</NavLink>
{{- end}}
</nav>
</header>

useEffect(() => {
fetch('/health')
.then((response) => response.json())
.then((data: { status: string }) => setHealth({ ...data, timestamp: new Date().toISOString() }))
.catch((error: { message: string }) => setHealthError(error.message));
}, []);
<main className="flex-1 p-6">
<Outlet />
</main>
</div>
);
}

const [maxMonthNum, setMaxMonthNum] = useState<number>(12);
const router = createBrowserRouter([
{
element: <Layout />,
children: [
{ path: '/', element: <HomePage /> },
{{- if .plugins.analytics}}
{ path: '/analytics', element: <AnalyticsPage /> },
{{- end}}
{{- if .plugins.lakebase}}
{ path: '/lakebase', element: <LakebasePage /> },
{{- end}}
],
},
]);

const salesParameters = { max_month_num: sql.number(maxMonthNum) };
export default function App() {
return <RouterProvider router={router} />;
}

function HomePage() {
return (
<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 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 className="max-w-2xl mx-auto space-y-6 mt-8">
<div className="text-center">
<h2 className="text-3xl font-bold mb-2 text-foreground">
Welcome to your Databricks App
</h2>
<p className="text-lg text-muted-foreground">
Powered by Databricks AppKit
</p>
</div>

<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>
</CardHeader>
<CardContent>
{loading && (
<div className="space-y-2">
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-8 w-1/2" />
</div>
)}
{error && <div className="text-destructive bg-destructive/10 p-3 rounded-md">Error: {error}</div>}
{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 text-primary">{data[0].value}</div>
</div>
)}
{data && data.length === 0 && <div className="text-muted-foreground">No results</div>}
</CardContent>
</Card>

<Card className="shadow-lg md:col-span-2">
<CardHeader>
<CardTitle>Health Check</CardTitle>
</CardHeader>
<CardContent>
{!health && !healthError && (
<div className="space-y-2">
<Skeleton className="h-6 w-24" />
<Skeleton className="h-4 w-48" />
</div>
)}
{healthError && (
<div className="text-destructive bg-destructive/10 p-3 rounded-md">Error: {healthError}</div>
)}
{health && (
<div className="space-y-2">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-success animate-pulse"></div>
<div className="text-lg font-semibold text-success">{health.status.toUpperCase()}</div>
</div>
<div className="text-sm text-muted-foreground">
Last checked: {new Date(health.timestamp).toLocaleString()}
</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.from({ length: 12 }, (_, i) => (
<SelectItem key={`month-${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} />
</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>
<Card className="shadow-lg">
<CardHeader>
<CardTitle>Getting Started</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<p className="text-sm text-muted-foreground">Your app is ready. Explore the resources below to continue building.</p>
<ul className="space-y-2 text-sm">
<li>
<a
href="https://github.com/databricks/appkit"
target="_blank"
rel="noopener noreferrer"
className="text-primary underline underline-offset-4 hover:text-primary/80"
>
AppKit on GitHub →
</a>
</li>
<li>
<a
href="https://databricks.github.io/appkit/"
target="_blank"
rel="noopener noreferrer"
className="text-primary underline underline-offset-4 hover:text-primary/80"
>
AppKit documentation →
</a>
</li>
</ul>
</CardContent>
</Card>
</div>
);
}

export default App;
Loading