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
4 changes: 4 additions & 0 deletions apps/kitchen-sink/src/demo-catalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ import { ToastDemo } from "./demos/ToastDemo";
import { CommentsDemo } from "./demos/CommentsDemo";
import { GavelStylingComparisonDemo } from "./demos/GavelStylingComparisonDemo";
import { HierarchicalLookupDemo } from "./demos/HierarchicalLookupDemo";
import { ProfilesDemo } from "./demos/ProfilesDemo";
import { QueryBrowserDemo } from "./demos/QueryBrowserDemo";
import { TourDemo } from "./demos/TourDemo";
import {
type StaticIconComponent,
Expand Down Expand Up @@ -431,6 +433,8 @@ export const DEMO_GROUPS: DemoGroup[] = [
{
title: "Clicky-RPC",
items: [
{ id: "query-browser", label: "QueryBrowser", component: QueryBrowserDemo, icon: UiTerminal },
{ id: "profiles", label: "Profiles", component: ProfilesDemo, icon: UiListTree },
{
id: "command-form",
label: "CommandForm",
Expand Down
197 changes: 197 additions & 0 deletions apps/kitchen-sink/src/demos/ProfilesDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useMemo, useState } from "react";
import type {
OperationsApiClient,
ResolvedOperation,
} from "@flanksource/clicky-ui";
import {
configureProfiles,
ProfileEditor,
type ProfileSchema,
} from "@flanksource/clicky-ui/profiles";
import { DemoSection } from "./Section";

const schema: ProfileSchema = {
type: "object",
required: ["profile", "provider"],
properties: {
profile: { type: "string", title: "Profile name" },
namespace: { type: "string", title: "Namespace" },
render: { type: "string", enum: ["table", "logs"] },
query: { type: "string", title: "Query" },
params: {
type: "array",
title: "Parameters",
items: {
type: "object",
properties: {
name: { type: "string" },
label: { type: "string" },
type: {
type: "string",
enum: ["string", "number", "boolean", "date", "enum", "list"],
},
role: {
type: "string",
enum: ["filter", "limit", "offset", "time-from", "time-to"],
},
required: { type: "boolean" },
},
},
},
imports: { type: "array", items: { type: "string" } },
aliases: {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string" },
value: { type: "string" },
},
},
},
processors: {
type: "array",
items: {
type: "object",
properties: {
use: { type: "string", enum: ["example.normalize", "example.redact"] },
},
},
},
output: {
type: "object",
properties: { title: { type: "string" } },
},
provider: {
type: "object",
properties: {
type: { type: "string", enum: ["sql", "opensearch"] },
},
},
},
$defs: {
sql: {
type: "object",
properties: {
options: {
type: "object",
properties: {
database: { type: "string", title: "Database" },
},
},
},
},
opensearch: {
type: "object",
properties: {
options: {
type: "object",
properties: { index: { type: "string", title: "Index" } },
},
},
},
},
};

configureProfiles({ schema });

const client: OperationsApiClient = {
async getOpenAPISpec() {
return {
openapi: "3.0.0",
info: { title: "Profile examples", version: "1.0.0" },
paths: {},
};
},
async executeCommand() {
return { success: true, exit_code: 0 };
},
async submitForm() {
return { success: true, exit_code: 0, message: "Profile saved" };
},
};

const action: ResolvedOperation = {
path: "/api/v1/profiles/{id}",
method: "put",
operation: {
operationId: "profile_update",
summary: "Update profile",
responses: { "200": { description: "Updated" } },
},
};

const initialValue = {
profile: "service-health",
namespace: "observability",
render: "table",
provider: { type: "sql", options: { database: "operations" } },
query:
"SELECT observed_at, service, status, duration_ms FROM service_health ORDER BY observed_at DESC",
params: [
{
name: "service",
label: "Service",
type: "string",
role: "filter",
},
],
columns: [
{
name: "observed_at",
label: "Observed",
type: "datetime",
kind: "timestamp",
},
{
name: "service",
label: "Service",
type: "string",
filter: { kind: "terms", lookup: true },
},
{
name: "status",
label: "Status",
type: "string",
kind: "status",
},
],
};

export function ProfilesDemo() {
const queryClient = useMemo(
() =>
new QueryClient({
defaultOptions: { queries: { retry: false, gcTime: 0 } },
}),
[],
);
const [savedProfile, setSavedProfile] = useState("");

return (
<DemoSection
id="profiles"
title="Profiles"
description="Edit a query profile through the route-sized section rail and workspace. This demo injects a compact host schema and an in-memory save client."
>
{savedProfile ? (
<p role="status" className="text-sm text-success">
Saved {savedProfile}
</p>
) : null}
<QueryClientProvider client={queryClient}>
<div className="h-[720px] min-h-0 overflow-hidden rounded-md border border-border">
<ProfileEditor
client={client}
action={action}
surfaceKey="profile-service-health"
initialValue={initialValue}
onClose={() => undefined}
onSuccess={setSavedProfile}
/>
</div>
</QueryClientProvider>
</DemoSection>
);
}
179 changes: 179 additions & 0 deletions apps/kitchen-sink/src/demos/QueryBrowserDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import {
QueryBrowser,
type DataTableServerColumn,
type JsonSchemaObject,
type QueryBrowserRequest,
type QueryBrowserResult,
} from "@flanksource/clicky-ui";
import { DemoSection } from "./Section";

const rows: Record<string, unknown>[] = [
{
observed_at: "2026-08-11T08:14:32Z",
service: "Checkout API",
status: "healthy",
region: "eu-west",
duration_ms: 84,
},
{
observed_at: "2026-08-11T08:14:21Z",
service: "Ledger Worker",
status: "degraded",
region: "us-east",
duration_ms: 413,
},
{
observed_at: "2026-08-11T08:13:58Z",
service: "Identity API",
status: "healthy",
region: "eu-west",
duration_ms: 126,
},
{
observed_at: "2026-08-11T08:13:44Z",
service: "Reporting API",
status: "failed",
region: "ap-south",
duration_ms: 1305,
},
{
observed_at: "2026-08-11T08:13:12Z",
service: "Checkout API",
status: "healthy",
region: "us-east",
duration_ms: 91,
},
{
observed_at: "2026-08-11T08:12:47Z",
service: "Ledger Worker",
status: "healthy",
region: "eu-west",
duration_ms: 204,
},
];

const columns: DataTableServerColumn[] = [
{ name: "observed_at", label: "Observed", kind: "timestamp" },
{
name: "service",
label: "Service",
filterKey: "service",
filter: {
kind: "terms",
options: ["Checkout API", "Ledger Worker", "Identity API", "Reporting API"].map(
(value) => ({ value }),
),
},
},
{
name: "status",
label: "Status",
kind: "status",
filterKey: "status",
filter: {
kind: "terms",
options: ["healthy", "degraded", "failed"].map((value) => ({ value })),
},
},
{ name: "region", label: "Region" },
{ name: "duration_ms", label: "Duration (ms)" },
];

const optionsSchema: JsonSchemaObject = {
type: "object",
properties: {
database: {
type: "string",
title: "Database",
enum: ["operations", "analytics"],
},
readOnly: { type: "boolean", title: "Read only" },
},
};

async function execute(request: QueryBrowserRequest): Promise<QueryBrowserResult> {
const filtered = rows.filter((row) =>
Object.entries(request.filters ?? {}).every(([key, encoded]) => {
const value = String(row[key] ?? "");
const tokens = encoded.split(",").filter(Boolean);
const included = tokens.filter((token) => !token.startsWith("!"));
const excluded = tokens.filter((token) => token.startsWith("!")).map((token) => token.slice(1));
return (included.length === 0 || included.includes(value)) && !excluded.includes(value);
}),
);
const limit = request.pagination?.limit ?? 4;
const offset = request.pagination?.offset ?? 0;
const page = filtered.slice(offset, offset + limit);
return {
rows: page,
columns,
durationMs: 18,
pagination: {
mode: "offset",
limit,
offset,
hasMore: offset + limit < filtered.length,
total: filtered.length,
totalRelation: "eq",
consistency: "snapshot",
},
...(request.debug
? {
diagnostics: {
provider: "postgresql",
request: {
query: request.query,
options: request.options,
details: { transaction: "read-only", plan: "Index Scan" },
},
response: {
durationMs: 18,
returnedRows: page.length,
contentType: "application/json",
preview: JSON.stringify(page),
},
},
}
: {}),
};
}

export function QueryBrowserDemo() {
return (
<DemoSection
id="query-browser"
title="QueryBrowser"
description="Run, filter and page through a provider-neutral SQL workspace. Debug mode exposes the synthetic provider request and response without requiring a backend."
>
<QueryBrowser
id="kitchen-sink-query-browser"
title="Service health"
language="sql"
queryLabel="PostgreSQL query"
initialQuery={
"SELECT observed_at, service, status, region, duration_ms\nFROM service_health\nORDER BY observed_at DESC"
}
optionsSchema={optionsSchema}
initialOptions={{ database: "operations", readOnly: true }}
completion={{
kind: "sql",
dialect: "postgresql",
defaultSchema: "public",
schemas: [
{
name: "public",
relations: [
{
name: "service_health",
columns: columns.map((column) => ({ name: column.name })),
},
],
},
],
}}
execute={execute}
className="h-[680px] min-h-0"
/>
</DemoSection>
);
}
Loading
Loading