Building a custom dashboard creation interface with compose SDK & Sisense rest APIs
Overview This use case demonstrates how to build a custom, user-friendly dashboard creation interface that leverages Sisense's powerful analytics engine while providing a tailored user experience. By combining Sisense's robust backend capabilities with a modern React frontend, organizations can create workflows that align perfectly with their specific needs and branding requirements. Value Proposition Why Build a Custom Interface? 🎨 Tailored Designer UX: Create interfaces that match your organization's design language and workflows, rather than being constrained by out-of-the-box UI limitations. 👥 Improved User Experience: Design streamlined workflows that reduce complexity for end users while maintaining full analytical capabilities. 🔒 Maintain Enterprise Controls: Leverage Sisense's built-in role-based access controls, security, and governance features without modification. ⚡ Faster Time-to-Insight: Reduce the steps required for users to create meaningful dashboards and visualizations. 🏢 Brand Consistency: Ensure dashboard creation tools match your organization's visual identity and user experience standards. Technical Stack Frontend Libraries { "@sisense/sdk-ui": "^1.x.x", "@mui/icons-material": "^5.x.x", "@mui/material": "^5.x.x", "react": "^18.x.x", "typescript": "^4.x.x" } Backend Libraries { "express": "^4.18.2", "cors": "^2.8.5", "dotenv": "^16.3.1" } Implementation Guide 1. Backend API Service Setup Create a Node.js/Express service to proxy Sisense API calls and handle authentication: // server.js const express = require('express'); const cors = require('cors'); require('dotenv').config(); const app = express(); app.use(cors()); app.use(express.json()); // Sisense Configuration const SISENSE_CONFIG = { baseUrl: process.env.SISENSE_BASE_URL, apiToken: process.env.SISENSE_API_TOKEN, dataSource: { title: 'Sample ECommerce', id: 'aLOCALHOST_aSAMPLEIAAaECOMMERCE', // ... other datasource config } }; // Helper function for Sisense API requests async function sisenseApiRequest(endpoint, method = 'GET', body = null) { const url = `${SISENSE_CONFIG.baseUrl}/api/v1${endpoint}`; const options = { method, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${SISENSE_CONFIG.apiToken}`, }, }; if (body) options.body = JSON.stringify(body); const response = await fetch(url, options); if (!response.ok) { const errorText = await response.text(); throw new Error(`Sisense API Error: ${response.status} ${errorText}`); } return await response.json(); } 2. Key API Endpoints Dashboard Management // List all dashboards app.get('/api/dashboards', async (req, res) => { try { const dashboards = await sisenseApiRequest('/dashboards'); res.json({ success: true, dashboards: dashboards.map(d => ({ id: d.oid || d._id, title: d.title, created: d.created, lastUpdated: d.lastUpdated })) }); } catch (error) { res.status(500).json({ error: 'Failed to list dashboards', details: error.message }); } }); // Create new dashboard app.post('/api/dashboards', async (req, res) => { try { const { title, description } = req.body; const payload = { title: title.trim(), desc: description || 'Dashboard created from custom UI', datasource: SISENSE_CONFIG.dataSource, layout: { type: 'columnar', columns: [{ width: 100, cells: [] }] } }; const dashboard = await sisenseApiRequest('/dashboards', 'POST', payload); res.json({ success: true, dashboard: { id: dashboard.oid || dashboard._id, title: dashboard.title, created: dashboard.created, } }); } catch (error) { res.status(500).json({ error: 'Failed to create dashboard', details: error.message }); } }); Widget Creation // Add widgets to dashboard app.post('/api/dashboards/:dashboardId/widgets', async (req, res) => { try { const { dashboardId } = req.params; const { widgets } = req.body; const results = []; for (let i = 0; i < widgets.length; i++) { const widget = widgets[i]; if (widget.widgetType !== 'chart') continue; const payload = processWidgetForSisense(widget, i); const savedWidget = await sisenseApiRequest( `/dashboards/${dashboardId}/widgets`, 'POST', payload ); results.push({ id: savedWidget.oid || savedWidget._id, title: savedWidget.title, type: savedWidget.type, }); } res.json({ success: true, saved: results.length, widgets: results }); } catch (error) { res.status(500).json({ error: 'Failed to create widgets', details: error.message }); } }); 3. Widget Data Processing Transform Sisense SDK objects to API-compatible format: function processWidgetForSisense(widget, index = 0) { const chartWidget = widget; // Chart type mapping const chartTypeMapping = { 'column': 'bar', 'bar': 'bar', 'line': 'line', 'pie': 'pie' }; const sisenseChartType = chartTypeMapping[chartWidget.chartType] || 'bar'; // Extract and process field data const category = chartWidget.dataOptions?.category?.[0]; const measure = chartWidget.dataOptions?.value?.[0]; const categoryName = category?._name || category?.name || 'Unknown Category'; const measureName = measure?._name || measure?.name || 'Unknown Measure'; // Format for Sisense API bracket notation const categoryDim = `[Commerce.${categoryName.replace(/\s+/g, ' ')}]`; const measureDim = `[Commerce.${measureName.replace(/\s+/g, ' ').replace('sum ', '')}]`; return { title: chartWidget.title || `Chart Widget ${index + 1}`, desc: 'Widget created from custom UI', datasource: SISENSE_CONFIG.dataSource, type: `chart/${sisenseChartType}`, subtype: sisenseChartType, metadata: { ignore: { ids: [], dimensions: [], all: false }, panels: [ { name: 'categories', items: category ? [{ jaql: { dim: categoryDim, datatype: 'text', title: categoryName, }, field: { id: `[Commerce.${categoryName.replace(/\s+/g, '')}]_1`, index: 0, }, format: {}, }] : [], }, { name: 'values', items: measure ? [{ jaql: { dim: measureDim, datatype: 'numeric', title: measureName, agg: 'sum', }, field: { id: `[Commerce.${measureName.replace(/\s+/g, '').replace('sum', '')}]_1`, index: 0, }, format: {}, }] : [], }, { name: 'break by', items: [] }, ], }, style: { widgetDesign: { widgetSpacing: 'none', widgetBackgroundColor: '#FFFFFF', // ... additional styling }, height: 280, }, }; } 4. Frontend Implementation Dashboard Selection Interface // React component for dashboard selection const DashboardSelector = () => { const [existingDashboards, setExistingDashboards] = useState<DashboardSummary[]>([]); const [isLoading, setIsLoading] = useState(false); const fetchDashboards = async () => { setIsLoading(true); try { const response = await fetch(`${BACKEND_URL}/api/dashboards`); const data = await response.json(); setExistingDashboards(data.dashboards || []); } catch (error) { console.error('Failed to fetch dashboards:', error); } finally { setIsLoading(false); } }; useEffect(() => { fetchDashboards(); }, []); return ( <div> {existingDashboards.map((dashboard) => ( <DashboardCard key={dashboard.id} dashboard={dashboard} onSelect={(id) => selectDashboard(id)} /> ))} </div> ); }; Widget Creation Integration // Integration with Sisense Compose SDK const WidgetBuilder = ({ dashboardId }: { dashboardId: string }) => { const [newWidgets, setNewWidgets] = useState<WidgetProps[]>([]); const { dashboard } = useGetDashboardModel({ dashboardOid: dashboardId, includeFilters: true, includeWidgets: true, }); const saveWidgets = async () => { try { const response = await fetch( `${BACKEND_URL}/api/dashboards/${dashboardId}/widgets`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ widgets: newWidgets }), } ); const result = await response.json(); if (result.success) { setNewWidgets([]); alert(`Saved ${result.saved} widgets successfully!`); } } catch (error) { console.error('Failed to save widgets:', error); } }; return ( <div> {dashboard && ( <Dashboard {...dashboardModelTranslator.toDashboardProps(dashboard)} config={{ filtersPanel: { visible: false } }} /> )} <button onClick={saveWidgets}> Save Widgets ({newWidgets.length}) </button> </div> ); }; Sisense API Endpoints Used Core Endpoints GET /api/v1/dashboards - List all dashboards POST /api/v1/dashboards - Create new dashboard GET /api/v1/dashboards/{id} - Get specific dashboard POST /api/v1/dashboards/{id}/widgets - Add widgets to dashboard Authentication All requests require Bearer token authentication: You can set this up with the SSO too - for the example I just hardcoded token. Authorization: Bearer {your-sisense-api-token} Environment Setup Backend Environment Variables # .env PORT=3001 SISENSE_BASE_URL=https://your-sisense-instance.com SISENSE_API_TOKEN=your_api_token_here Frontend Environment Variables # .env REACT_APP_BACKEND_URL=http://localhost:3001 Key Considerations Licensing Requirements Designer License Required: This solution requires Sisense Designer licenses or above for users who will be creating dashboards Token Management: Implement proper token rotation and security practicesBenefits Realized ✅ Improved User Adoption: Create tailored workflows that align with your product's UX. ✅ Consistent Branding: Custom UI matches organizational design language ✅ Faster Dashboard Creation: Streamlined workflow reduces clicks and complexity ✅ Maintained Security: Full Sisense RBAC and security features preserved Conclusion This approach demonstrates how organizations can leverage Sisense's powerful analytics capabilities while providing users with a custom interface that fits their specific workflow requirements. By maintaining the underlying Sisense security and data governance features while improving the user experience, teams can achieve faster adoption and more efficient dashboard creation processes. The modular architecture of Compose SDK also allows for future enhancements such as custom widget templates, automated dashboard generation, and integration with existing enterprise applications.281Views3likes0Comments