Skip to main content
Sisense Community logo
  • Community Feedback
  • Chapters
  • Events
  • Forums
    • Help and How To
    • Product Feedback Forum
    • Strategy & Use Cases
  • Blogs
  • KB Docs
    • KB Docs
    • Add-Ons & Plug-Ins
    • APIs
    • Best Practices
    • Blox
    • CDT
    • Cloud Managed Service
    • Data Models
    • Data Sources
    • Embedding Analytics
    • How-Tos & FAQs
    • Onboarding
    • PySisense
    • Security
    • Sisense Administration
    • Sisense Intelligence & AI
    • Troubleshooting
    • Widget & Dashboard Scripts
  • Support
  • Learning
    • Sisense Academy: Free Courses and Certifications
    • Official Developer Documentation
    • Official Product Documentation
    • Official Sisense Youtube Channel
    • Sisense Compose SDK Playground
  • Use Case Gallery
All PostsDiscussionsBlogsIdeasQuestions
Leaderboards
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
  •                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
Discussions
  • TagsChevronRightIcon
Compose SDK
  • Blog banner
    • Embedding AnalyticsChevronRightIcon

    Styling Existing Sisense Widgets with ComposeSDK's WidgetById

                                                                             

    Styling Existing Sisense Widgets with ComposeSDK's WidgetById When embedding an existing Sisense dashboard widget in a ComposeSDK application, there are three general approaches, each with a different tradeoff between speed and how much of the widget can be changed afterward. WidgetById renders the widget directly by its dashboard and widget ID, and stays connected to the live Fusion widget definition. Building a native widget from scratch, optionally using an existing widget as a visual reference, produces a fully independent ComposeSDK widget with every data option editable, at the cost of doing that work by hand. Between those two, useGetWidgetModel paired with widgetModelTranslator pulls an existing widget's definition from Fusion and converts it into props for a native widget component, automating what would otherwise be manual recreation. This article covers the first approach, WidgetById , and specifically the large set of visual styling options it exposes through its styleOptions prop. The other two approaches are noted briefly near the end for context, not covered in depth here. WidgetById was previously named DashboardWidget . That name was deprecated in ComposeSDK 1.23.0 and removed entirely in 2.0.0, along with the matching DashboardWidgetProps and DashboardWidgetStyleOptions types, which became WidgetByIdProps and WidgetByIdStyleOptions . Anyone maintaining code or working from an older tutorial that still references DashboardWidget is working against a removed component, not a currently deprecated one, and needs to migrate. What WidgetById does and does not do WidgetById takes a dashboardOid and a widgetOid , both required, and renders that widget as it exists on the connected Fusion instance. It supports a handful of props for scoping the widget further, additional filters and highlights , a filtersMergeStrategy for how those combine with the dashboard's own filters, and callbacks like onDataPointClick for interaction, but it does not expose the widget's underlying data options. Its dimensions, measures, and chart type stay whatever they are on the Fusion dashboard. That tradeoff is what makes it fast to use and what makes the other two approaches worth knowing about for cases that need more control. Styling and visual configuration are handled through two separate props rather than one. styleOptions covers the visual styling this article focuses on, and a newer config prop covers widget behavior, such as which actions appear in the widget's header menu, described in its own section below. Styling with styleOptions The following example renders a widget and sets every field currently documented on WidgetByIdStyleOptions . import { WidgetById } from '@sisense/sdk-ui'; export function ComposeSDKWidget() { const styleOptions = { backgroundColor: 'lightblue', border: true, borderColor: 'green', cornerRadius: 'Large', shadow: 'Dark', spaceAround: 'Large', header: { hidden: false, titleAlignment: 'Center', backgroundColor: 'lightblue', titleTextColor: 'blue', }, height: 200, width: 200, }; return ( <WidgetById widgetOid="65ab8958857ff900335db870" dashboardOid="65ab8948857ff900335db86e" styleOptions={styleOptions} title="My Chart" /> ); } Each field, its type, and what it controls: backgroundColor (string, an HTML color) sets the background color of the widget itself. border (boolean) turns the widget container's border on or off. borderColor (string, an HTML color) sets that border's color, when enabled. cornerRadius ("Large", "Medium", "Small", or "None") sets the corner radius of the widget container, similar to the CSS property of the same name. shadow ("Dark", "Light", "Medium", or "None") sets the shadow level of the widget container. It only has a visible effect when spaceAround is also set to something other than "None". spaceAround ("Large", "Medium", "Small", or "None") sets the space between the widget container's edge and the chart inside it. height and width (number, in pixels) set the widget's fixed size. Left unset, the widget fills its container instead. header (object) groups the header and title styling below it. header.hidden (boolean) toggles the header and title's visibility entirely. header.titleAlignment ("Left", "Center", or "Right") sets where the title sits in the header. header.backgroundColor (string, an HTML color) sets the header's background color. header.titleTextColor (string, an HTML color) sets the title's text color. header.dividerLine (boolean) toggles a divider line between the header and the chart. header.dividerLineColor (string, an HTML color) sets that divider line's color, when visible. This is the same field set the older DashboardWidgetStyleOptions documented, with three additions since, a "None" option on cornerRadius , shadow , and spaceAround , and a "Right" option on header.titleAlignment . Nothing was removed from the older type. Beyond styling, the config prop WidgetById also accepts a config prop, of type WidgetConfig , which did not exist on the old DashboardWidget component. Where styleOptions controls appearance, config controls widget behavior, for a chart widget specifically that currently means: actions.downloadCsv.enabled and actions.downloadExcel.enabled toggle whether "Download as CSV" and "Download as Excel" appear in the widget's own header menu. header (a separate object from the one under styleOptions , this one typed as WidgetHeaderConfig ) controls which items are available in that header menu. narrative configures the widget's narrative text, where applicable. Setting id on the widget is worth doing alongside config , since the Excel download action specifically depends on it to identify the widget being exported. A note on a later change to WidgetById itself ComposeSDK 2.3.0 refactored WidgetById to apply the dashboard's own palette to it by default, and changed its return type from a plain React element to one that can also resolve asynchronously. There may be other slight changes to visual appearance by default between the new and old component. The other two approaches For a widget that needs its data options changed, not just its appearance, WidgetById is not the right tool. Two other approaches exist. Building the widget natively means constructing a ChartWidget , or the more general Widget facade component, directly, setting its chart type and data options in code, optionally using an existing Fusion widget as a visual reference while building it. The result has no ongoing connection to any Fusion widget or dashboard. useGetWidgetModel automates part of that process. It is a React hook that retrieves an existing widget's model from a connected Fusion instance, given the same dashboardOid and widgetOid used above. Its result can be converted into props for a native widget component with a function from the widgetModelTranslator namespace, such as widgetModelTranslator.toWidgetProps , then rendered with <Widget {...widgetProps} /> . This produces a fully native, editable widget without hand copying its data options, at the cost of needing that initial Fusion connection to retrieve them. Useful links WidgetById reference WidgetByIdProps reference WidgetByIdStyleOptions reference useGetWidgetModel reference ComposeSDK 2.0.0 migration guide ComposeSDK changelog

    Jeremy Friedel
    Jeremy FriedelPosted 3 weeks ago
    0
             
  • Blog banner
    • Embedding AnalyticsChevronRightIcon

    Using ComposeSDK Planning Documents: Guiding an AI Coding Assistant with a Written Planning Document

                                                                     

    Using ComposeSDK Planning Documents: Guiding an AI Coding Assistant with a Written Planning Document From Zero to ComposeSDK describes building a ComposeSDK application one prompt at a time, and that approach scales well. Adding one page, one widget, one filter at a time, each described on its own and confirmed before moving to the next, works for a small dashboard and for a larger one, provided each addition is independent and the person directing the assistant is already thinking through the pieces one at a time. This article describes a different way of working with an AI coding assistant. It is not strictly better than the approach in that article, simply different. Rather than prompting through a ComposeSDK build step by step, the assistant drafts a written plan first. That plan gets revised over a few rounds while nothing has been built yet, and only then does the assistant implement most or all of it in one longer working session. It is a more involved process than the one in From Zero to ComposeSDK, worth using when enough of a project is already decided in advance that writing it down once is less effort than describing it prompt by prompt, not a step every ComposeSDK build needs. Any modern LLM based coding assistant can drive this workflow the same way it drives the one described in From Zero to ComposeSDK . It needs the same two capabilities, editing files in the project folder and running terminal commands. This article mostly describes Claude Code as an example, but nothing about a written planning document is specific to it, and the same steps apply with another agentic editor, a standalone CLI assistant, or a desktop app with terminal access. This article does not cover installing the editor, the assistant, Node, or ComposeSDK itself, and it assumes the setup from From Zero to ComposeSDK is already in place, a scaffolded React project with the ComposeSDK packages installed and a .env file holding the Sisense instance URL and API token. It builds on that setup rather than repeating it. A planning document does not remove the need to review the assistant's work. It moves most of that review earlier, into the document, before the assistant starts writing code against it, rather than after each small step. A plan reviewed carefully before implementation catches a wrong field name or a missing page as a one line edit. The same mistake caught after a long stretch of implementation can require changes in many different places, and costs considerably more time to fix. What goes Into the Planning Document A planning document for a ComposeSDK build works best as a plain markdown file kept in the project, for example plan.md in the project root, rather than something that only exists in the chat history. As a file on disk, it can be opened and edited directly, referenced again in a later session, and it survives a long session's context being condensed in a way that conversation history alone does not. The document does not need a fixed template, but it generally works better when it covers a few things beyond a page and widget list. What the application is for, who uses it, and what habit or decision it supports. Which ComposeSDK flavor the project uses, React, Angular, or Vue, since that decides the package names and component syntax everything else in the plan assumes. The data model involved, referencing the .ts file already generated by the ComposeSDK CLI, or naming the data source if it still needs generating, along with why the fields involved matter to that audience, not just their names. Each page or view and its widgets, specific enough to build from, naming the chart type, the dimensions and measures, and any filters. Interactivity between widgets or pages, such as a filter or a click on one page affecting another. Layout and visual style, to whatever level of detail is already decided, colors, branding, density. Where the Sisense URL and token is and how the assistant checks its own connection without viewing or saving the authentication token, covered in its own section below. A milestone checklist, ordered the way the build should proceed, written as markdown checkboxes so the assistant can mark each one complete as it finishes. Anything explicitly out of scope, so the assistant does not add it unasked while working through a long stretch unsupervised. The checklist carries the most weight during implementation. A long LLM working session eventually has its earlier history summarized, and a agent's summarization keeps only a handful of the most recently read files in full alongside the summary. A checklist file the assistant is instructed to re-open at the start of each milestone stays accurate regardless of how much of the conversation itself has been condensed, because the current state of the build lives in the file rather than in memory of the conversation. Drafting the plan The first cycle is a conversation, not a single prompt. Describing the project and asking for a draft is enough to start. The prompts throughout this article are rough examples of the general tone and type of instruction, not meant to be copied directly. The right wording depends on the specific ComposeSDK application being built. Draft a planning document for a new React ComposeSDK application in this folder, save it as plan.md. It's for the regional sales team, replacing three spreadsheets they currently cross-reference by hand before the weekly pipeline review, built on the Sample ECommerce data model, which mirrors what's in those spreadsheets. Use ComposeSDK's ExecuteQuery function to look at the actual values in a column if that would help, not just the field names in the schema file. The token for authentication is in .env. Cover the purpose, why the data matters to this audience, each page and its filters, any interactivity between pages, a rough visual style, and a milestone checklist. List anything you're unsure of as open questions in its own section rather than guessing. That last instruction matters. An assistant asked to draft a plan will otherwise fill a gap with a guess that sounds reasonable rather than flagging it, and a guess buried in a paragraph of prose is easy to miss on a first read. A dedicated "Open questions" section in the draft is easy to scan and resolve before moving on. Revising the plan across cycles The draft is rarely final on the first pass. Revising it happens either by editing the markdown file directly, or by describing the change and letting the assistant update the file. In plan.md, change the regional breakdown page to a map visualization instead of a bar chart, and add a country filter UI that applies across all pages. Update the milestone checklist to match. Either editing style works, and most planning sessions mix both, a person adjusting a sentence directly while asking the assistant to work out the consequences elsewhere in the document, such as keeping the checklist in sync with a changed page list. As many cycles as needed happen before implementation starts. Nothing has been built yet, so a revision at this stage costs a paragraph, not a refactor. Keeping the connection working while the build runs The plan should note where the Sisense URL and token live, .env , and that the assistant's own read access to it is denied, the setup already covered in From Zero to ComposeSDK. That protection does not need re-explaining here, the plan only needs to point at it. What is worth adding for a longer, less supervised run is two different checks, since they answer different questions. Rotating the token or changing the URL is the only thing that actually requires re-testing whether the assistant can still reach Sisense at all. Reusing the same ComposeSDK CLI command already used to generate the data model file, pointed at a throwaway output path, is a reasonable way to confirm that without the assistant ever seeing the token value, since the script reads .env at run time on its own rather than through a tool call the assistant's file permissions would block. // scripts/check-credentials.mjs // Confirms Sisense credentials still authenticate, without printing // the token. Run with: node --env-file=.env scripts/check-credentials.mjs import { execFileSync } from 'node:child_process'; const url = process.env.VITE_SISENSE_URL; const token = process.env.VITE_SISENSE_TOKEN; try { execFileSync( 'npx', [ '@sisense/sdk-cli', 'get-data-model', '--url', url, '--token', token, '--dataSource', 'Sample ECommerce', '--output', 'scratch/credential-check.ts', ], { stdio: 'ignore' }, ); console.log('Sisense credentials OK'); } catch { console.log('Sisense credential check FAILED'); } This is worth running once after setup, and again only if the plan notes that .env has changed. Running it after an ordinary milestone, like adding a widget, confirms nothing new, since nothing about the credentials changed either. This is for the assistant to run on its own, a person does not need to run it by hand. What the assistant will almost certainly check on its own, once there is a widget to look at, is whether it renders the way the plan describes, and that is a visual check, not a credential check. If the session has a browser automation tool connected, a Playwright MCP server is a common example, the assistant can open the running dev server itself, take a screenshot, and confirm the new chart or page looks right. Asked to verify a milestone and given a way to see the running app, most assistants will reach for exactly this on their own, without needing the mechanism spelled out. Without a connected browser tool, this check still means a person glancing at the running app in a browser, the same as the smoke test described in From Zero to ComposeSDK. If the assistant asks partway through to install a browser automation tool, whether as a yes or no prompt or a plain request, it is usually worth approving. Iterative development goes far better when the assistant can see what its own code produces instead of just describing it. The plan's connection section can state this plainly, without prescribing how. URL and token in .env, read access denied per project settings. Re-run scripts/check-credentials.mjs only if these values change. After each milestone, confirm the change actually renders correctly in the browser. Choosing a permission mode for the implementation run Claude Code, used here as the concrete example, cycles through a few permission modes with Shift+Tab, and the mode in use during implementation determines how often the assistant stops to ask before acting. Manual (the default) asks before every file edit and most shell commands. It suits the planning cycles above, where little is being written yet, but is not practical for a long implementation run, since it interrupts constantly. Accept Edits auto-approves file edits and common filesystem commands, while still asking before other shell commands, such as installing a package or running a build, unless those have already been allow-listed. It is a reasonable default for working through a reviewed plan. Code changes stop interrupting, while a command run for the first time still gets a look. Auto goes further, approving tool calls generally with a background safety check evaluating each action against what was asked, rather than a person reviewing each one. It suits a long stretch of implementation against a plan that has already been reviewed carefully, since there are fewer opportunities to catch a problem as it happens. Bypass Permissions ( --dangerously-skip-permissions at startup) skips prompts almost entirely. It is documented as intended for use inside a container or VM the assistant cannot otherwise damage, not on a developer's own machine. Since the setup this article builds on keeps a live Sisense token in .env on that same machine, this mode is out of scope here. Other LLM's have very similar permission modes. Whichever mode is active, deny rules are checked before any mode grants approval, so the .env protection from From Zero to ComposeSDK stays in effect through Accept Edits and Auto mode as well. Commands already known to be safe and expected by the plan, running the dev server, running tests, regenerating the data model, can be allow-listed directly in .claude/settings.json (or equivalent for your LLM) so they stop prompting even once, while everything else continues to ask. { "permissions": { "allow": [ "Bash(npm run *)", "Bash(npm test *)" ], "deny": [ "Read(./.env)", "Read(./.env.*)" ] } } A Stop hook, configured the same way in settings.json , is a further option worth knowing about. It runs a chosen shell command each time the assistant finishes responding, which can be pointed at the credential check script so it runs automatically after any milestone that touches .env , rather than depending on the assistant remembering the instruction in the plan. The Claude Code hooks documentation is linked below. Other LLM code assistants have similar features. Handing off the finished plan Once the plan reads correctly end to end, implementation itself is one request. Work through plan.md from top to bottom. After finishing each item on the milestone checklist, check it off in the file, confirm the change renders correctly in the browser, and report the result before starting the next item. Re-read plan.md at the start of each milestone rather than relying on memory of earlier parts of this conversation. Stop and ask only when a decision is not covered by the plan. The instruction to re-read the file matters on a long session. It is what keeps the assistant's sense of what is done and what remains accurate even after earlier parts of the conversation have been condensed. Resuming and checking status Because the plan and its checklist live on disk, a new session, or the same session after a break, can pick up where the last one left off. Read plan.md and tell me which milestones are checked off, what remains, and whether any of the finished ones still need a browser check before I can consider them done. This works whether the pause was intentional or the result of the assistant stopping to ask about something the plan did not cover. Example planning document The following is a shortened example of what a finished plan looks like before implementation begins, for a small internal ComposeSDK app. # Regional Sales Pulse *Sisense ComposeSDK Planning Document* ## Purpose Built as a React ComposeSDK application for the regional sales managers who currently pull this picture together from three spreadsheets before the weekly pipeline review. The app should answer, at a glance, whether a region or category is trending up or down. It exists specifically for that meeting, and is meant to make it faster and more informative. ## Data model and what it contains Sample ECommerce (src/models/sample-ecommerce.ts). Revenue and Units are the two figures managers actually watch weekly. Category and Country are the two dimensions they currently cross-reference by hand for trends. Condition (New/Used) does not matter here and should not appear on any page unless someone asks for it later. ## Pages, filters, and interactivity 1. Overview. Revenue and Units by month, column chart. This is the page a manager opens first, so it should load with the current calendar year already selected. 2. Regional Breakdown. Revenue by Country, map visualization. 3. Product Performance. Revenue by Category, ranked bar chart, highest to lowest. Two filters sit at the top of the app and apply across all three pages, a date range defaulting to the last two weeks, and a country selector. Both should be visible without opening a menu. ## Visual style Matches the internal tools intranet look, navy header, white background, no dark mode needed for this audience. Cards with some padding around each chart rather than charts running edge to edge. Nothing more elaborate than that is expected here. ## Connection and verification URL and token in .env, read access denied per project settings. Re-run scripts/check-credentials.mjs only if these values change. After each milestone, confirm the change actually renders correctly in the browser. ## Milestones - [x] Scaffold three page routes with placeholder headers and the shared filters wired to nothing yet - [ ] Overview page with Revenue and Units by month - [ ] Regional Breakdown page with Revenue by Country as a map - [ ] Product Performance page with Category ranked by Revenue - [ ] Shared date range and country filters applied across all three pages - [ ] Navigation between the three pages - [ ] Visual pass matching the style notes above ## Testing Add unit and integration tests where they make sense, and skip them where they don't. Use headless browser screenshots for visual checks, the same way each milestone gets confirmed in the browser. Confirm at least once, early on, that a real query actually returns data, since the credential check script only proves the token authenticates, not that a query returns rows. ## Out of scope No user accounts or role management beyond what Sisense already provides. No PDF export or scheduled email in this version. Condition does not appear anywhere unless a later request asks for it. ## Open questions - Should the country filter support selecting more than one country at once? Left single select for now, since that already matches what the spreadsheets show today. Prompt library Drafting and revising. Draft a planning document for [project description] as a [React/Angular/Vue] ComposeSDK application, saved as plan.md. Cover the purpose, why the data matters, which ComposeSDK flavor it uses, each page and its filters, any interactivity, a rough visual style, and a milestone checklist, with open questions listed separately. Use ExecuteQuery to check the actual values in [columns] before finalizing that page in the plan, not just the field names in the schema file. In plan.md, change [specific details] and update the milestone checklist to match. Review plan.md and flag anything ambiguous enough that you would have to guess during implementation. Connection and verification. Add scripts/check-credentials.mjs as described in plan.md, and note that it only needs to run again if the .env values change. After this milestone, confirm it renders correctly in the browser before checking it off. Settings for a longer run. Add an allow rule to .claude/settings.json (or equivalent for the LLM Code Assistant you or using) for [command], so it stops prompting for that one going forward. Add a Stop hook in settings.json that runs scripts/check-credentials.mjs automatically whenever .env changes. Handoff and resumption. Work through plan.md from top to bottom, checking off each milestone as it's finished, confirming each one in the browser, and re-reading plan.md at the start of the next. Stop only for decisions the plan does not cover. Read plan.md and report which milestones are done, what remains, and which finished ones still need a browser check. Useful links From Zero to ComposeSDK ComposeSDK documentation ComposeSDK ExecuteQuery reference Claude Code permissions documentation Claude Code hooks documentation ComposeSDK Github Monorepo Sisense CSDK Github Skills Examples Sisense MCP Github Server Sisense REST API and authentication documentation A written planning document is worthwhile when a application design and purpose is already decided in enough detail to write down once. A application still being thought out piece by piece is usually still faster to build the direct way, one prompt, one page, one widget at a time.

    Jeremy Friedel
    Jeremy FriedelPosted 1 month ago
    0
             
  • Blog banner
    • Embedding AnalyticsChevronRightIcon

    Debugging Web Access Token (WAT) Issues with ComposeSDK

                                                                                                                             

    Debugging Web Access Token (WAT) Issues with ComposeSDK In initial testing, developers embedding Sisense dashboards and widgets with ComposeSDK sometimes find that a Web Access Token (WAT) passed to the SisenseContextProvider component through the wat prop fails to authenticate, and dashboards or widgets do not load. This article describes the steps for isolating the cause, starting with confirming the Sisense license includes WAT at all, then working through the token's own configuration. Confirm the Sisense license includes WAT Before debugging the token itself, confirm that the WAT works directly against Fusion, outside of ComposeSDK. Some Sisense licenses do not include WAT as a feature, and a token generated on a server without WAT licensed will not work in ComposeSDK regardless of how it is configured. WAT is also incompatible with the Sisense Multitenancy feature. Test the token directly against Fusion using the following URL pattern, replacing the placeholders with the organization's Sisense URL, the generated token, and the target dashboard or widget ID: https://mysisense.com/wat/insert_your_generated_token/app/main#/dashboards/dashboard_id https://mysisense.com/wat/insert_your_generated_token/app/main#/dashboards/dashboard_id/widgets/widget_id If this URL returns an error with status code 403 and a message stating that the license is turned off, the Sisense license does not currently include WAT. The organization's Sisense account representative can discuss adding WAT to the agreement. Until WAT is added, see "Use alternative authentication when WAT is not available" below for other options to continue development and testing.

    Status Message Screenshot
    Full documentation on WAT is available on the Using Web Access Tokens page. Validate the WAT's own claims If the license includes WAT, confirm the token payload itself is correctly structured before testing in ComposeSDK. These checks apply whether the token is being tested in Fusion, other forms of embedding, or in ComposeSDK. Confirm the token is valid using the "Test Existing Token" function, described on the Using Web Access Tokens page, in the Sisense Admin panel. This runs structure, logic, and data validation together, so it can catch an invalid "sub" user, theme, or dashboard ID in a single check before working through the items below individually.
    Confirm the WAT works when tested directly against Fusion, using the URL pattern from the license section above, if this has not been tested yet. This confirms the token's claims work outside of ComposeSDK before assuming the problem is in the token itself. Confirm the "sub" claim in the token is a valid, existing user id on the server. The current logged in user's user ID can be retrieved from the browser developer console with: prism.user._id
    Prism User ID Console Command
    Confirm the user id in the "sub" claim has access to the relevant data sources. Confirm any theme id included in the token exists on the server. Theme ids can be checked with the List Themes endpoint in the REST API. As a test, try generating a token with no theme id set. Confirm the token's start and end unix timestamps are correct, and that the current unix time falls between them. The site unixtimestamp.com can be used to check this. If dashboard or widget ids are used, confirm those ids are included in the token. Remove any parameters from the WAT that are not strictly required. The number of required parameters is smaller than what default token generation includes. Confirm the secret (public key) used matches the token configuration referenced by the "kid" in the token's header. A secret from a different token configuration produces the error "Invalid public key." If the payload includes large "prm", "res", "flt", or "acl" claims, confirm their combined character count does not exceed 81,200 characters per token. This limit is rarely reached, but can occur with very large permission or filter lists. Isolate whether the failure is specific to ComposeSDK If the token work's in Fusion and other form's of Sisense embedding and passes all checks, but WAT still fails only when used through ComposeSDK, and not through Fusion, the cause is likely in the ComposeSDK application itself rather than the token. Test with a known good token, manually pasted in as a temporary replacement to any variable based structure, to confirm whether the issue is in this particular token or in the surrounding code. If testing with a dashboard id has not worked, try testing with a ComposeSDK widget defined directly in CSDK code, with no dashboard or widget ID server dependency, to rule out an ID mismatch. Test on a blank localhost page with a minimal ComposeSDK implementation, to rule out interference from other libraries in the application. Use alternative authentication when WAT is not available If the Sisense license does not include WAT, ComposeSDK development and testing can continue using other authentication methods. A viewer role user, or higher, is all that either option requires: A bearer token SSO Organizations interested in adding WAT to their license should contact their Sisense account representative.

    Jeremy Friedel
    Jeremy FriedelPosted 2 months ago
    0
             
  • Blog banner
    • Embedding AnalyticsChevronRightIcon

    [Linux]: Accessibility in CSDK

             

    This article explains how accessibility (a11y) is supported in Sisense and how you can build more accessible analytics applications using the Sisense Compose SDK. Accessibility is not just about supporting users with disabilities. It also improves the experience for keyboard users, users with temporary injuries, older users, and anyone who benefits from a more usable interface. Compose SDK already provides accessibility support for several built-in components, but building an accessible application is a shared responsibility between the SDK and the developer. What is Accessibility (a11y)? Accessibility (often abbreviated as a11y , because there are 11 letters between "A" and "Y") is the practice of building applications that everyone can use, including people with visual, hearing, motor, or cognitive disabilities. One of the most important accessibility standards is the Web Content Accessibility Guidelines (WCAG) , which defines recommendations for making web content more accessible. Since Compose SDK is used to build web applications, following these guidelines helps ensure your dashboards are usable by as many people as possible. WCAG defines three levels of conformance: Level A The minimum accessibility requirements. Level AA The recommended level for most web applications. This is the level we'll focus on throughout this article. Level AAA The highest accessibility level. It includes additional requirements that are often difficult or unnecessary for most business applications. Accessibility in Sisense Fusion There are several ways to improve accessibility when using Sisense Fusion. The Accessibility App is an external plugin created by QBeeQ that provides multiple accessibility settings and customization options. The Accessibility Button Plugin , created by Paldi, adds a toggle that changes supported visualizations into a more screen-reader-friendly experience. These are currently the primary accessibility options available for Fusion.

    Another important improvement is enabling the New Side Navigation , which already includes better keyboard navigation and accessibility support. Admin → Feature Management → Management If you're embedding Fusion dashboards, enabling CSDK Mode can also improve accessibility for navigation and UI interactions. However, if your goal is to achieve WCAG Level AA , embedding your analytics using Compose SDK generally provides greater flexibility and control over accessibility. Embedding Analytics The two most common ways to embed Sisense analytics are: IFrame and Compose SDK. Using an IFrame is not an accessibility issue by itself. However, the embedded Fusion application must also be accessible, since users are interacting with the Fusion interface. Compose SDK gives developers much more control over accessibility because the surrounding application is built entirely with React. Accessibility in Compose SDK Compose SDK already provides accessibility support for many built-in components, especially charts powered by Highcharts.
    However, accessibility is a shared responsibility. Compose SDK provides: Built-in accessibility support for Highcharts Theme customization Accessible React components Integration with semantic HTML Developers are still responsible for: Building semantic page structures Keyboard navigation for custom components Focus management Color contrast Labels and descriptions Accessible custom widgets Testing accessibility The following concepts are the foundation of building accessible Compose SDK applications. Core Accessibility Concepts Semantic HTML Semantic HTML is one of the most important accessibility principles. Instead of generic elements like div , use HTML elements that describe their purpose, such as: main, navm section, article, header, footer, button Semantic HTML helps browsers, screen readers, and assistive technologies understand the structure of your application. Whenever possible, prefer semantic HTML instead of recreating its behavior with JavaScript. Keyboard Navigation Not every user interacts with your application using a mouse.nUsers should be able to navigate your application using only the keyboard. Common keyboard interactions include: Tab: Move to the next interactive element Shift + Tab: Move to the previous element Enter: Activate buttons and links Space: Activate controls Arrow Keys: Navigate lists, menus, or tables Home, End: Jump to the first or last item Escape: Close dialogs or menus Proper keyboard navigation is one of the easiest ways to improve accessibility. Focus Management Keyboard users always need to know which element currently has focus. Every interactive component should display a visible focus indicator. When opening dialogs or modals: Move focus into the dialog. Keep keyboard focus inside while it is open. Return focus to the triggering element when the dialog closes. Good focus management makes applications significantly easier to navigate. Screen Readers Screen readers allow blind and visually impaired users to navigate applications. They rely on semantic HTML, labels, headings, and descriptions to understand the interface. Using the correct HTML elements is usually enough for many components, but custom components should also provide meaningful labels and descriptions where necessary. ARIA ARIA (Accessible Rich Internet Applications) provides additional information to assistive technologies when semantic HTML alone is not sufficient. Common ARIA attributes include: aria-label aria-labelledby aria-describedby As a general rule: Prefer semantic HTML whenever possible. Use ARIA only when native HTML cannot provide the required accessibility information. Color Contrast Users should be able to distinguish text, icons, and interface elements regardless of visual impairments. Follow the WCAG recommendations for color contrast to ensure your application remains readable. Also avoid communicating information using color alone. For example, instead of showing only a red indicator for an error, include an icon or descriptive text as well. Enabling CSDK Accessibility Compose SDK uses Highcharts to render most chart visualizations. Highcharts includes built-in accessibility features such as: Keyboard navigation, Screen reader descriptions, Accessible chart labels, Improved chart navigation These features can be enabled through the SisenseContextProvider . return ( <SisenseContextProvider url={connection.url} token={connection.token} appConfig={{ accessibilityConfig: { enabled: true, }, }} > <YourApp /> </SisenseContextProvider> ) Once enabled, users can navigate supported charts using only the keyboard.
    This configuration also applies to the DashboardById component, but only for widgets rendered using Highcharts. Pivot and Indicator widgets use different rendering implementations, so this configuration does not affect them. Accessibility for Custom Components When building your own React components, accessibility becomes entirely your responsibility. For example, you can implement keyboard navigation inside a custom table: function handleRowKeyDown( e: React.KeyboardEvent<HTMLTableRowElement>, index: number ) { switch (e.key) { case "ArrowDown": e.preventDefault() focusRow(index + 1) break case "ArrowUp": e.preventDefault() focusRow(index - 1) break case "Home": e.preventDefault() focusRow(0) break case "End": e.preventDefault() focusRow(data.length - 1) break } }
    React makes it straightforward to implement accessible keyboard interactions for custom components. Color Contrast and Themes Compose SDK themes allow you to customize colors across your application. Creating a theme with WCAG compliant color helps improve readability and accessibility throughout your application.
    If you're using DashboardById , the situation is slightly different. DashboardById inherits the theme configured for that dashboard inside Fusion. For Compose SDK widgets rendered with Highcharts, you can also use onBeforeRender to apply additional Highcharts accessibility configurations before rendering. <ColumnChart ... onBeforeRender={(highchartsOptions) => { highchartsOptions.title = { text: "Revenue by country" } highchartsOptions.accessibility = { ...highchartsOptions.accessibility, description: "Bar chart showing total revenue for each country, filtered by the current report options.", point: { ...highchartsOptions.accessibility?.point, descriptionFormatter: (point) => `${point.category}: revenue of ${point.y}`, }, } return highchartsOptions }} />
    highchartsOptions.title : adds the title in the chart. highchartsOptions.accessibility : here we're spreading the a11y object, so we can keep the Highcharts config, and add anything that we want. description : it's not visible in the screen, but help the screen reader. point : we also are keeping the default using spread, but we're putting a description for each data point in the chart. You can see more about this configs inside Highcharts A11y Module Documentation . Testing Accessibility After implementing accessibility improvements, it's important to verify that everything works correctly. One of useful tool is: Lighthouse.
    **You should also test your application using only the keyboard to ensure users can complete every task without using a mouse. Summary of the Best Practices Prefer semantic HTML over ARIA whenever possible. Ensure every interactive element is keyboard accessible. Always provide a visible focus indicator. Maintain sufficient color contrast. Don't rely only on color to communicate information. Add meaningful labels to custom controls. Test using both automated tools and screen readers. Test your application using only the keyboard. Conclusion Accessibility is not a feature that can simply be enabled with a configuration option. Building accessible analytics experiences requires combining semantic HTML, keyboard navigation, focus management, screen reader support, sufficient color contrast, and accessible custom components. Compose SDK provides many of the building blocks needed to create accessible applications, but achieving WCAG compliance ultimately depends on how your application is designed and implemented. By following these best practices, you can build dashboards that are more inclusive, easier to use, and accessible to a much wider audience. You can also access the demo through this url . And the repository in Github . References/Related Content  Highchart documentation CSDK Theme Documentation CSDK Sisense Context Provider API Disclaimer: This post outlines a potential custom workaround for a specific use case or provides instructions regarding a specific task. The solution may not work in all scenarios or Sisense versions, so we strongly recommend testing it in your environment before deployment. If you need further assistance with this, please let us know.

    Micael Santana
    Micael SantanaPosted 2 months ago
    0
             
  • Blog banner
    • News & UpdatesChevronRightIcon

    Compose SDK 2.30.0 is here!

                     

    Compose SDK 2.30.0 is here! Widget Plugins reach public beta, a new Sankey chart joins the visualization library, and queries and filtering gain powerful new options. Why: Customers want to extend Compose SDK with their own visualizations and get richer control over their data. This release opens plugin-based customization to everyone and adds tools to surface more insight with less code. Released on June 23, 2026. What's New in 2.30.0 • Widget Plugins are now in public beta — build and register custom widgets to extend dashboards with your own visualizations. • New SankeyChart component (beta) — visualize flows and relationships between stages in React. • New includeRowCount query option (beta) — retrieve the total row count alongside results, ideal for pagination and summaries. • Top and Bottom (ranking) filters — surface the highest or lowest performers directly from the filter editor. • Dashboard module system and header customization APIs (internal testing) — more flexible dashboard composition. • Stability & fixes: numerous PivotTable sorting and loading fixes, plus improvements to filtering, number formatting, and data model generation. Check out the full changelog for all details and fixes included in Compose SDK 2.30.0.

    Amanda Hammar
    Amanda HammarPosted 3 months ago
    0
             
  • Blog banner
    • Embedding AnalyticsChevronRightIcon

    From Zero to ComposeSDK: Building a Sisense React Application with an AI Coding Assistant

                                                                                                                                     

    From Zero to ComposeSDK: Building a Sisense React Application with an AI Coding Assistant A computer with no development related setup, no code editor application, no programming languages or runtimes such as Node installed, no development tooling of any kind, can create a running ComposeSDK React application in a single development session, connected to a live Sisense instance and ready for continued iteration. The computer can be a laptop, desktop, or server, and the finished app can be served from localhost, a local IP address, or a domain. Almost none of this development and setup has to be done by hand. An AI coding assistant installs the tooling, runs the commands, and writes the code, while the developer describes what should happen and confirms the results along the way. This style of development is sometimes called vibe coding, which refers to working conversationally and letting the AI handle the mechanics. Any modern LLM based coding assistant can drive this workflow. It needs two capabilities, editing code files in the project folder and running terminal commands. This article uses Claude Code in VS Code as the concrete example, but the same steps work in other editors with agentic assistants, in a standalone CLI tool like Claude CLI, or in a desktop app with terminal access like Claude Desktop. The prompts translate directly and are not specific to Claude. Only two steps require manual installation. The first is installing the text editor with its AI assistant, and the second is generating an API token inside the Sisense web interface. Everything else happens through conversation, from installing Node.js to creating the project, configuring credentials, generating the .ts representation of a Sisense data model, and building the widgets, dashboards, and other visualizations themselves. The starting point on the Sisense side is a modern Linux instance with at least one data model, either an ElastiCube or a live model, and permission to generate an API bearer token on it. Prerequisites A modern Linux Sisense instance with at least one data model Permission to generate an API bearer token on that instance A computer with the ability to install programs and CLI access Access to an LLM like Claude, or similar Getting Started An editor and an AI assistant have to be installed before anything can be done conversationally, so the first two steps are done manually. Download and install Visual Studio Code . Open the Extensions view in VS Code, search for the assistant of choice, install it, and sign in. For Claude Code, see the Claude Code documentation . Other agentic assistants with file editing and terminal access work the same way. One behavior is worth knowing about before the first prompt. Agentic assistants ask for approval before running each terminal command, so expect to click Yes regularly during setup, and read each command before approving it. The approval step exists so the developer always knows what is about to run on the machine, and it only provides that protection if the commands are actually read. Most assistants also offer an option to allow certain commands without asking again, which becomes convenient once a pattern has been reviewed a few times. Editing files inside the project folder is a sensible thing to approve universally. Commands that reach outside the project folder, or that install programs system wide, should be reviewed every time and not auto approved. How Granular to Be The sections below proceed step by step, with each prompt doing one focused thing. That structure is deliberate. It makes each step verifiable before the next is added, and it gives the reader a chance to learn what is happening at every stage. It is not a limitation of the LLM. In practice, an agentic assistant handles much larger requests comfortably. Given an empty folder and little else, a single prompt can carry the project most of the way. Set up a new ComposeSDK React application in this folder. Install Node if it is missing, create a React TypeScript Vite project, add the Sisense ComposeSDK packages, and start the dev server. My Sisense instance is at https://example.sisense.com and I will provide an API token in the file where it is needed, tell me where it is. The assistant works through the chain on its own, installing what is missing, flagging where the token goes, and reporting back when the dev server is running. Granular prompts tend to be the better choice while learning. Broad prompts work better once the process is familiar. Most real sessions mix both freely. If any errors are visible, pasting the error message into the LLM chat will often allow the LLM to resolve the issue. Checking the browser developer tools console for error messages to copy can also be helpful. Handing the Environment to the Assistant From this point forward, work happens in the assistant's chat panel. The first task is the development environment itself. To open the Claude VS Code UI, click the orange flower icon in the top right corner when a text file is open in VS Code. Open VS Code in a new empty folder that will hold the project (File, Open Folder, then create or select a directory. Name the folder appropriately, to match the name of the ComposeSDK (CSDK) application). Create an empty text file if the Claude VS Code icon (or the equivalent for the LLM in use) is hidden. Then open the LLM chat panel and ask the assistant to set up the toolchain. Install the latest LTS version of Node on this machine, make sure it is on the path, then verify that node and npm both work from the terminal. The assistant detects the operating system, picks an appropriate install method for that platform, runs it, and confirms the versions. If the path changed during installation, the assistant will usually mention that the terminal or VS Code may need a restart before the change takes effect. Node.js can also be installed manually for the relevant OS from the Node.js website download section , if the LLM struggles to install it via CLI. This first exchange establishes the pattern for everything that follows. State the goal, let the LLM assistant run the commands, confirm the result, move to the next step. Creating the Project With the toolchain verified, the next request creates the application. Create a new React TypeScript app in this folder using Vite, install the dependencies, and start the dev server so I can confirm the default page loads. The assistant plans and sets up the project, installs packages, and starts the Vite dev server, usually at http://localhost:5173 . Open that URL in a browser and confirm the default Vite page renders. Confirming each layer before adding the next is a habit worth keeping. If a later step fails, a verified baseline makes it obvious where the problem was introduced. Installing ComposeSDK ComposeSDK ships as npm packages, so adding it is one request. Add the Sisense ComposeSDK packages to this project: sdk-ui and sdk-data as dependencies, and the SDK CLI as a dev dependency. The three packages serve distinct purposes. @sisense/sdk-ui provides the React components, the charts, dashboards, and filter UI. @sisense/sdk-data provides the query construction layer, including dimensions, measures, and filter factories. @sisense/sdk-cli is a development tool used to generate the TypeScript data model files covered later in this article, and never ships with the application. Full ComposeSDK documentation is available on developer.sisense.com. Connecting to the Sisense Instance Two values connect and authenticate the CSDK application to Sisense, the instance URL and an API bearer token. Generate the token in the Sisense web interface, from the user profile menu under API Token ( Sisense documentation on generating API tokens ). Copy it once generated. The recommended approach keeps the token out of the LLM chat entirely, so the LLM never has access to it. The assistant builds the configuration with the token left blank. Create a .env file with VITE_SISENSE_URL set to https://example.sisense.com and VITE_SISENSE_TOKEN left blank. Add .env to .gitignore, deny your own read access to .env in your permission settings, wrap the app in SisenseContextProvider using those environment variables, and tell me where to paste my token. The assistant creates the .env file with the relevant keys, excludes it from both version control and its own file access, and points to where the token goes. Editing the URL and pasting the token into .env directly in the editor completes the setup. From that point the application can read the bearer token value but the assistant cannot. In Claude Code specifically, the mechanism for blocking its own access is a permission deny rule rather than an ignore file. The rule lives in .claude/settings.json at the project level, or in ~/.claude/settings.json to apply across all projects. { "permissions": { "deny": ["Read(./.env)", "Read(./.env.*)"] } } The /permissions command in Claude Code can also add this rule interactively, saving it to .claude/settings.local.json . Other assistants have their own equivalents, whether ignore files or access controls. Whichever tool is in use, it is worth verifying the block before pasting the real token. Put a dummy value in .env , ask the assistant to read the file, and confirm it refuses. Pasting the token straight into the chat and letting the assistant write the whole file also works, but is not recommended, since it means trusting the chat tool with a credential. Either way, keep .env out of version control and rotate the token if exposure is suspected. Clicking the refresh icon in the Profile bearer token page rotates the token. A token from a viewer role user is all that is required for CSDK, as long as that user has access to every dashboard and data source the application uses. Bearer tokens are also not the only option. ComposeSDK can authenticate with Web Access Tokens (WAT) and SSO as well, which are worth considering beyond initial development ( ComposeSDK authentication documentation ). The resulting setup looks approximately like this in main.tsx , the entry file Vite generates. Wrapping inside App.tsx works just as well, as long as the provider sits above every ComposeSDK component. import { createRoot } from 'react-dom/client'; import { SisenseContextProvider } from '@sisense/sdk-ui'; import App from './App.tsx'; // The provider supplies the connection to every ComposeSDK component below it. // Values come from .env so credentials stay out of source control. createRoot(document.getElementById('root')!).render( <SisenseContextProvider url={import.meta.env.VITE_SISENSE_URL} token={import.meta.env.VITE_SISENSE_TOKEN} > <App /> </SisenseContextProvider> ); Smoke Testing the Connection Before building further, it is worth confirming the application can reach and authenticate with the Sisense instance. There are two reasonable ways to do it. The fastest is DashboardById , which renders an existing Sisense dashboard and requires no data model work in the project. Open any dashboard in Sisense and copy its OID from the browser URL. Render the dashboard with OID 65a1b2c3d4e5f6a7b8c9d0e1 from my Sisense instance on the main page using DashboardById, replacing the default Vite content. The alternative is a minimal chart against a known data model, which previews the widget workflow used in the rest of this article. Either one confirms the same thing, that the URL and token work and the instance is reachable from the browser. A rendered dashboard means the connection is established. A blank or broken one usually traces back to one of these common causes. The token is invalid or belongs to a user without access to the dashboard or data source. Regenerate the token and confirm the user can open the dashboard in Sisense directly. The URL is incorrect, commonly a missing https:// or a trailing path. The value should be the bare instance origin. The browser blocks the request with a CORS error. ComposeSDK typically works without any CORS configuration, but if the console shows a CORS error, an administrator can add http://localhost:5173 to the allowed origins in the Sisense Admin section under Security Settings ( Sisense documentation on CORS configuration ). Browser console errors generally identify which of these applies, and the error output can be pasted straight into the chat for diagnosis. Generating the TypeScript Data Model ComposeSDK includes a built in tool for this step. The SDK CLI installed earlier generates a .ts file that describes the data model, listing its tables, dimensions, and measures as typed TypeScript objects, so widget code references real fields with autocomplete rather than hand typed strings. It works the same way whether the model is an ElastiCube or a live model. Being specific in the prompt matters here. Naming the goal, a .ts file generated by the SDK's own CLI, steers the assistant toward the right tool, there otherwise being the possibility it would try to create or hallucinate its own representation. Use the ComposeSDK CLI (the get-data-model command from @sisense/sdk-cli) to generate a .ts file describing my data model named Sample ECommerce, and save it to src/models. Use the token in .env. Under the hood this runs a command of the following form. # Generates a .ts file describing the data model's tables, dimensions, and measures. # The token and URL are the same values used by the app itself. npx @sisense/sdk-cli get-data-model \ --url https://example.sisense.com \ --token <token> \ --dataSource "Sample ECommerce" \ --output src/models/sample-ecommerce.ts The generated .ts file exports a typed object, conventionally imported as DM , containing every table, dimension, and measure in the model. Everything built from this point on references fields through that object, which means typos become compile errors instead of silently empty charts. Building the First Widget With the .ts model file in place, building the first real chart is a single request. Using the generated Sample ECommerce data model in src/models, add a column chart below the dashboard showing total Revenue by Condition. The assistant writes a component along these lines. import { Chart } from '@sisense/sdk-ui'; import { measureFactory } from '@sisense/sdk-data'; import * as DM from './models/sample-ecommerce'; // A minimal ComposeSDK chart: one category dimension, one aggregated measure. <Chart dataSet={DM.DataSource} chartType="column" dataOptions={{ category: [DM.Commerce.Condition], value: [measureFactory.sum(DM.Commerce.Revenue, 'Total Revenue')], }} /> Once it renders, iteration can be done through continued conversation or manual code editing. The Vite application will auto update with any changes, without requiring manual refreshes. Change it to a bar chart, break it down by Gender, and only include 2024 data. The assistant adds the breakBy entry to the data options and a date filter to the chart, and the result appears on the next hot reload. Describe a change, see it render, refine. That loop is the core of the workflow, and it is the same whether the change is a chart type, a filter, or a layout adjustment. Styling and Structuring the Application The same conversational approach turns the result from a test page into something that looks like an application. Add a professional looking header and footer to the application, including the title [app title], and lay out the dashboard and the chart in cards. What this prompt actually says will vary by user and project. The card layout here is just one option, and the assistant follows whatever visual direction it is given, from minimal to fully branded. One distinction is worth knowing. Application CSS and widget theming are separate layers. Page layout, headers, and footers are ordinary React and CSS. The appearance of the ComposeSDK widgets themselves, including chart colors, fonts, and backgrounds, is controlled through the SDK's ThemeProvider component ( ComposeSDK theming documentation ). Asking for "a dark theme on the charts" versus "a dark background on the page" will, correctly, touch different code. Working with Live Data The ComposeSDK application itself does not typically contain or store data, though exceptions exist, a point that is often misunderstood. Every widget sends a JAQL query to the Sisense instance at render time, so the CSDK application always reflects the current state of the data model. For an ElastiCube that means fresh data appears after each build, and for a live model queries hit the source directly. In neither case does updated data require rebuilding or redeploying the application. The generated .ts model file only needs regeneration when the schema changes, such as new fields, renamed columns, or restructured tables. Data refreshes do not affect it. When the schema does change, regeneration is one request. My data model schema changed. Use the ComposeSDK CLI to regenerate the .ts model file and tell me if any existing widget code references fields that no longer exist. Because the model file is typed, removed fields surface as compile errors, and the assistant can identify and fix the affected components in the same exchange. Prompt Library The prompts below are starting points, not strings that need to be copied exactly. Everything in this article was done conversationally, and describing the goal in natural language as it comes to mind is usually faster than locating a prompt to paste. What makes these prompts work is their specificity, with actual field names, file locations, and a clear definition of when the task is finished. Those qualities carry over to any phrasing, including a single broad prompt that covers several steps at once. Setup and environment: Install the latest LTS version of Node, make sure it is on the path, and verify node and npm from the terminal. Create a React TypeScript Vite app here, install dependencies, and start the dev server. Set up a complete ComposeSDK React app in this folder: install anything missing, create the project, add the SDK packages, and get the dev server running. ComposeSDK installation and connection: Add the ComposeSDK packages: sdk-ui and sdk-data as dependencies and the SDK CLI as a dev dependency. Create a .env with my Sisense URL and a blank token, add it to .gitignore, deny your own read access to it, wire up SisenseContextProvider from those variables, and tell me where to paste my token. The dashboard component shows a network error. Here is the browser console output: [paste]. Diagnose whether this is CORS, the token, the URL, or something else. Data and widgets: Use the ComposeSDK CLI to generate a .ts file for the data model named [name] into src/models. Render the dashboard with OID [oid] using DashboardById. Add a line chart of [measure] by [date dimension] at month granularity, with a member filter on [dimension] limited to [values]. Add breakBy on [dimension] to the existing column chart and move the legend to the bottom. Advanced: Use useExecuteQuery to fetch [measure] grouped by [dimension] and render the result in a plain HTML table instead of an SDK chart component. Use onBeforeRender on the line chart to set the line width to 3 and enable data labels through the underlying Highcharts options. The advanced prompts reference real ComposeSDK extension points. useExecuteQuery runs a query directly and returns rows for custom rendering, and onBeforeRender exposes the underlying Highcharts options object before a chart draws, allowing customization beyond the SDK's own props ( useExecuteQuery reference , chart customization reference ). Useful Links ComposeSDK documentation ComposeSDK quickstart for React Claude Code documentation Sisense REST API and authentication documentation The techniques in this article generalize. Any capability in the ComposeSDK documentation, from dashboard filters to custom widget types, is reachable the same way. Describe it to the assistant with enough specificity, confirm the result in the browser, and refine conversationally.

    Jeremy Friedel
    Jeremy FriedelPosted 3 months ago • Last reply 3 months ago
    3
             
  • Blog banner
    • Embedding AnalyticsChevronRightIcon

    CSDK Custom Widgets Example [Linux]

             

    Motivation Compose SDK has a bunch of ways to create Custom Widgets and use them inside your embedding application. This article is intended to show an example of a configurable environment so you can see the possibilities behind Compose SDK features. Why Create Custom Widgets? Custom widgets inside CSDK (Compose SDK) can be two different things. We have a feature called Custom Widgets , which is used to replace Plugin widgets inside the DashboardById component, We will discuss that later. The most common case is when you create custom widgets in the framework of your choice, for example: This is a Top Performers List Widget that doesn’t exist inside Sisense, but nothing blocks us from creating it natively inside React. That is one of the main concepts behind Compose SDK. Using it, you are no longer limited by the Sisense UI, and now you can create any kind of UI that you want. How to get Data? To do that, the main method is useExecuteQuery . In React, we call it a hook . Inside it, you can pass the query that you want Sisense to execute. For example: you want to create a table, you can pass this query to useExecuteQuery : { dataSource: DataSource, dimensions: [Country.Country], measures: countryTableMeasures(), filters: filters, count: TABLE_PAGE_SIZE, offset: (tablePage - 1) * TABLE_PAGE_SIZE, }; It’s closer to using raw JAQL in the Sisense API, but using the JSON Query Model from CSDK. That allows you to create large queries or complex measures on the Sisense server side. That is one of the main features of CSDK. Manipulate the Data Another important concept is the factories. It has a bunch of them that you can check here . In this demo, we’re mostly using the filterFactory and the measureFactory . The important thing that you should know is that measures and filters are important concepts for creating a query, and the factories help you do that. In the Top Performers Table, we’re using them, for example: const quantity = measureFactory.sum(Commerce.Quantity, "Quantity"); const revenue = measureFactory.sum(Commerce.Revenue, "Revenue"); { dataSource: DataSource, dimensions: [dimension], measures: [quantity, revenue], }; You can create several types of measures in the same query, but in this case, we’re using them to get the Quantity and Revenue. This allows Sisense to do these calculations for you, so you don’t need to do the calculations on the client side. The filters follow the same logic, but they are passed in other prop: filterFactory.members(Commerce.Condition, [options.condition]) In general, you want to pass what is being filtered from your frontend. The Data Structure You will see more about that in the setup step. DataModel is the representation of your data model inside Sisense, so if you run the command to get the data model, that will be exactly what your Sisense data model is inside your application. This is essential for creating queries using Type IntelliSense, so you don’t get stuck trying to create queries by hand. Dynamic Configuration The core concept of this demo is to allow you to test it in your Sisense deployment. To do that, we use a few concepts from React. What you need to know is the SisenseContextProvider . It is a Context , if you already use React, you probably know what it is, but imagine a context as a pool where you can put some components inside that will consume the same data, and this also applies to Sisense: export function App() { const { connection } = useSisenseConnection(); if (!connection) { return ( <ConnectScreen/> ); } return ( <SisenseContextProvider url={connection.url} token={connection.token}> <Dashboard /> </SisenseContextProvider> ); } So, using this concept, we can create a preview screen where you can pass any configuration before starting your application. This opens a wide range of implementations using Compose SDK, and this works for any component too. You can use it to display components and connect to Sisense only when it’s needed. Setup Creating the Data Model: To make the setup easier, we use the Data Model that already comes with Sisense. In this demo, we will use the Sample ECommerce data. If you don’t have your ElastiCube running, you must start and publish it. The data that you have inside this ElastiCube is from 2013, so when you apply the filters, keep that in mind: Creating your Dashboard: This is only needed if you want to see the demo working with Compose SDK Custom Widgets. The Plugin Widget that we’re replacing is the Histogram. It’s a free widget from Sisense, so you can apply it to any dashboard. You must have it to see the Custom Widget implementation. The DashboardById component that we’re using doesn’t need to have the Data Model inside the code, so you can use any ID. You can import this dashboard as example. After creating you dashboard you should save the id of it, you can find the id in your URL: <YOUR_SISENSE_URL>/app/main/dashboards/<YOUR_DASHBOARD_ID> . Getting your API Token: There are a few ways to get your API Token. The simplest one is through the Sisense UI . Another way is by using the Compose SDK CLI . Both methods work. After that, save your token. In this demo, the token and any other information are not being saved anywhere, so you can safely put your information there. Configure the CORS: You must configure your CORS. That is how your Sisense server will recognize this demo as reliable. To configure it, you must copy the domain/URL from the demo: https://csdk-community-example.vercel.app . You can follow the same steps that are used in the CSDK docs . Setting the Settings: Now you can configure the demo. Go to the url , and fill the fields. After you click in connect the dashboard will be opened. Report Tab The application is divided into two tabs. The Report Tab contains widgets that were fully created using React without any usage of useExecuteCustomWidgetQuery , and widgets that use it. Those widgets that use the Custom Widget will have a disclaimer at the top. This is where we use most of the measure and filter factories. The main idea is to show that it is possible to create any kind of widget using it. The widgets here were created using Tailwind, and you can organize the components however you want inside React. Since your data is coming from useExecuteQuery or useExecuteCustomWidgetQuery , the heavy work will be processed by Sisense. Some components, like the Indicators, still have some calculations inside the JS, for example: const clamped = Math.min(100, Math.max(0, metricPercent)); This is used to get the right size of the filled bar compared to the value. The way that I like to organize it is: The data that is purely informational, like percentages, revenue, and quantity, I let CSDK handle. Those kinds of data that are for frontend display, I handle in the frontend. You need to be careful when doing operations in the frontend. You don’t want to lose your source of truth for the data, which is Sisense. Filters Another important part of this dashboard is the filters. Those are applied through React, but they are using the filter factories from Compose SDK. That means those filters are not just on the client side. In React, you can share context and values between components, like it was explained earlier in this article. So we can pass the values from some widgets, or to all widgets. Using the factory, you can bind the filters to the React state and pass the values to a builder that will apply them to all those widgets, for example: if (options.period === "thisYear") { filters.push(filterFactory.thisYear(Commerce.Date)); } else if (options.period === "last12Months") { filters.push(filterFactory.dateRelative(Commerce.Date.Months, -11, 12)); } Custom Widget In the ReportTab, we use the Custom Widget (Code) , which is not related to a WidgetById or DashboardById . What does that mean? It means that we’re not using outside hooks like useExecuteQuery , and we need to register the widget. The process is almost the same as using it inside a DashboardById or WidgetById component, so you need to register the widget and, right after that, pass the query to the Widget component. In this example, we’re doing a bunch of style changes, but the idea is the same. Also, we pass the buildReportFilters that we use in other non Custom Widgets. const { registerCustomWidget } = useCustomWidgets(); registerCustomWidget("topperformers", CustomTopPerformersWidget); return ( <Widget key={dim.name} widgetType="custom" id={`top-performers-${dim.name.toLowerCase()}`} title={dim.name} dataSource={DM.DataSource} customWidgetType="topperformers" filters={[ ...buildReportFilters(reportOptions), filterFactory.topRanking(dim, qty, reportOptions.topPerformersCount), ]} dataOptions={{ dimensions: [{ column: dim }], value: [ { column: qty, name: "Quantity" }, { column: measureFactory.sum(DM.Commerce.Revenue, "Revenue"), name: "Revenue", }, ], }} styleOptions={{ height: 280, header: { hidden: true }, cornerRadius: "None", shadow: "None", spaceAround: "None", topCount: reportOptions.topPerformersCount, backgroundColor: "None", }} /> ); Using it you can inside the WidgetComponent just get the data instead of fetching the data outside the component inside a Context. export const CustomTopPerformersWidget: CustomWidgetComponent = (props) => { const { data, isLoading, isError } = useExecuteCustomWidgetQuery(props) // Rest of your component... } Custom Widget Dashboard If you do not pass the Dashboard ID, this tab will be disabled. So, if you pass the Dashboard ID, you will be able to see the DashboardById implementation. This is one of the standard implementations of Compose SDK Custom Widgets Dashboard . One of the main functions of Custom Widgets is to replace Plugin Widgets or Custom Widgets that you created in Sisense. That means that we need to register or unregister the custom component, like this: useEffect(() => { registerCustomWidget(HISTOGRAM_WIDGET_TYPE, DashboardTopPerformersWidget) return () => unregisterCustomWidget(HISTOGRAM_WIDGET_TYPE) }, [registerCustomWidget, unregisterCustomWidget]) The TopPerformers Widget inside the Dashboard is a different version from the one used in the ReportTab. That is because DashboardById passes the data in a different way, since we’re using a Histogram widget to be replaced. At the end of the day, the component is the same. What changes is just the data handling that we need to parse to build the widget in the case of DashboardById or WidgetById components. Conclusion Compose SDK opens a lot of possibilities when you want to build custom components. In most cases, using Sisense as the source of truth is the right approach, so you avoid having different values. However, in some cases, you will need to change the data shape to fit your component (like in the Custom Widget inside DashboardById). We also showed a way for you to handle the Sisense Context to create custom connections or change configurations dynamically. In addition, we explored a little bit about factories and why you should use them when handling Custom Widgets. See for yourself and test it in the Demo URL . You can also find the Repository URL on GitHub so you can explore and modify the code. References/Related Content  Github Repository Compose SDK Custom Widget Documentation Compose SDK Custom Widget Playground How to get the Token Documentation Compose SDK documentation: Sinsense Context Provider . Mentioned Plugins: Histogram . Disclaimer: This is an experimental demo application and not an officially supported Sisense demo. This post outlines a potential custom workaround for specific use cases and provides examples of how Compose SDK features can be implemented. The solution may not work in all scenarios or Sisense versions, so we strongly recommend testing it in your environment before deployment. If you need further assistance with this, please let us know.

    Micael Santana
    Micael SantanaPosted 3 months ago
    0
             
  • Discussion
    Micael Santana
    • Knowledge Base Docs
             
    Micael SantanaAdmin
    How to Replace Plugin Widgets in React Compose SDK [Linux]
                             

    This article explains how to replace plugin widgets inside embedded dashboards that use Compose SDK. You’ll learn how the system works internally, registration functions, and how to override the default behavior of the registration. Step 1: Understand the goal When working with embedded dashboards that contain Custom Widget Plugins, for example: Histogram, Tabber etc. The default behavior of CSDK is show an error placeholder: The main idea of Custom Widgets is to replace them, so the CSDK renders it instead of the original widget inside the DashboardById component. Step 2: Custom Component To start you must create the component itself, to do it you should implement CustomWidgetComponent , and fetch widget data using the useExecuteCustomWidgetQuery hook. import { CustomWidgetComponent, useExecuteCustomWidgetQuery } from '@sisense/sdk-ui'; const ResultsTable: CustomWidgetComponent = (props) => { const { data } = useExecuteCustomWidgetQuery(props); if (!data) { return null; } return ( <table style={{ margin: '20px' }}> <thead> <tr> {data.columns.map((column, columnIndex) => ( <th key={columnIndex}>{column.name}</th> ))} </tr> </thead> <tbody> {data.rows.map((row, rowIndex) => ( <tr key={rowIndex}> {row.map((cell, cellIndex) => ( <td key={cellIndex}>{cell.text}</td> ))} </tr> ))} </tbody> </table> ); }; What it does: The component itself it's just a component from React, so you implement: charts, simple tables etc. The key is the useExecuteCustomWidgetQuery that executes the widget query and returns structured data so your component can render however you want. Note: If you prefer working with raw query results instead of formatted data, you should use extractDimensionsAndMeasures together with useExecuteQuery . Step 3: The Data The CustomWidgetComponent receives properties similar to those by plugins internally, like: metadata, filters, configuration. This means: Some logic from plugin widgets can often be reused. Data remains compatible with Fusion. Migration from plugin widgets → Compose SDK is simplified Compose SDK mainly handles rendering, while configuration and data still come from the Sisense. Step 4: Register the custom widget After creating your component, you must register it before rendering the dashboard. Registration tells the SDK which component should be used when it encounters a Custom Widget Type. import {DashboardById,useCustomWidgets }from'@sisense/sdk-ui'; functionApp() { const { registerCustomWidget }=useCustomWidgets(); registerCustomWidget('histogramwidget',ResultsTable); return<DashboardByIddashboardOid={'66f4d4dd384428002ae0a21d'}/>; } Important Registration must happen before the dashboard renders. Once registered, the widget is stored inside the main Compose SDK context. Any dashboard within that context will automatically use your custom widget. Step 5: Understand more about how it works internally Internally, the SDK keeps registered widgets inside a registry managed by the CSDK Context: So the rendering flow will be: Dashboard loads widget metadata. CSDK checks if that widget type exists in the registry: If registered → renders your custom component If not → renders the error placeholder The registry uses a key-value structure for fast lookup and to avoid repeated registrations. Step 6: Why widgets are designed to register only once Registration happens once per context lifecycle by design. This provides several benefits: Performance: prevents repeated registrations on re-renders Predictability: ensures only one implementation per widget type Stability: avoids conflicts between components trying to register the same widget Memory safety: prevents the registry from growing uncontrollably Since registrations are stored in a Context map, calling registerCustomWidget multiple times is unnecessary and discouraged. Step 7: How to unregister it? If you need to unregister a component, recently we added a new method named unregisterCustomWidget , to the CSDK Custom Widgets, that you can use to do it: Note: this was added in v2.26.0 , of Compose SDK. import { useCustomWidgets } from '@sisense/sdk-ui'; export const FunnelWidget = () => { const { registerCustomWidget, unregisterCustomWidget } = useCustomWidgets(); const [type, setType] = useState<'funnel' | 'treemap'>('funnel'); const widget = type === 'funnel' ? CustomFunnel : CustomTreemap; useEffect(() => { registerCustomWidget('histogramwidget', widget); return () => unregisterCustomWidget('histogramwidget'); }, [registerCustomWidget, unregisterCustomWidget, widget]); return ( <div> <Select value={type} onValueChange={(v) => setType(v as 'funnel' | 'treemap')} > <SelectTrigger className="w-[180px] my-3"> <SelectValue placeholder="Chart Type" /> </SelectTrigger> <SelectContent> <SelectGroup> <SelectItem value="funnel">Funnel</SelectItem> <SelectItem value="treemap">Treemap</SelectItem> </SelectGroup> </SelectContent> </Select> <DashboardById key={`dashboard-${type}`} dashboardOid={'68dae1429e8028a69c6a41ee'} /> </div> ); }; Using it you don't need to re-render the Sisense Main Context to change the register type, but you still need to re-render the Dashboard Component. Best practice recommendation Register custom widgets once during application initialization whenever possible. Only force a context remount if you specifically need dynamic registration behavior. Conclusion:  Custom Widgets in Compose SDK provide a powerful way to replace plugin widgets while still using the original widget data. Understanding that registration is stored in a context-level registry explains: why widgets are registered once, why repeated registration isn’t needed and why remounting the context is sometimes required for advanced scenarios. References/Related Content  Compose SDK documentation: DashboardById Component . Compose SDK documentation: Custom Widgets . Compose SDK documentation: Sinsense Context Provider . Mentioned Plugins: Tabber , Histogram . Disclaimer: This post outlines a potential custom workaround for a specific use case or provides instructions regarding a specific task. The solution may not work in all scenarios or Sisense versions, so we strongly recommend testing it in your environment before deployment. If you need further assistance with this, please let us know.

    4 months ago
    0
             
  • Discussion
    kundankumar
    • Help and How-To
             
    kundankumar
    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

    1 year agolast reply 10 months ago
    7
             
  • Blog banner
    • Use Case GalleryChevronRightIcon

    Jira Automation App with Sisense + GenAI

             

    A fully automated analytics-to-action workflow. Sisense triggers threshold-based alerts using Compose SDK queries, then through a POST request directly to Jira via API. The ticket is auto-populated with narrative insights generated using Sisense’s GenAI capabilities. Closes the loop between insight and action - reducing response times, improving accountability, and eliminating manual ticket creation in operational workflows. Check out this public Github repo for detailed steps.

    spuma
    spumaPosted 1 year ago
    0