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.
🎨 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.
{ "@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" }
{ "express": "^4.18.2", "cors": "^2.8.5", "dotenv": "^16.3.1" }
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(); }
// 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 }); } });
// 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 }); } });
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, }, }; }
// 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> ); };
// 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> ); };
- 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
All requests require Bearer token authentication: You can set this up with the SSO too - for the example I just hardcoded token.
# .env PORT=3001 SISENSE_BASE_URL=https://your-sisense-instance.com SISENSE_API_TOKEN=your_api_token_here
# .env REACT_APP_BACKEND_URL=http://localhost:3001
- 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
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.

