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
      • Official Sisense Discord
    • Use Case Gallery
    •      
    Discussions
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    Discussions
    • TagsChevronRightIcon
    Sisense Embed SDK
    • Blog banner
      • Widget & Dashboard ScriptsChevronRightIcon

      Hiding Widgets if a Widget Has No Results [Linux]

                                                                                                               

      Hiding Widgets if a Widget Has No Results [Linux] Introduction A common dashboard design requirement is hiding widgets that have no data. This article includes a widget script that conditionally hides an indicator widget when its primary value is empty or represents an N/A like value. This behavior is commonly requested when dashboard filters or formulas result in no meaningful value and the widget should be visually hidden rather than showing an empty or zero indicator. The article also includes an alternative dashboard-level approach that hides widgets based on filter selections, without inspecting widget query results. This alternative is derived from the linked external blog post . This is applicable to both on-cloud and on-prem Sisense in all recent Sisense versions. Use Case Customers often want indicator widgets to disappear when their calculated value is not meaningful. Common examples include: Filters resulting in no matching data Calculations returning N/A, null, or empty values Conditional metrics that only apply to certain filter selections Rather than showing an empty indicator, this approach hides the widget entirely and restores it automatically when the value becomes valid again. Layout Considerations For best visual results, it is ideal to use one of these two layouts for the indicator widgets that may be hidden: Place the indicator widget in its own dashboard row Place it at the end of a row Hiding a widget via a script does not automatically resize or reflow other widgets on the same row. Step by Step Guide Widget Script to Hide Widget Based on Indicator Value This widget script is applied directly to the indicator widget. It evaluates the widget’s primary value after each render and hides or shows the widget accordingly. Behavior If the primary value is null, empty, or an N/A like string, the widget container is set to display: none. When the value becomes valid and not null again, the widget is restored to visibility and redrawn to ensure correct indicator rendering. A guard variable prevents infinite redraw loops, since a redraw triggers the widget ready event. A debug flag allows optional console logging when needed. Widget Script /** * Hide widget when its primary indicator value is empty or N/A-like. * * Behavior: * - If the value is empty, set the widget container to display "none". * - If the returned value becomes a number or valid value, restore display style to original and redraw * - A redraw triggers "ready" again, so a guard variable prevents a redraw loop. * * Debug: * - Set debug variable = true to enable console logging to track script status. * * For best results use on indicator in own row or at end of row, if other widgets exist on row, empty space will appear in dashboard */ widget.on("ready", function () { function run() { var debug; var suppressNextReady; var hideValues; debug = false; suppressNextReady = false; hideValues = [ "n/a", "#n/a", "na", "none", "null", "undefined" ]; // Function to turn console logging on or off function log(message, data) { if (!debug) { return; } if (data === undefined) { console.log("[hide-empty-indicator] " + message); return; } console.log("[hide-empty-indicator] " + message, data); } // Widget CSS selector function getWidgetElement() { return document.querySelector('widget[widgetid="' + widget.oid + '"]'); } // Get value of primary indicator value function getPrimaryValue() { if ( widget.queryResult && widget.queryResult.value && widget.queryResult.value.data !== undefined ) { return widget.queryResult.value.data; } try { if ( widget.queryResult && widget.queryResult.data && widget.queryResult.data.length ) { return widget.queryResult.data[0][0]; } } catch (e) { log("Value read failed for data[0][0].", e); } if (widget.queryResult && Array.isArray(widget.queryResult)) { if (widget.queryResult.length && widget.queryResult[0].length) { if (widget.queryResult[0][0]) { return widget.queryResult[0][0].Value; } } } return null; } function shouldHide(value) { var text; var normalized; if (value === null || value === undefined) { return true; } text = String(value).trim(); if (!text) { return true; } normalized = text.replace(/\\/g, "/").toLowerCase(); return hideValues.indexOf(normalized) !== -1; } function isElementHidden(element) { if (!element) { return false; } return element.style.display === "none"; } function hideWidget(element) { if (!element) { return; } if (element.style.display === "none") { return; } element.style.display = "none"; log("Hid widget due to empty/N/A-like value."); } function showWidgetAndRedrawIfNeeded(element) { var wasHidden; if (!element) { return; } wasHidden = isElementHidden(element); element.style.display = ""; if (!wasHidden) { log("Widget already visible."); return; } if (typeof widget.redraw !== "function") { log("Widget restored, redraw not available."); return; } suppressNextReady = true; log("Widget restored, triggering redraw."); widget.redraw(); } function applyRule() { var element; var primaryValue; element = getWidgetElement(); if (!element) { log("Widget container element not found."); return; } if (widget.queryResult === undefined) { log("queryResult is not available yet, keeping widget visible."); element.style.display = ""; return; } primaryValue = getPrimaryValue(); log("Primary value evaluated.", primaryValue); if (shouldHide(primaryValue)) { hideWidget(element); return; } showWidgetAndRedrawIfNeeded(element); } function onReady() { if (suppressNextReady) { suppressNextReady = false; log("Ready fired after redraw, applying rule without redraw."); applyRule(); return; } applyRule(); } onReady(); } run(); }); Notes The script uses display: none instead of jQuery hide or show to avoid layout and rendering issues with indicator widgets. Redraw is triggered only when restoring visibility, not when hiding. The script relies only on the widget ready event, which fires again after redraw and filter changes. Dashboard Script to Hide Widgets Based on Filter Selections As an alternative, widgets can be hidden purely based on filter selections, without checking whether the widget returns data. This approach is useful when visibility rules are deterministic based on filters. This method uses a dashboard script and CSS classes to hide widget containers. Example Dashboard Script dashboard.on('filterschanged', function (se, ev) { let filterName = 'Region' //mapping of filter items and widgets to be hidden. //if selected filter item is not available in the list, widgets in 'default' key will be hidden let itemWidgetMapping = { 'Midwest':['6390b5a285a029002e9e2ad6'], 'South': ['6238887ba77683002ea4425b'], 'West':['6390b5a285a029002e9e2ad6', '6238887ba77683002ea4425b'], 'default':[] } selectedFilter = ev.items.find(el=>el.jaql.title == filterName) let selectedItem = 'default' if(selectedFilter && selectedFilter.jaql.filter.members) selectedItem = selectedFilter.jaql.filter.members[0] //unhide all widgets first and then hide widgets based on selected filter $(`widget`).closest('.dashboard-layout-subcell-host').removeClass('dontshowme-parent') if(selectedItem in itemWidgetMapping){ for (const [key, value] of Object.entries(itemWidgetMapping)) { if(key == selectedItem){ itemWidgetMapping[key].forEach(function (item, index) { $(`widget[widgetid="${item}"]`).closest('.dashboard-layout-subcell-host').addClass('dontshowme-parent') }); } } } else{ itemWidgetMapping['default'].forEach(function (item, index) { $(`widget[widgetid="${item}"]`).closest('.dashboard-layout-subcell-host').addClass('dontshowme-parent') }); } }); Choosing the Right Approach Generally the widget script is best suited when: Visibility depends on whether data is returned The indicator value can be empty due to calculations or filters Generally the dashboard script is best suited when: Visibility depends only on filter selections Centralized control over multiple widgets is required These approaches are alternatives and should not be used simultaneously for the same widgets. Conclusion Hiding indicator widgets based on their returned value can potentially improve dashboard clarity and user experience. The widget script approach provides result aware behavior, while the dashboard script approach offers deterministic, filter based control. Both methods are powerful tools for customizing dashboard widget visibility. Two Full Row Indicator Widgets, both visible First Row Indicator Widget is now hidden, by script, due to no data Two Indicators in one row, both visible Second Indicator is now hidden by script, due to no data 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.

      Jeremy Friedel
      Jeremy FriedelPosted 1 week ago
      0
               
    • Micael Santana

      Knowledge Base Docs

               
      Micael Santana
      Posted 2 weeks ago
      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.

                                             
      0
               
      • Embedding AnalyticsChevronRightIcon

      Sisense Compose SDK

                                                                               

      Sisense Compose SDK The Future is Composable In the fast-paced world of data-driven applications, developers are constantly seeking innovative ways to create customized data products that seamlessly integrate into their projects. Sisense has stepped up to the challenge with its groundbreaking Compose SDK, which has fundamentally changed the game. This powerful toolkit empowers developers to harness the full potential of Sisense components, enabling them to craft data products that are not only highly customized but also perfectly tailored to their applications.  Traditionally, the embedded analytics market constrained developers within the boundaries of their chosen tools. However, Compose SDK breaks free from this limitation. Instead of merely embedding a third-party tool, developers now have the freedom to build bespoke data products by selecting from a rich library of Sisense components and seamlessly integrating them with a wide range of other tools within their Integrated Development Environment (IDE).  A Practical Example  In the world of UI design, consistency is key. Imagine your company has chosen the Material UI framework to style your application. It provides a sleek and uniform look, but now you need to populate Material UI tables with dynamic data from Sisense. Enter Compose SDK. With it, you can seamlessly merge Material UI's aesthetics with Sisense's analytical power. Start with Material UI tables that align perfectly with your design guidelines. Then, using the Compose SDK's ExecuteQuery component, you effortlessly infuse these tables with real-time data from Sisense.         The result? Stunning, data-rich components that not only adhere to your design principles but also deliver invaluable insights. Your users get the best of both worlds - visually appealing design and powerful data. Get Started with Compose SDK  Excited to explore the potential of the Compose SDK? Good news - it's currently in Beta and available to all Sisense partners, ready for you to harness its capabilities today. To embark on your journey, you can begin by cloning the example repository from GitHub listed below. To install the necessary packages for your sample application, follow the user-friendly Compose SDK Quickstart Guide. Keep in mind that, for the time being, these packages are accessible on GitHub, requiring a personal access token for access.  In conclusion, Sisense's Compose SDK represents a revolutionary leap forward in the world of data product development. It empowers developers to break free from the constraints of traditional embedded analytics and build highly customized, seamlessly integrated data products. With the Compose SDK, the possibilities are limitless, and the power to transform your  applications lies at your fingertips. So, why wait? Dive into the world of Compose SDK, unleash your creativity, and revolutionize your data products today. Your applications will thank you for it!  Credit : Sample Application initially developed by Sisense Senior Director of Field Engineering: Tom Linton  Compose SDK -- Material UI Example   Sisense Quick Start Guide   Sisense Github

      Sisense User
      Sisense UserPosted 2 years ago • Last reply 1 year ago
      1
               
      • APIsChevronRightIcon

      Using the InternalHttp Function Within Scripts and Plugins

                                                                                                                               

      Using the InternalHttp Function Within Scripts and Plugins The InternalHttp function is a Sisense function within the Sisense internal Prism object. The prism object and the InternalHttp function are present on all Sisense pages and can be used in scripts and plugins, including when embedded with various forms of Sisense embedding . It facilitates custom additional API requests to the Sisense server by applying the same request headers used for internal Sisense requests to handle details of API requests such as authentication , CORS , and CSRF . Custom additional requests to Sisense API endpoints via custom scripts and plugins are often utilized to perform additional JAQL requests for fetching data from Sisense data models. This process is documented in this informative Sisense Community page . The Community page code remains current and includes an example of using the InternalHttp function for additional JAQL requests. Below is a simplified and shortened version of the example code:     widget.on('ready', function (se, ev) { function runHTTP(jaql) { // Use $internalHttp service if exists const $internalHttp = prism.$injector.get("base.factories.internalHttp"); const ajaxConfig = { url: "/api/datasources/x/jaql", method: "POST", data: JSON.stringify(jaql), contentType: "application/json", dataType: "json", async: false, }; const httpPromise = $internalHttp(ajaxConfig, true); // Return response values return httpPromise.responseJSON.values; } const jaqlObj = { "datasource": { "title": "Sample ECommerce", }, "metadata": [ { "jaql": { "dim": "[Category.Category]", } } ] }; console.log('JAQL Result Values are ', runHTTP(jaqlObj)); });     While InternalHttp is commonly used for JAQL API requests, it can be utilized for any Sisense endpoint, allowing custom requests to any Sisense API, not just those related to JAQL. The JAQL API is how Sisense requests data from Sisense data sources. The flexibility offered by custom API calls significantly enhances the capabilities of scripts, enabling developers to interact with the full range of Sisense functionalities. Notable Recent Resolved Scenarios and the InternalHttp Function CSRF Header Issue: A customer who previously did not use InternalHttp had various widget scripts making custom JAQL requests via the JAQL API endpoint. After modifying the CSRF setting, these scripts failed, and not only did not work, but viewing the widget logged the user out of Sisense immediately. The issue was due to missing CSRF header parameters in the request headers. This was most easily resolved by using InternalHttp in the widget scripts, which automatically includes the required CSRF header parameters. Unauthorized Errors with Sisense.js: A customer using Sisense embedded with Sisense.js on a third party domain and using WAT for authentication faced "Unauthorized" API errors for the custom widget script JAQL requests. Despite working outside of WAT , these requests failed within it. This issue is a known limitation with internalHttp. The workaround for this issue is demonstrated below:     widget.on('beforequery', function (se, ev) { function runHTTP(jaql) { // Use $internalHttp service if exists const $internalHttp = prism.$injector.has("base.factories.internalHttp") ? prism.$injector.get("base.factories.internalHttp") : null; const ajaxConfig = { url: "/api/datasources/x/jaql", method: "POST", data: JSON.stringify(jaql), contentType: "application/json", dataType: "json", async: false, xhrFields: { withCredentials: true, }, }; const httpPromise = $internalHttp ? $internalHttp(ajaxConfig, false) : $.ajax(ajaxConfig); // Return response return httpPromise; } const jaqlObj = { "datasource": { "title": "Sample ECommerce", }, "metadata": [ { "jaql": { "dim": "[Category.Category]", } } ] }; runHTTP(jaqlObj).then((API_Response) => { console.log('JAQL Result Values are ', API_Response.data.values); }); }); ​     In this workaround, the second parameter of the InternalHttp function is set to false, and the returned object is handled as a JavaScript promise. The InternalHttp function is designed to be updated along with Sisense, and to adapt to any changes, allowing for scripts written for one environment (in the native Sisense UI for example) to work in many others without issue (when embedded in a third-party domain for example). These scenarios highlight the value of InternalHttp and its potential applications. Leveraging this function allows developers to ensure their custom scripts remain robust and secure in all scenarios, including in environments with specific security requirements or complex authentication mechanisms. As Sisense continues to evolve and update, understanding and utilizing internal tools like internalHttp will be helpful for maximizing the platform's capabilities and maintaining seamless integration within various deployment and embedded contexts.

      Jeremy Friedel
      Jeremy FriedelPosted 1 year ago
      0
               
      • Embedding AnalyticsChevronRightIcon

      React Typescript Component for Embedding Sisense Dashboards

                                               

      To embed dashboards from Sisense into your web application, typically you would use either the IFRAME or EmbedSDK approaches (see sisense.dev for more details). If your application is written in React , then React components to achieve tasks, like this, make life easier. You can always write your own components around Sisense SDKs and APIs, but here is one I wrote that you can use to get started. Example Application To see the component working in a live sample application, simply visit this link and enter your Sisense URL and dashboard ID into the relevant input boxes.     To download the source of this example application, clone this repo and follow the instructions on the readme page.   Use the React Component in Your Application To see more details on how to use the React Component for Sisense Embed SDK in your application, visit the repo for the component and follow the instructions on the read me. At the time of writing this article, here is all you should need to do: Install module using npm npm install --save sisensers/sisense-embedsdk-react   Import the module into your React application import SisenseDashboardEmbed from 'sisense-embedsdk-react'   Use the `SisenseDashboardEmbed` component on your page <SisenseDashboardEmbed<br/> sisenseUrl={sisenseUrl}<br/> dashboardId={dashboardId}<br/>/>   The component accepts a number of other optional 'props', including Booleans, to control the dashboard settings (toolbar, left pane, right pane), volatile mode (don't save changes to dashboard state), as well as strings for the look and feel theme, css height/width for rendered frame etc. Also, you can control if things like EmbedSDK should be unloaded when the component unmounts through the props, as well as attach your own handler to execute when the dashboard has loaded. I hope this helps some of you get started with using Sisense in React! Leave a comment if you use my example and share your experience. 

      steve
      stevePosted 3 years ago • Last reply 2 years ago
      5
               
      • Embedding AnalyticsChevronRightIcon

      Modify Date filters using EmbedSDK to keep the original filter ID

               

      Disclaimer: Please note that this blog post contains one possible custom workaround solution for users with similar use cases. We cannot guarantee that the custom code solution described in this post will work in every scenario or with every Sisense software version. As such, we strongly advise users to test solutions in their environment before deploying them to ensure that the solutions proffered function as desired in their environment. For the avoidance of doubt, the content of this blog post is provided to you "as-is" and without warranty of any kind, express, implied, or otherwise, including without limitation any warranty of security and or fitness for a particular purpose. The workaround solution described in this post incorporates custom coding, which is outside the Sisense product development environment and is, therefore, not covered by Sisense warranty and support services. Modify Date filters using EmbedSDK to keep the original filter ID There are cases when you need to modify the existing date filter level but as Sisense considers it as a new filter that should be added as a separate one - some issues could be faced. Currently, the most common way to do this is to remove old date filters and apply new date filters which are the same except for level. But sometimes you need to exclude date filters from your widgets and the reference to it is based on filter ID which is not the same once you remove filters and then add new filters. This behavior happens because the EmbedSDK dashboard object contains filters without IDs so you can modify filters by referring to dimension only (or dimension + level only for date filters).  Example:   const changeLevel = async (level) => { let dateFilters = window.dashboard.filters.filter(f => f.jaql.datatype === 'datetime') await window.sisenseFrame.dashboard.removeFilters(dateFilters, true) dateFilters.forEach(f => { f.jaql.level = level }) await window.sisenseFrame.dashboard.applyFilters(dateFilters, true) }   This function updates date filters on the dashboard to set the level passed as an argument.  It removes current filters and then sets updated filters.  window.dashboard is a dashboard object returned on dashboardloaded event. In this example, 2 indicator widgets exclude respective date filters, once you execute this function - both filters will be present on widgets and the results might be unexpected: Each widget should use its filter and exclude another one, but currently, both of the filters are used on them causing N/A results. Solution:     const changeLevelWA = async (level) => { let currentFilters = await fetch(`/api/v1/dashboards/${dashboardId}?fields=filters`) currentFilters = await currentFilters.json() let dateFilters = currentFilters.filters.filter(f => f.jaql.datatype === 'datetime') await window.sisenseFrame.dashboard.removeFilters(dateFilters, true) dateFilters.forEach(f => { f.jaql.level = level }) await window.sisenseFrame.dashboard.applyFilters(dateFilters, true) }     This example does the same logic, but instead of using the dashboard object returned from the dashboardloaded event, it sends an API request to get dashboard filters directly. The difference is that API response filters contain an instanceID parameter which is used on widgets to identify which filter should be excluded. As a result, we create new filters using the same instanceID, and relevant filters remain excluded:  Each widget uses its relevant filter after the update.  

      ILLIA
      ILLIAPosted 2 years ago
      0
               
      • Add-ons & Plug-InsChevronRightIcon

      Plugin - HideChartTypeByGroup

                                                                                       

      Plugin - Hide Widget Type in Chart Type Dropdown From User Groups   This plugin hides widget types from the widget selection dropdown menu, for user groups set in the config of the plugin. The widget types hidden are set in the config. Widgets of that type that are pre-existing in dashboards can still be viewed and edited by all users.   Installation To install this plugin, download and unzip the attachment. Then drop the HideChartTypeByGroup folder into your plugins folder (/opt/sisense/storage/plugins). Enable the plugin on the Add-ons tab in the Admin section, wait for the plugin to build, and the plugin will be enabled. Configure To configure this plugin, set the two parameters in the config.6.js file,  chartTypesToHide, and  userGroupToModify. chartTypesToHide is an array of strings of the chart names to hide in the dropdown menu,  userGroupToModify is an array of group OIDs for which users belonging to those groups the charts defined in chartTypesToHide will be hidden in the dropdown menu. Below is an example of a config.6.js file:   export const config = { // Chart types to hide in the dropdown, case sensitive, include full name chartTypesToHide: ["Bar Chart", "BloX"], // Members of these groups will have this plugin modify the dropdown // Group unique OID, can be copied from group API or from prism.user.groupNames userGroupToModify: ["6532f4a99760e6cbf6e18585", "3265f4a99760e3bbe3e18517"] }           Tip  - Include the full name of the Chart type exactly as it appears in the dropdown to avoid potentially hiding Charts that also include the string.   Below is an example of the plugin hiding the Bar Chart option in the widget selection dropdown for a user that is a member of one of the groups set in userGroupToModify.                                       How did the plugin work for you? What other type of plugin are you looking to learn more about? Let me know in the comments!

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago • Last reply 2 years ago
      2
               
    • Blog banner
      • BloxChevronRightIcon

      Sync Blox Input Element with Current Filter State

                                                                                                                       

      Sync Blox Input Element with Current Filter State When creating a Blox widget  and altering a filter within a dashboard or widget, it is sometimes advantageous to modify the default behavior, so that upon activating a Blox action , the Blox widget input or inputs retain the recently entered values instead of reverting to the default Blox inputs set in the Blox template. Additionally, when a user has permission to modify the relevant filter directly, bypassing Blox widget actions, and subsequently making changes to the filter that the Blox widget is designed to manipulate, the default Blox input values may no longer match with the current state of the filter. The following widget script offers a solution for this specific requirement by maintaining synchronization between the Blox input and the values of a selected filter, identifying the filter by its title. This approach guarantees that when the Blox widget is used to modify the filter, the input values persist, reflecting the current filter state as potentially altered by the Blox action. This synchronization ensures alignment between the Blox inputs and the filter values.     // Preserve Blox filter inputs after filter change, sync with current filter state widget.on('ready', function () { // Modify to match the relevant filter title let filterModifiedName = "days" filterModifiedName = filterModifiedName.toLowerCase() // Variable containing the Dashboard filter modified by Blox let modifiedFilter = widget.dashboard.filters.$$items.find(function (filterItem) { return !filterItem.isCascading && filterItem.jaql && filterItem.jaql.title && filterItem.jaql.title.toLowerCase() === filterModifiedName; }); // If relevant filter has from and to values selected (change as needed if filter uses members or any other type of filter) if (modifiedFilter && modifiedFilter.jaql && modifiedFilter.jaql.filter && modifiedFilter.jaql.filter.from && modifiedFilter.jaql.filter.to) { // Filter from and to value from dashboard filters let dateFilterFrom = modifiedFilter.jaql.filter.from let dateFilterTo = modifiedFilter.jaql.filter.to // Change filter ID selector as needed, to match ID parameter of input Elements let inputElementFrom = $('[id="filters[0].filterJaql.from"]', element)[0] let inputElementTo = $('[id="filters[0].filterJaql.to"]', element)[0] // Set value of inputs to value of filter inputElementFrom.value = dateFilterFrom inputElementTo.value = dateFilterTo } })     For Blox inputs that involve dates and modifications to date type filters, there is a dedicated Community article that includes a widget script for this particular functionality. Although initially developed for Blox-type widgets, this script can be adapted with minor adjustments to affect other elements on the page or non-Blox widgets. The fundamental principle involves utilizing the dashboard or widget filter object to retrieve filter values or values and then using a dashboard or widget (JavaScript) script to manipulate the HTML content of a text element or input present on the dashboard page. Share your experience in the comments! 

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago
      0
               
    • Blog banner
      • Widget & Dashboard ScriptsChevronRightIcon

      Animating Sisense Title Elements and Widgets Using CSS Animations

                                                                                                                       

      Animating Sisense Title Elements and Widgets Using CSS Animations Animating Sisense widget elements and dashboard/widget titles is a possible way to enhance the visual appeal and user experience of a Sisense dashboard or highlight a particular part of a dashboard or widget. Sisense widgets and titles are standard HTML elements, making it possible to apply animation using solely standard CSS stylesheets, much like other HTML elements on non-Sisense pages. Animations can include effects like fading widgets in or out and cyclic changes in the text color of widgets or titles. For documentation of CSS animation, you can explore the following pages which include many examples: Mozilla Developer Documentation on CSS Animation CSS Text Animations Examples CSS Text Animations Examples W3Schools CSS3 Animations In Sisense, widget and dashboard scripting can be used to add additional CSS stylesheets to the header of the HTML page using Javascript. CSS selector rules can be specific to a widget OID (all widgets have a unique OID, it is visible in the widget editor URL) in the selector string and not apply to any other widgets. Any valid CSS can be added via widget or dashboard scripting. Here's an example of a widget script that pertains to a text widget and animates the text color :     widget.on('ready', function () { var style = document.createElement('style'); // Any valid CSS will work in styleString and be appended to the CSS, including any CSS animation, modify as needed var styleString = ` [widgetid='${widget.oid}'] .wd div font { animation: color-animation 10s linear infinite; } @keyframes color-animation { 0% { color: blue } 32% { color: red } 45% { color: green } 55% { color: black } 66% { color: blue) } 80% { color: grey } 100% { color: purple } } ` if (style.styleSheet) { style.styleSheet.cssText = styleString; } else { style.appendChild(document.createTextNode(styleString)); } document.getElementsByTagName('head')[0].appendChild(style); });   Here's another example of CSS animation, in this example a widget is faded in and then out by modifying opacity :     widget.on('ready', function () { var style = document.createElement('style'); // Any valid CSS will work in styleString and be appended to the CSS, including any CSS animation, modify as needed var styleString = ` [widgetid='${widget.oid}'] .wd { animation: fadeIn ease 8s infinite; } @keyframes fadeIn { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } } ` if (style.styleSheet) { style.styleSheet.cssText = styleString; } else { style.appendChild(document.createTextNode(styleString)); } document.getElementsByTagName('head')[0].appendChild(style); });     It is also possible to animate other HTML elements within a Sisense dashboard, using the same principles.   Share in the comments if this is a code you tried and let us know how it went! 

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago
      0