Can Explanations be Relevant?
We are interested in rolling out Explanations to our clients, but I've hit a block and need some guidance. I think that the source of the issue may be our use of null replacement values in our data models. We don't allow data to join out of a query and instead ensure that all fact data joins to a null-replacement row in our dimensional tables. For example, given a fact table of product PURCHASES, imagine it was possible to sell those products at a discount in some orders. Assume that the fact table then joins to a dimension table of DISCOUNTS. Most purchases do not have a discount, but some do. Without a null-replacement row in the DISCOUNTS table, adding a field from that dimensional table onto a widget will limit the results in the widget to only fact rows with a discount. In our models, we would replace the null discount key in the fact table with a value that joins to a null-replacement row in the DISCOUNTS table. For example, replace null with -999999 in the fact table and have a row in DISCOUNTS with [id] = -999999 and a [description] = "(none)". The problem we're running into with Explanations is that the feature is presenting only the most useless information as being the most probable explanations. For example, when a single discount exists in the data set, then 99.9% of the PURCHASE rows have a "(none)" DISCOUNT, which is deemed this highest scoring field. More generally it seems that Explanations just reports back the most highly probably explanations as those fields where the member in the field is the commonly present in the data anyway, and it's just reflecting the raw change in fact data. For example, here's an image where the drop in revenue is explained by the drop in revenue from web sales (BRestAPI). But web sales constitute a larger over proportion of sales after the drop (89% up from 82% in the prior period). And another example in which an "Initiator Specified Flag" is whether or not an alternate order "owner" has been specified on a order. Specifying an alternat owner is allowed ("Y"), but not a common practice ("N"). Again, we're just seeing a reflection of sales in the explanation. Is there anything we can do to make the explanations discovered by Sisense relevant? The way it's functioning now, it would be better if Sisense DID NOT recommend possible explanations and just let the user Explore Other Fields immediately without having to wait for Sisense to return the list of completely irrelevant fields. I've tested this across many of our clients' data sets and found the same behavior. Hopefully there are options for us to improve the recommendations or maybe prevent it from recommending.98Views0likes2CommentsLast 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.113Views0likes5CommentsFiltering 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.239Views0likes6CommentsSeeking 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!191Views0likes6CommentsDashboard not auto-refreshing for mobile app performance data
Hi everyone, I’m using Sisense to track performance metrics from my mobile entertainment app — like daily active users, watch duration, and session time. Lately, I’ve noticed that some dashboards don’t refresh automatically, and I have to manually reload them to see updated results. I’ve already checked the data connector and refresh schedule, but everything looks fine there. Could this delay be due to caching, or maybe a dashboard setting I’m missing? Would appreciate any suggestions or settings to look into — thanks in advance!97Views1like4CommentsCASE WHEN Statements
We are using CASE WHEN statements for our customers looking to create their own booleans. We use live connection so these cannot be added to the elasticube. We are looking to complete the following: CASE WHEN [Timeline Type] = "emergency department" AND [Vital Systolic Blood Pressure] < 90 THEN 1 ELSE 0 END Additionally, customers would like to know the percentage of time the above is yes. Can this be completed in the function area or do we need to handle this in our transformation pipeline?775Views0likes1CommentHow to hide a row in a pivot table?
Hello. I am looking to hide (not filter out) a row based on its content in a pivot table widget. I haven't been able to find a native functionality for that, but I assume there's javascript to do that. Can anyone assist? I've only been able to find column masking, but that isn't helpful.164Views0likes6CommentsDashboard (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. Thanks64Views0likes4CommentsHow 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.109Views0likes3CommentsWidget 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_action194Views0likes7Comments