Widget 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_action
DRay
·9 months agoHi kundankumar ,
I’m following up to see if the solution offered by steve workedharikm007 for you.
If so, please click the 'Accept as Solution' button on their post. That way others with the same questions can find the answer. If not, please let us know so that we can continue to help.
Thank you.
kundankumar
OP11 months agosteve Is there any hook which expose the dashboard data or schema of data like we have in example case
"import * as DM from '/sample-ecommerce';"
Steve Lewis
·11 months agokundankumar it's not officially supported at the moment, since it's marked as 'internal'.
But you can try using this and see if it works for you.
kundankumar
OP11 months agosteve I need to apply filters on both dashboards and widgets directly from my application. For example, I have different types of solutions, and based on the selected solution, I want to dynamically apply the relevant filters to the dashboard and widgets. Can you suggest possible solutions for this?
Steve Lewis
·11 months agokundankumar for creating filters to apply to Compose SDK components such as charts, queries, widgets and dashboards (via the `filters` prop) you should use the filterFactory from `@sisense/sdk-data` to create the type of filter you want.
The documentation and example here should help, plus i'll include some links to other examples .. both creating filters in code, and using the built in UI components (I understand you want to use your own, but being able to interact with the filter tiles, and see the result, might help)
kundankumar
OP11 months agosteve Is there a hook available that exposes the dashboard data schema, similar to how it works in the example import * as DM from '/sample-ecommerce” ?
Steve Lewis
·11 months agokundankumar it looks like your code is quite complicated, and trying to handle things that should not need to be handled (like fetching things directly from our REST API, manipulating JAQL, filters etc)
If you want to embed a dashboard, you can simply use the `DashboardById` component as a child of `SisenseContextProvider` as in this example
Similarly, if you just want widgets as individual components and not in a dashboard, then use WidgetById as in this example
If you want to first get the dashboard, make some changes and then render the result, you may prefer the `useGetDashboardModel` hook + `Dashboard` component combination as in this example
I see you have some of these elements in the code already, but also a lot more other stuff (registerCustomWidget, CustomHistogramWidget, api calls, jaql manipulation etc) which can make it confusing and hard to read / follow.
Hopefully the links help to simplify the approach for your intended use case... if you still get stuck using those then please reply back with a cleaner code example, the intended use case, and describe the problem you're facing.