Forum Discussion

kundankumar's avatar
kundankumar
Data Storage
09-30-2025

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

6 Replies

  • steve's avatar
    steve
    Sisense Employee

    kundankumar​ 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.

  • steve​ 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's avatar
      steve
      Sisense Employee

      kundankumar​ 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's avatar
        kundankumar
        Data Storage

        steve​  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​  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's avatar
      steve
      Sisense Employee

      kundankumar​ 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.

      import { useGetDataSourceFields } from '@sisense/sdk-ui';
      import { DataSourceField } from '@sisense/sdk-data';
      
      const CodeExample = () => {
        const { dataSourceFields } = useGetDataSourceFields({
          dataSource: "Sample ECommerce",
        });
      
        return (
          <>
            {dataSourceFields?.map((field: DataSourceField) => (
              <div key={field.table + '-' + field.column}>
                {field.table}.{field.column} : {field.dimtype}
              </div>
            ))}
          </>
        );
      };
      
      export default CodeExample;