Dashboards
Drizzle Cube provides flexible dashboard components for creating interactive analytics interfaces. Built with React and react-grid-layout
, the dashboard system supports drag-and-drop layouts, responsive design, and configurable analytics portlets.
Installation
Section titled “Installation”# Full client (includes dashboards)npm install drizzle-cube react react-dom recharts react-grid-layout
# Components only (dashboards without charts - ~218KB)npm install drizzle-cube react react-dom react-grid-layout
# With charts (modular approach)npm install drizzle-cube react react-dom recharts react-grid-layout
Import Options
Section titled “Import Options”// Full client importimport { AnalyticsDashboard, DashboardGrid, AnalyticsPortlet } from 'drizzle-cube/client';
// Components-only import (dashboard without built-in charts)import { AnalyticsDashboard, DashboardGrid, AnalyticsPortlet, QueryBuilder, Modal, PortletEditModal, DashboardEditModal} from 'drizzle-cube/client/components';
// Combined modular approachimport { AnalyticsDashboard, DashboardGrid } from 'drizzle-cube/client/components';import { RechartsBarChart, RechartsLineChart } from 'drizzle-cube/client/charts';import { useCubeQuery } from 'drizzle-cube/client/hooks';import { CubeProvider } from 'drizzle-cube/client/providers';
Overview
Section titled “Overview”The dashboard system consists of three main components: DashboardGrid
for layout management, AnalyticsPortlet
for individual visualizations, and various configuration interfaces for customization. All components are designed to work seamlessly with Cube.js-compatible data and the useCubeQuery
hook.
Bundle Size Considerations
Section titled “Bundle Size Considerations”- Full client: Complete dashboard functionality with all chart types
- Components-only: Dashboard UI without built-in charts (~218KB) - bring your own charts
- Modular approach: Mix and match components, charts, hooks as needed for optimal bundle size
Core Components
Section titled “Core Components”DashboardGrid
Section titled “DashboardGrid”The main dashboard container that manages layout, editing, and portlet interactions.
import { DashboardGrid } from 'drizzle-cube/client'
function MyDashboard() { const [config, setConfig] = useState<DashboardConfig>({ id: 'main-dashboard', name: 'Analytics Dashboard', portlets: [ { id: 'employees-chart', name: 'Employee Count', x: 0, y: 0, w: 6, h: 4, chartConfig: { xAxis: ['Employees.createdAt'], yAxis: ['Employees.count'] }, query: { measures: ['Employees.count'], timeDimensions: [{ dimension: 'Employees.createdAt', granularity: 'month' }] }, chartType: 'bar' } ] })
return ( <DashboardGrid config={config} editable={true} onConfigChange={setConfig} onSave={saveDashboardConfig} apiUrl="/cubejs-api/v1" /> )}
AnalyticsPortlet
Section titled “AnalyticsPortlet”Individual visualization components within the dashboard.
import { AnalyticsPortlet } from 'drizzle-cube/client'
<AnalyticsPortlet config={{ id: 'revenue-chart', name: 'Monthly Revenue', query: { measures: ['Orders.totalRevenue'], timeDimensions: [{ dimension: 'Orders.createdAt', granularity: 'month', dateRange: ['2023-01-01', '2023-12-31'] }] }, chartConfig: { xAxis: ['Orders.createdAt'], yAxis: ['Orders.totalRevenue'] }, chartType: 'line' }} apiUrl="/cubejs-api/v1" onEdit={() => setEditingPortlet(config)} onRefresh={() => refreshPortlet(config.id)} onDelete={() => deletePortlet(config.id)}/>
Configuration Structure
Section titled “Configuration Structure”Dashboard Configuration
Section titled “Dashboard Configuration”interface DashboardConfig { id: string name: string description?: string portlets: PortletConfig[] layout?: { breakpoints?: { [key: string]: number } cols?: { [key: string]: number } margin?: [number, number] containerPadding?: [number, number] }}
Portlet Configuration
Section titled “Portlet Configuration”interface PortletConfig { id: string name: string description?: string
// Layout (react-grid-layout format) x: number // X position in grid y: number // Y position in grid w: number // Width in grid units h: number // Height in grid units minW?: number // Minimum width minH?: number // Minimum height
// Query configuration query: CubeQuery // Cube.js query object chartConfig: ChartConfig // Chart configuration displayConfig?: DisplayConfig // Chart display options
// Chart type chartType: 'bar' | 'line' | 'area' | 'pie' | 'scatter' | 'table'
// Behavior autoRefresh?: number // Auto-refresh interval (seconds) cachingEnabled?: boolean // Enable result caching}
Dashboard Features
Section titled “Dashboard Features”Drag-and-Drop Layout
Section titled “Drag-and-Drop Layout”Enable interactive layout editing:
<DashboardGrid config={config} editable={true} // Enable editing mode onConfigChange={(newConfig) => { setConfig(newConfig) // Optionally auto-save changes saveDashboardConfig(newConfig) }}/>
Features:
- Drag portlets to reposition
- Resize portlets by dragging corners
- Responsive breakpoints for different screen sizes
- Snap-to-grid alignment
Responsive Design
Section titled “Responsive Design”Dashboards automatically adapt to different screen sizes:
// Default responsive configurationlayout: { breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }, cols: { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }, margin: [16, 16], containerPadding: [16, 16]}
Real-time Updates
Section titled “Real-time Updates”Portlets support automatic refresh and real-time updates:
// Portlet with auto-refresh every 30 seconds{ id: 'live-metrics', name: 'Live Metrics', query: { /* ... */ }, chartConfig: { /* ... */ }, chartType: 'line', autoRefresh: 30 // Refresh every 30 seconds}
Portlet Types
Section titled “Portlet Types”Chart Portlets
Section titled “Chart Portlets”Display various chart types with full interactivity:
// Bar Chart Portlet{ chartType: 'bar', chartConfig: { xAxis: ['Employees.departmentName'], yAxis: ['Employees.count', 'Employees.avgSalary'] }, displayConfig: { showLegend: true, stackedBarChart: false }}
// Time Series Line Chart{ chartType: 'line', chartConfig: { xAxis: ['Orders.createdAt'], yAxis: ['Orders.totalRevenue'] }, query: { measures: ['Orders.totalRevenue'], timeDimensions: [{ dimension: 'Orders.createdAt', granularity: 'day' }] }}
Table Portlets
Section titled “Table Portlets”Display data in tabular format:
{ chartType: 'table', query: { measures: ['Employees.count', 'Employees.avgSalary'], dimensions: ['Employees.departmentName'], order: [['Employees.count', 'desc']] }, displayConfig: { showPagination: true, pageSize: 10, sortable: true }}
KPI/Metric Portlets
Section titled “KPI/Metric Portlets”Display single metrics or key performance indicators:
{ chartType: 'kpi', query: { measures: ['Orders.totalRevenue'] }, displayConfig: { format: 'currency', showChange: true, compareToLastPeriod: true }}
Interactive Features
Section titled “Interactive Features”Portlet Editing Modal
Section titled “Portlet Editing Modal”Built-in modal for configuring portlets:
import { PortletEditModal } from 'drizzle-cube/client'
function DashboardEditor() { const [editingPortlet, setEditingPortlet] = useState<PortletConfig | null>(null)
return ( <> <DashboardGrid config={config} editable={true} onPortletEdit={setEditingPortlet} />
{editingPortlet && ( <PortletEditModal portlet={editingPortlet} isOpen={!!editingPortlet} onClose={() => setEditingPortlet(null)} onSave={(updatedPortlet) => { updatePortletConfig(updatedPortlet) setEditingPortlet(null) }} availableCubes={cubeNames} apiUrl="/cubejs-api/v1" /> )} </> )}
Portlet Actions
Section titled “Portlet Actions”Standard portlet actions for management:
// Portlet header actionsconst portletActions = [ { icon: 'refresh', label: 'Refresh', onClick: () => refreshPortlet(portletId) }, { icon: 'edit', label: 'Edit', onClick: () => setEditingPortlet(portlet) }, { icon: 'delete', label: 'Delete', onClick: () => deletePortlet(portletId) }]
Advanced Configuration
Section titled “Advanced Configuration”Custom Breakpoints
Section titled “Custom Breakpoints”Define custom responsive breakpoints:
const customLayout = { breakpoints: { xl: 1400, // Extra large screens lg: 1200, // Large screens md: 996, // Medium screens sm: 768, // Small screens (tablets) xs: 480 // Extra small screens (phones) }, cols: { xl: 16, // 16 columns on XL screens lg: 12, // 12 columns on large screens md: 8, // 8 columns on medium screens sm: 4, // 4 columns on small screens xs: 2 // 2 columns on mobile }}
Dashboard Themes
Section titled “Dashboard Themes”Apply custom styling and themes:
<div className="dashboard-theme-dark"> <DashboardGrid config={config} editable={false} /></div>
<style>.dashboard-theme-dark .portlet { @apply bg-gray-800 text-white border-gray-700;}
.dashboard-theme-dark .portlet-header { @apply bg-gray-700 border-gray-600;}</style>
Dashboard Persistence
Section titled “Dashboard Persistence”Save and load dashboard configurations:
// Save dashboard configurationconst saveDashboardConfig = async (config: DashboardConfig) => { await fetch('/api/dashboards', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) })}
// Load dashboard configurationconst loadDashboardConfig = async (dashboardId: string) => { const response = await fetch(`/api/dashboards/${dashboardId}`) return response.json() as DashboardConfig}
Performance Optimization
Section titled “Performance Optimization”Lazy Loading
Section titled “Lazy Loading”Load portlets on demand for better performance:
import { lazy, Suspense } from 'react'
const LazyAnalyticsPortlet = lazy(() => import('drizzle-cube/client').then(m => ({ default: m.AnalyticsPortlet})))
function OptimizedDashboard({ config }: { config: DashboardConfig }) { return ( <DashboardGrid config={config}> {config.portlets.map(portlet => ( <Suspense key={portlet.id} fallback={<PortletSkeleton />}> <LazyAnalyticsPortlet config={portlet} /> </Suspense> ))} </DashboardGrid> )}
Query Caching
Section titled “Query Caching”Enable caching for improved performance:
// Portlet with caching enabled{ id: 'cached-chart', query: { /* ... */ }, cachingEnabled: true, // Enable result set caching cacheTimeout: 300 // Cache for 5 minutes}
Virtual Scrolling
Section titled “Virtual Scrolling”For dashboards with many portlets:
// Enable virtualization for large dashboards<DashboardGrid config={config} virtualScrolling={true} visiblePortletBuffer={5} // Render 5 portlets outside viewport/>
Testing Dashboards
Section titled “Testing Dashboards”import { render, screen, fireEvent } from '@testing-library/react'import { DashboardGrid } from 'drizzle-cube/client'
const mockConfig: DashboardConfig = { id: 'test-dashboard', name: 'Test Dashboard', portlets: [{ id: 'test-portlet', name: 'Test Chart', x: 0, y: 0, w: 6, h: 4, query: { measures: ['Test.count'] }, chartType: 'bar', chartConfig: { yAxis: ['Test.count'] } }]}
test('renders dashboard with portlets', () => { render(<DashboardGrid config={mockConfig} />)
expect(screen.getByText('Test Chart')).toBeInTheDocument()})
test('handles portlet editing', () => { const onConfigChange = jest.fn()
render( <DashboardGrid config={mockConfig} editable={true} onConfigChange={onConfigChange} /> )
// Test editing interactions const editButton = screen.getByRole('button', { name: /edit/i }) fireEvent.click(editButton)
// Verify edit modal opens expect(screen.getByRole('dialog')).toBeInTheDocument()})
Best Practices
Section titled “Best Practices”- Responsive Design: Always test dashboards on different screen sizes
- Performance: Limit the number of portlets per dashboard (< 20)
- User Experience: Provide loading states and error handling
- Data Freshness: Set appropriate auto-refresh intervals
- Security: Ensure all queries respect security context
- Accessibility: Use proper ARIA labels and keyboard navigation
- Persistence: Save dashboard state frequently to prevent data loss
Dashboard Examples
Section titled “Dashboard Examples”Executive Dashboard
Section titled “Executive Dashboard”const executiveDashboard: DashboardConfig = { id: 'executive-dashboard', name: 'Executive Overview', portlets: [ // Revenue KPI { id: 'total-revenue', name: 'Total Revenue', x: 0, y: 0, w: 3, h: 2, chartType: 'kpi', query: { measures: ['Orders.totalRevenue'] } }, // Growth Chart { id: 'revenue-growth', name: 'Revenue Growth', x: 3, y: 0, w: 9, h: 4, chartType: 'line', query: { measures: ['Orders.totalRevenue'], timeDimensions: [{ dimension: 'Orders.createdAt', granularity: 'month', dateRange: ['2023-01-01', '2024-12-31'] }] } } ]}
Operations Dashboard
Section titled “Operations Dashboard”const operationsDashboard: DashboardConfig = { id: 'operations-dashboard', name: 'Operations Metrics', portlets: [ // Employee Productivity { id: 'productivity-trend', name: 'Productivity Trend', x: 0, y: 0, w: 8, h: 4, chartType: 'area', query: { measures: ['Productivity.avgLinesOfCode'], dimensions: ['Productivity.departmentName'], timeDimensions: [{ dimension: 'Productivity.date', granularity: 'week' }] } }, // Department Comparison { id: 'dept-comparison', name: 'Department Comparison', x: 8, y: 0, w: 4, h: 4, chartType: 'bar', query: { measures: ['Employees.count', 'Employees.avgSalary'], dimensions: ['Employees.departmentName'] } } ]}
Next Steps
Section titled “Next Steps”- Learn about Hooks for data fetching patterns
- Explore Charts for visualization options
- Review React Client overview
- Check dashboard examples in the repository
Roadmap Ideas
Section titled “Roadmap Ideas”- Dashboard templates and marketplace
- Advanced dashboard sharing and collaboration
- Dashboard embedding and white-labeling
- Real-time dashboard notifications and alerts
- Dashboard performance analytics
- Advanced dashboard filtering and drill-down capabilities