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