Product Update | Asset Auditor incorporates user access, permissions, and asset sharing
A more user-centric Asset Auditor In this release, we’re excited to introduce a user-focused expansion of the Asset Auditor, including two new dashboards, Users and Users Validation, along with enriched underlying data. You can now easily understand: Who has access and permissions to which data assets How assets are shared across your organization Whether access could be impacting engagement Revealing how their access and permissions connect to your data assets With this release, you get a clearer, more actionable picture of how people and assets interact to empower better oversight. We’ve also made major improvements across the existing dashboards to integrate this new data, elevate insights, and provide more actionable recommendations. Assets can’t deliver value unless users can access and engage with them In Sisense, dashboards and data models are governed by separate access controls. And they don’t operate in silos. They’re shared, cloned, embedded, and repurposed across teams. When someone shares a dashboard, they may not have permission to share the underlying model. The result? Users open dashboards expecting insights, only to find missing charts or blank visuals. They’re unsure whether the data is broken, restricted, or simply unavailable, while the sharer assumes everything is fine. The Asset Auditor gives clear visibility into which users or groups have: Access to dashboards but not to the underlying data models Access to data models but no corresponding dashboard access No access to any dashboards Yet access alone doesn’t guarantee adoption, and adoption issues are often misdiagnosed as access problems. Are dashboards underused because people truly lack access? Or because they simply aren’t engaging with the content? By surfacing these mismatches, you can prevent confusion, improve collaboration, and ensure every shared dashboard delivers the full experience it’s meant to. By detecting both over-permissioning and under-permissioning, you can tighten governance without slowing productivity. Permission drift happens quietly, introducing operational risk long before it becomes visible Do users have the correct permissions? Do some users have too many permissions? Do users have permissions to data models or dashboards that they shouldn’t? Use the Asset Auditor to see whether users have the right level of access: too little to be effective, or too much for their role. Identify misaligned configurations, such as users who maintain data model access for development or testing, but no corresponding dashboard access, which is a strong indicator that permissions no longer reflect the real workflow. By detecting both over-permissioning and under-permissioning, you can tighten governance without slowing productivity. Understand the reach of your dashboards across users and groups The Asset Auditor helps you understand the reach of your dashboards across users and groups, revealing how far each asset spreads and where engagement actually concentrates. Detect and reduce redundancy, find duplicates or overlapping assets shared across teams. Pair these insights with Sisense Usage Analytics to understand not just who can access assets, but who actively engages with them. By bringing these signals together, teams can zero in on whether the problem is permissions, visibility, or user behavior. The Asset Auditor provides much more data and insights beyond users and shares! Check it out and get smarter about how you manage your data assets. If you want to start getting better visibility into what your assets are doing inside your environment, reach out to us for a live demo or a free trial.18Views0likes0CommentsDashboard (fire unit time limit)
Hello, I am currently working in ESO (insights), and I wondered if anyone had any luck with making a dashboard that would display information like the one attached? It would be for certain apparatus that go lights-and-sirens to an incident, but are over a time limit. I am having issues getting the filters to function for me. Thanks41Views0likes4CommentsHow to filter a table/pivot table: Filter one column that does not equal another column?
Hi DRay Liliia_DevX , I am trying to see if there is logic where I have a pivot table and I want to filter one column based on another column that do not equal, so getting all the results that match within a widget filter or maybe a formula filter.80Views0likes3CommentsWidget Error
I’m working on integrating a few Sisense dashboards into my project, but I’m running into this error. import React, { useMemo, useEffect, useState } from 'react'; import { useApi, configApiRef } from '@backstage/core-plugin-api'; import { SisenseContextProvider, WidgetById } from '@sisense/sdk-ui'; import { filterFactory } from '@sisense/sdk-data'; import SisenseDashboard from './SisenseDashboard'; import solutionSisenseOrg from '../../solution-sisense-org-mapping.json'; const SISENSE_URL = ''; const SISENSE_TOKEN = ''; type SisenseOrgMapping = Record<string, Record<string, any>>; type SisenseChartProps = { dashboardOid: string; tab: string; widgetLists?: []; isWidgetRequired?: boolean; }; const centerStyle = { minHeight: '40vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', fontSize: '1.1rem', background: 'none', }; export const SisenseChart: React.FC<SisenseChartProps> = ({ dashboardOid, tab, widgetLists = [], isWidgetRequired = true, }) => { const config = useApi(configApiRef); const { sisenseUrl, sisenseToken } = useMemo( () => ({ sisenseUrl: config.getOptionalString('sisense.url') ?? SISENSE_URL, sisenseToken: config.getOptionalString('sisense.token') ?? SISENSE_TOKEN, }), [config], ); // Extract current org from URL const currentUrl = window.location.href; const solutionOrg = useMemo(() => { const match = currentUrl.match(/\/system\/([^/]+)/); return match?.[1] ?? ''; }, [currentUrl]); // Map to Sisense org const mappedSisenseOrg = useMemo(() => { if (!solutionSisenseOrg || typeof solutionSisenseOrg !== 'object') return undefined; return (solutionSisenseOrg as SisenseOrgMapping)[solutionOrg]; }, [solutionOrg]); // State for dashboard filters const [filters, setFilters] = useState<any[]>([]); const [isLoading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); // Reset filters/loading when dashboardOid or tab changes useEffect(() => { setFilters([]); setLoading(true); setError(null); }, [dashboardOid, tab]); // Fetch dashboard filters useEffect(() => { if (!dashboardOid || !sisenseUrl || !sisenseToken) return; const fetchDashboard = async () => { try { const response = await fetch( `${sisenseUrl}/api/v1/dashboards/${dashboardOid}`, { headers: { Authorization: `Bearer ${sisenseToken}` } }, ); if (!response.ok) throw new Error('Failed to fetch dashboard'); const data = await response.json(); setFilters(data.filters || []); setLoading(false); } catch (err: any) { setError('Failed to load dashboard filters.'); setLoading(false); } }; fetchDashboard(); }, [dashboardOid, sisenseUrl, sisenseToken]); console.log('Fetched filters:', filters); // Memoize jaqlFilters const jaqlFilters = useMemo(() => { // Extract filters from levels const levelFilters = filters.flatMap( (filter: any) => filter.levels?.filter( (level: any) => level.table === 'projects' || level.table === 'products' || // (level.table === 'Business_Unit_Sols' && level.column === 'solution') || (level.table === 'rca_all_issues' && (level.column === 'solutions' || level.column === 'incident_date' || level.column === 'incident_date (Calendar)')) || (level.table === 'defect_metrics' && level.column === 'project_name') || (level.table === 'quality_scorecard' && level.column === 'filter_solution'), ) || [], ) || []; // Extract root jaql filters const rootJaqlFilters = filters .filter((filter: any) => filter.jaql) .map((filter: any) => filter.jaql) .filter( (jaql: any) => (jaql.table === 'rca_all_issues' && (jaql.column === 'solutions' || jaql.column === 'incident_date' || jaql.column === 'incident_date (Calendar)')) || (jaql.table === 'Business_Unit_Sols' && jaql.column === 'solution') || (jaql.table === 'projects') || (jaql.table === 'products') ); return [...levelFilters, ...rootJaqlFilters]; }, [filters]); // Memoize jaql, attribute, filterquery (always called, never conditionally) let hasValidFilter = null; let members = null; if (mappedSisenseOrg && mappedSisenseOrg[tab]) { if (typeof mappedSisenseOrg[tab] === 'string') { members = [mappedSisenseOrg[tab]]; hasValidFilter = Boolean( jaqlFilters.length && jaqlFilters[0]?.dim && mappedSisenseOrg && mappedSisenseOrg[tab], ); } else if (typeof mappedSisenseOrg[tab] === 'object') { hasValidFilter = Boolean( jaqlFilters.length && jaqlFilters[0]?.dim && mappedSisenseOrg && mappedSisenseOrg[tab] && mappedSisenseOrg[tab][dashboardOid], ); members = [mappedSisenseOrg[tab][dashboardOid]]; } } const jaql = useMemo( () => hasValidFilter ? { dim: jaqlFilters[0]?.dim, datatype: jaqlFilters[0]?.datatype || 'text', isDashboardFilter: jaqlFilters[0]?.isDashboardFilter || false, title: jaqlFilters[0]?.title, collapsed: jaqlFilters[0]?.collapsed || false, filter: { explicit: true, multiSelection: true, members: members ? members : [], }, } : null, [members, hasValidFilter, jaqlFilters], ); const attribute = useMemo( () => hasValidFilter ? { jaql: () => ({ jaql: { dim: jaqlFilters[0]?.dim, datatype: jaqlFilters[0]?.datatype || 'text', isDashboardFilter: jaqlFilters[0]?.isDashboardFilter || false, title: jaqlFilters[0]?.title, collapsed: jaqlFilters[0]?.collapsed || false, filter: { explicit: true, multiSelection: true, members: members ? members : [], }, }, }), serialize: () => ({ dim: jaqlFilters[0]?.dim, datatype: jaqlFilters[0]?.datatype || 'text', title: jaqlFilters[0]?.title, }), id: jaqlFilters[0]?.dim, } : null, [members, hasValidFilter, jaqlFilters], ); const filterquery: any = useMemo(() => { if (!hasValidFilter || !attribute || !jaql) return null; return filterFactory.customFilter(attribute as any, jaql as any, { disabled: false, }); }, [hasValidFilter, attribute, jaql]); if (!sisenseUrl || !sisenseToken) return null; if (!solutionOrg) return ( <div style={centerStyle as React.CSSProperties}> Could not determine organization from URL. </div> ); if (!mappedSisenseOrg) return ( <div style={centerStyle as React.CSSProperties}> No Sisense organization mapping found for "{solutionOrg}". </div> ); if (error) return <div style={centerStyle as React.CSSProperties}>{error}</div>; if (isLoading) return ( <div style={centerStyle as React.CSSProperties}> <div className="sisense-loader" style={{ marginBottom: 12 }}> {' '} Loading...{' '} </div> </div> ); if (!hasValidFilter || !filterquery) { return ( <div style={centerStyle as React.CSSProperties}> No valid filter found for this {tab} dashboard. </div> ); } const renderWidgets = () => ( <div> {widgetLists.map((widgetOid, idx) => ( <WidgetById key={widgetOid || idx} // Ensure key is never null widgetOid={widgetOid} dashboardOid={dashboardOid} includeDashboardFilters={false} filters={[filterquery]} /> ))} </div> ); let contentToShow: React.ReactNode = null; if (isWidgetRequired && widgetLists.length > 0) { contentToShow = renderWidgets(); } else if (!isWidgetRequired && widgetLists.length === 0) { contentToShow = null; } else { contentToShow = ( <SisenseDashboard filterquery={filterquery} dashboardOid={dashboardOid} /> ); } return ( <SisenseContextProvider url={sisenseUrl} token={sisenseToken}> {contentToShow} </SisenseContextProvider> ); }; --------------------- import React from 'react'; import { Dashboard, dashboardModelTranslator, useGetDashboardModel, useCustomWidgets, } from '@sisense/sdk-ui'; import CustomHistogramWidget from './CustomHistogramWidget'; interface SisenseDashboardProps { filterquery: any[]; dashboardOid: string; } const SisenseDashboard: React.FC<SisenseDashboardProps> = ({ filterquery, dashboardOid, }) => { const { dashboard } = useGetDashboardModel({ dashboardOid, includeFilters: false, includeWidgets: true, }); const { registerCustomWidget } = useCustomWidgets(); if (!dashboard) return null; const { title, widgets, layoutOptions, styleOptions, widgetsOptions } = dashboardModelTranslator.toDashboardProps(dashboard); registerCustomWidget('histogramwidget', CustomHistogramWidget); // console.log('Dashboard Props:widgets', widgets); // Add a non-null key property to each widget using its id console.log('Dashboard Widgets:', layoutOptions); const widgetsWithValidId = Array.isArray(widgets) ? widgets.map((widget: any, idx: number) => { let id = typeof widget?.id === 'string' && widget.id.trim() ? widget.id.trim() : `widget-${idx}`; if (!id) { console.warn('Widget missing id:', widget); id = `widget-${idx}`; } const widgetType = widget.widgetType || widget.type || widget.kind || widget.category || ''; const widgetProps: any = { ...widget, id, key: id}; if ( widgetType !== 'text' && widgetType !== 'TextWidget' && widgetType !== 'TEXT' ) { widgetProps.filters = filterquery; } else { delete widgetProps.filters; } return widgetProps; }) : []; return ( <div style={{ marginBottom: '24px' }}> <Dashboard id={dashboardOid} title={title} layoutOptions={layoutOptions} widgets={widgetsWithValidId} filters={filterquery} config={{ filtersPanel: { visible: false } }} styleOptions={styleOptions} widgetsOptions={widgetsOptions} /> </div> ); }; export default SisenseDashboard; ERROR: 1. dimension, [rca_all_issues.incident_date (Calendar)], was not found. rcas_and_related_actions {"dimension":"[rca_all_issues.incident_date (Calendar)]"} 2. Value cannot be null. (Parameter 'key') rcas_and_related_action122Views0likes7CommentsLast 4 digits formula
I am an ESO user and am trying to create a widget that will show only the final 4 digits of an incident number. The beginning of the incident numbers are not always the same but the last 4 are what I need. In excel I use =right(A2,4). I then use this list in excel with an =if formula (=if(c3-c2=1,"","missing") to show me any incident numbers that were not reported. If there is a way to make this part of the widget also, it would make my job that much easier. Thank you.57Views0likes4Comments/dashboards/admin not working on Linux 2025.3
Hello, We have been using Linux version 2025.2 and having no issues with the endpoint "/dashboards/admin". Now, we upgraded to 2025.3 and the "/dashboards/admin" endpoint is returning 500 error code 'Internal Server Error'. Other endpoints are working as expected. Wondering if there is a new endpoint to be using now, or some server setting we need to adjust. Appreciate the any assistance.56Views0likes3CommentsCalculated Value Filter in Widget Error
I'm trying to use CASE WHEN function in a calculated value to generate a filter for the Pivot Table Widget. This works in a bar chart, but I get an error when I try to use the same calculation and setup in a pivot table. Here are the details. The goal is to filter the table when cumulative % of total spend is less than or equal to 10%. Here I was able to tag all rows that are below 10% with the value 1. Then I use that to generate a filter "exclude all 0".140Views0likes6CommentsFiltering Orders by Grouped Filter Criteria with Optional Nulls
Afternoon SS community, We need a filtering mechanism in our business where users can apply filter groups to retrieve specific orders. Each filter group consists of three fields: location_id, category_id, and client_id. When a filter value is NULL, it should behave as a wildcard and match all values for that field. The goal is for users to select one or more filter groups at a time and retrieve all matching orders — without returning duplicates, even if the same order matches multiple groups. Currently, I can filter by individual fields, but I’m unsure how to structure things to support grouped filters like this without requiring users to select each field individually. The business requirement is to let users select entire filter groups, not individual filters. Sample Data: order_id location_id category_id client_id 1 1 1 1 2 1 1 2 3 2 2 3 4 2 3 4 5 2 1 4 Filter Group Examples: Group 1 location_id: 1 category_id: 1 client_id: 1 Expected Result: Order 1 only Group 2 location_id: 1 category_id: 1 client_id: NULL Expected Result: Orders 1 and 2 Group 3 location_id: 2 category_id: NULL client_id: NULL Expected Result: Orders 3, 4, and 5 How can I design a filtering logic that: Accepts multiple filter groups (with NULLs acting as wildcards) Returns all matching orders Avoids duplicate orders if they match more than one group Any guidance or sample query structure would be greatly appreciated! Thanks in advance.149Views0likes5CommentsSeeking Best Practice for Live Detail Reporting in Sisense (Replacing SSRS)
Afternoon Sisense community, Our team is looking to replicate the functionality of a crucial SSRS report within Sisense. This report is used by a department to obtain a detailed list of jobs for a specific month. The workflow involves: Running the report for a selected month (typically the current or previous month). Reviewing the output for discrepancies. Updating the source system based on the review. Re-running the report immediately to verify the changes (requiring live data). Current Sisense Implementation & Performance Issue I've attempted to recreate this report's dataset using a Live Model connected to a Redshift SQL View. The view is complex: It contains approximately 50 columns of detailed data. It involves JOINs across 15 different tables to consolidate all necessary dimensions and metrics. The Issue: The performance of this Live Model is unacceptable. Users are accustomed to the SSRS report running a stored procedure and returning the filtered data in under 30 seconds. My Sisense Live Model is timing out. Constraints & Goal Requirement: The data must be live (no ElastiCube, as users need immediate reflection of system changes after updates). Target Performance: Sub-30-second return for monthly filtered data. Request for Guidance Given the high number of columns, multiple joins, and the strict requirement for live data with fast filtering (specifically by month), what would be the recommended best practice for implementing this detailed report in Sisense? Are there specific Sisense configurations, data modeling techniques for live connections that would address this performance bottleneck while meeting the "live" requirement? Thank you for your insights!112Views0likes5Comments