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
    Technology
      • Product Feedback ForumChevronRightIcon
      Allow Multiple Providers for Sisense Intelligence
                                               
      Fred Ortmann
      Fred OrtmannPosted 1 week ago
               
      0
               
    • Blog banner
      • APIsChevronRightIcon

      UserReplaceTool - Automating Dashboard Ownership Transfers - Useful for Deleting User Accounts

                                                                                                       

      Automating Dashboard Ownership Transfer in Sisense with UserReplaceTool Managing and deleting user accounts in Sisense can create manual processes when users leave an organization or change roles. A frequent issue is the reassignment of dashboard ownership to prevent losing Sisense dashboards when a given user account is deleted, as deleting a Sisense user will delete all dashboards owned by that user. The UserReplaceTool addresses this task by automating the transfer of dashboard ownership of all dashboards owned by a given user, ensuring continuity and data integrity. Overview UserReplaceTool is a Python-based, API-based Tool solution designed to seamlessly transfer the ownership of dashboards and data models from one user to another in Sisense. This tool simplifies and automates this process, allowing organizations to reassign dashboard ownership without manual processes or the risk of losing dashboards and widgets. All components are accomplished by using Sisense API endpoint requests. Key Features Automated Dashboard Transfer : Reassigns ownership of all dashboards from the current user to a designated replacement user. Data Model Sharing : Ensures that all data models accessible and editable by the previous user are shared with the replacement user. Batch User Processing : Capable of handling multiple user transfers to a replacement user in a single operation, enhancing efficiency. Complete Logging:  All dashboards and datasource transferred are logged both in the console and in a separate log file. Setup Instructions 1. Setting Up the Environment To run the tool within a Python virtual environment , follow these steps: Activate the Python Virtual Environment : source /venv/bin/activate Create a Virtual Environment (if not already present): python3 -m venv .venv Install Dependencies : pip3 install -r requirements.txt For manual installation, including without using a Python virtual environment: pip3 install urllib3 jsonpath_ng pyyaml requests colorama 2. Configuration Edit the settings.yaml file to configure the tool. Key parameters include: Sisense Domain and Port : Specify the URL and port of your Sisense server, which is used for making API requests. API Bearer Token : Provide an admin-level bearer token for API authentication. See the  Sisense API Bearer Token Documentation for instructions on generating and using Bearer Tokens. Users to Replace : List the user IDs to be replaced. User IDs can be retrieved via the Users API or using the console command prism .user._id. Replacement User : Specify the user ID of the new replacement owner of all dashboards. This should typically be an admin level user. Data Model Sharing : Enable or disable the sharing of data models with the Replacement user (True or False), usually True. Dashboard Ownership Transfer : Enable or disable the transfer of dashboard ownership (True or False), usually True. Unlike Dashboards, if a user is deleted, the datasources themselves are not deleted from the server, ownership is automatically transferred to the main System Admin of the Sisense server.  3. Running the Tool Run the tool with the following command:   python3 replaceUser.py Practical Considerations Admin and Network Access : Ensure you have admin-level API access to the Sisense instance. Python Environment : Python3 and pip should be installed on the machine running the tool. All dependencies can be installed using Pip, and a Python virtual environment can be used. Once a Python Virtual Environment folder is set up it can be shared with the Tool and run directly on all systems using the same OS, but it is not cross OS compatible. By automating the transfer of dashboard ownership, UserReplaceTool provides a reliable and efficient solution for managing user transitions in Sisense. This ensures that datasources and dashboards remain accessible and under the control of the appropriate users, maintaining the continuity of Sisense resources. For further customization and configuration details, refer to the attached full Python Tool, which includes a README file and modify the "settings.yaml" file as necessary.    

      Jeremy Friedel
      Jeremy FriedelPosted 1 year ago • Last reply 3 months ago
      3
               
      • Sisense AdministrationChevronRightIcon

      Update and add new Highcharts modules for use in Sisense plugins

                                                                                                                       

      Update and add new Highcharts modules for use in Sisense plugins The JavaScript library framework Highcharts is natively included in Sisense and is utilized in many native Sisense widgets as well as in numerous Sisense plugins. Although Sisense typically does not alter the Sisense Highcharts library version with every release, the versions of Highcharts included in Sisense may change when upgrading to a new major version release. Highcharts can load additional chart types and other types of functionality via JS module files that contain code-adding features such as additional chart types, which can be used within plugins along with additional code to create additional widget types. If a plugin utilizes a Highcharts module, you can source the module directly in the "plugin.json" file's source parameter, as shown in this example:   "source": [ "HighchartModule.js", ],   To determine the current Highcharts version being used in your Sisense version, you can use the "Highcharts" command in the web console while viewing any page on your Sisense server. After identifying the current Highcharts version, you can find the corresponding module hosted on the Highcharts code hosting website using the following URL format: https://code.highcharts.com/${Highcharts_Version}/modules/${module_name}.js  For example: https://code.highcharts.com/6.0.4/modules/heatmap.js You can save this module and upload it to the plugin folder or replace the older module JS file simply by copying and pasting the code directly. Be sure to update the "plugin.json" file to point to the new module file if the file name has changed or if this is the first time the module is included. Simply sourcing the module file in the "plugin.json" file is sufficient to load the module into Highcharts; no further code is required to load the module.

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago • Last reply 1 year ago
      2
               
    • Blog banner
      • Widget & Dashboard ScriptsChevronRightIcon

      Advanced Scripting for Sisense’s Simply Ask NLQ: Adding Currency Symbols to Pivot Tables

                                                                                       

      Advanced Scripting for Sisense’s Simply Ask NLQ: Adding Currency Symbols to Pivot Tables Sisense’s Simply Ask feature empowers users to create visualizations instantly by asking natural language questions, simplifying data exploration without needing technical expertise or manual widget configuration. This Natural Language Query (NLQ) capability accelerates insights generation without the need to manually create new Sisense widgets through the standard UI for each question or data relationship of interest.   User interface of the 'Simply Ask - Sample ECommerce' dashboard in the Sisense data analytics tool. A central search bar at the top allows users to 'Ask a question about your data.' Below the search bar is a large icon with a plus symbol, inviting users to ask questions about dashboard data. On the right, there is a list of 'Available fields' categorized under Commerce, Category, Country, and Formulas, with individual fields like Brand ID, Amount, Age Range, and Revenue. A 'Done' button is highlighted at the bottom. A previously published article discussed how to customize Simply Ask NLQ-generated widgets using scripting, providing multiple examples of basic modifications and more detail on the basic mechanics of scripting Simply Ask widgets. This article will share a more complex example to show how NLQ widgets can be customized in more complex conditional ways, in this example: programmatically adding currency symbols to specific data columns in pivot table widgets generated by Simply Ask. Enhancing NLQ Widgets with Currency Symbols When dealing with financial data, it’s can be important to display currency symbols alongside numeric values for clarity, especially if multiple currency may be used in a datasource. However, Simply Ask NLQ-generated widgets may not automatically include these symbols, especially if they are dynamically generated based on natural language user queries. The following script demonstrates how to: Detect when a new NLQ widget is rendered. Identify specific data columns based on their titles using purely HTML based Javascript observation. Append a currency symbol to the numeric text values in those columns. Full Script:   const addCurrencySignToValues = () => { // Column keywords to match (case-insensitive) const currencySignColumns = ['Revenue', 'Cost']; // Replace with actual column keywords as needed // Shared constants for selectors and class patterns const NLQ_MAIN_CONTENT_SELECTOR = 'div.nlq-widget__main-content'; const DATA_CELL_SELECTOR = 'td.table-grid__cell--data'; const HEADER_CELL_SELECTOR = 'td.table-grid__cell--row-0'; const INNER_CONTENT_SELECTOR = 'div.table-grid__content__inner'; const ROW_CLASS_PREFIX = 'table-grid__cell--row-'; const COLUMN_CLASS_PREFIX = 'table-grid__cell--col-'; const NUMBER_PATTERN = /^[0-9,.]*$/; // Determine the default symbol to use let symbol = '$'; // Function to get the cell's row and column indices const getCellPosition = (cell) => { let classList = cell.classList; // Find row and column classes let rowClass = Array.from(classList).find(cls => cls.startsWith(ROW_CLASS_PREFIX)); let columnClass = Array.from(classList).find(cls => cls.startsWith(COLUMN_CLASS_PREFIX)); // If both classes are found, extract indices if (rowClass && columnClass) { let rowIndex = rowClass.split(ROW_CLASS_PREFIX)[1]; let columnIndex = columnClass.split(COLUMN_CLASS_PREFIX)[1]; return { row: parseInt(rowIndex, 10), column: parseInt(columnIndex, 10) }; } // Return null if not found return null; }; // Build a mapping of column indices to titles let columnHeaders = {}; // Collect header cells (row 0) $(HEADER_CELL_SELECTOR).each(function () { let cellPosition = getCellPosition(this); if (cellPosition) { let headerText = $(this).text().trim(); columnHeaders[cellPosition.column] = headerText; } }); // Process data cells $(`${NLQ_MAIN_CONTENT_SELECTOR} ${DATA_CELL_SELECTOR}`).each(function () { let cellText = $(this).text(); let cellPosition = getCellPosition(this); if (cellPosition) { let columnIndex = cellPosition.column; let columnTitle = columnHeaders[columnIndex]; // Check if column title includes any of the keywords (case-insensitive) if ( currencySignColumns.some(keyword => columnTitle.toLowerCase().includes(keyword.toLowerCase()) ) && NUMBER_PATTERN.test(cellText) ) { let changedText = `${cellText}${symbol}`; // Modify the text within the inner div $(this).find(INNER_CONTENT_SELECTOR).text(changedText); } } }); }; // Set up the MutationObserver to detect when new NLQ widgets are rendered dashboard.on('domready', function () { $('button.nlq-main-button').click(function () { setTimeout(function () { // Observe changes in the widget container const elementToObserve = $('div.nlq-widget__main-content')[0]; const observer = new MutationObserver(function () { addCurrencySignToValues(); }); observer.observe(elementToObserve, { subtree: true, childList: true }); }, 1000); }); }); ​   How It Works Define Column Keywords : The currencySignColumns array variable contains the keywords for column titles to target (in this example, ‘Revenue’ and ‘Cost’). The script will search for these keywords in the pivot column headers to determine which columns should have the currency symbol appended. Set Up Constants for CSS Class Names : Constants for selectors and class prefixes are defined for easy maintenance and readability. Determine the Currency Symbol : By default, in this example the symbol is set to '$'. It can be modified to any symbol required. If needed this can be be read from a Javascript variable set by a plugin or other script, to determine the correct currency symbol to use for a dashboard and datasource. Get Cell Position Function : The getCellPosition function extracts the row and column indices from a cell’s class list, which follows a naming convention contained in class names. Build a Column Headers Map : The script builds a mapping (columnHeaders) of column indices to their respective header titles by iterating over header cells. This same concept and code can be used in other scripts that customize pivots based on header titles. Process Data Cells : Iterates over all data cells, checks if cell's column title matches any of the keywords, and if the cell content is numeric. Append Currency Symbol : If both conditions are met, the script appends the currency symbol to the cell’s text within the inner content div. Set Up Mutation Observer : The script uses a MutationObserver to detect when a new NLQ widget is rendered in the Simply Ask modal. It observes the main widget content element for changes and triggers addCurrencySignToValues when mutations occur. Customization Tips Updating Keywords : Modify the currencySignColumns array to include any keywords relevant to the data columns in question. Changing the Currency Symbol : Change the symbol variable to use a different currency symbol or to dynamically set it based on user preferences or locale. Adjusting Number Pattern : The NUMBER_PATTERN regex can be adjusted to match different numeric data includes different formats (e.g., decimal points, thousand separators). Use as Template:  This example can be used as a template for completely different types of custom scripting of Simply Ask NLQ widgets. Integrating the Script into a Dashboard To use this script in a Sisense environment: Add the Script to the Dashboard : Place the script within the dashboard’s JavaScript editor. This will ensure it runs when the dashboard is loaded.   Considerations CSS Class Updates : CSS class names can change between Sisense versions; keeping CSS selector strings as variables allows for easy updating if needed. Conclusion By leveraging standard JavaScript, and dashboard scripting NLQ-generated widgets can be enhanced in Sisense’s Simply Ask feature with advanced customizations like appending currency symbols to specific data columns. This approach allows for maintaining consistency and clarity in data presentations, providing a better experience for users interacting with dynamically generated visualizations. This example shows that complex widget scripting customizations are possible with Simply Ask NLQ generated widgets. Related Content: For more details on scripting with Simply Ask NLQ and additional examples, refer to the previous article Modifying Simply Ask Natural Language Query Generated Widgets with Scripting .

      Jeremy Friedel
      Jeremy FriedelPosted 1 year ago
      0
               
    • Blog banner
      • Widget & Dashboard ScriptsChevronRightIcon

      Customizing the Offset Size of Selected Categories in Sisense Pie Chart Widgets

                                                                                               

      Customizing the Offset Size of Selected Categories in Sisense Pie Chart Widgets In Sisense, pie chart widgets, including the various pie chart style options such as donut charts, are a common way to visualize data. By default, when a user selects a category within a pie chart, that slice "pops out" to highlight the selection. This article explains how to customize the offset size of selected categories in Sisense pie chart widgets by leveraging the  Highcharts settings of the widget using Sisense widget scripting capabilities. Understanding the Default Behavior When a slice in a Sisense pie chart is selected, it moves outward from the center by a medium-sized offset. The size of this move is set by the slicedOffset property in the Highcharts setting object , Highcharts is the JavaScript visualizing library that Sisense uses for rendering pie charts and many other common widget types.   Default Pie Chat Offset Default Donut Pie Chart Offset The Highcharts slicedOffset Property The slicedOffset property determines the distance, in pixels, that a slice is moved when it is selected or "sliced out." By modifying this property, it is possible to increase or decrease the offset to better fit the visualization requirements. The unit is always set in raw pixels as an integer and not any other unit. Accessing and Editing the Highcharts Options Object via Sisense Widget Scripting To customize the Highcharts settings object within a Sisense widget, the beforeviewloaded event in a widget script can be used. This specific widget event is triggered immediately before Highcharts is used to begin rendering the already fetched and processed data from the Sisense data source, allowing customization of the chart options before the chart is displayed. Implementation Steps   Add a Widget Script Navigate to the pie chart widget in question. Open the widget's script editor to add a custom script. Insert the Custom Script Use the following code as a template:     // Increase or decrease the size of the offset when a slice in the pie/donut chart is selected widget.on('beforeviewloaded', function (widget, env) { // Pixels to move the selected slice when selected var slicedOffset = 50; // Set the desired offset size here // Access Highcharts options for the widget var chartOptions = env.options; // Access pie chart specific Highcharts options var pieOptions = chartOptions.plotOptions.pie; // Set the offset of the selected slice pieOptions.slicedOffset = slicedOffset; });       Customize the Offset Value Modify the slicedOffset variable to the desired number of pixels. For example, to decrease the offset size, it might be set to 3, to increase it might be set to 50. Trial and error can be used to determine the best value for the use case.   Offset Increased to 50   Offset Increased to 50 in Donut Pie Chart   Save and Refresh Save the script and refresh the dashboard. The pie chart will now reflect the new offset size when a slice is selected. Important Considerations   Highcharts Version Compatibility Sisense uses a specific version of Highcharts that may differ between Sisense versions and might not always be the latest available version of Highcharts. To ensure compatibility:   Check the Highcharts Version : In the browser's console , enter Highcharts.version to display the current Highcharts version used by Sisense.   Consult Appropriate Documentation : Use the version number to refer to the correct Highcharts documentation in case any settings have changed between versions. Viewing Current Highcharts Settings For advanced customizations, it can be helpful to view the existing explicitly defined by Sisense Highcharts options for a widget:     widget.on('beforeviewloaded', function (widget, env) { console.log('Highchart Options: ', env.options); // Logs the current Highcharts options to the console });     By printing the env.options paramater, other customizable properties within the Highcharts configuration object can be viewed. Not all possible or relevant Highchart options are by default defined by Sisense when the default value is not being modified, for example, the slicedOffset parameter is not set directly by Sisense, it did not exist in the default Sisense Highchart Setting object before being defined in a script, the Highchart documentation contains the full list of possible Highcharts settings.   Extending Customizations The scripting customization method described here isn't limited to adjusting the slicedOffset property. Any Highcharts setting available for the chart type by altering the env.options object within the beforeviewloaded event. This allows extensive visual customization of Sisense widgets. Conclusion Customizing the offset size of selected slices in Sisense pie chart widgets can enhances the interactivity and readability of pie chart visualizations. By tapping into the Highcharts settings through the beforeviewloaded event, it is possible to have granular control over the chart's behavior and appearance. Other Highcharts settings can also be modified using this script template. References Highcharts Documentation : Highcharts Reference Sisense Documentation on Widget Scripts : Sisense Widget JavaScript Sisense Academy course on Widget Plugins and Scripts: Advanced Dashboards with Plug-ins and Scripts

      Jeremy Friedel
      Jeremy FriedelPosted 1 year ago
      0
               
    • Blog banner
      • Widget & Dashboard ScriptsChevronRightIcon

      Modifying Simply Ask Natural Language Query Generated Widgets with Scripting

                                                                                       

      Modifying Simply Ask Natural Language Query Generated Widgets with Scripting Sisense's Simply Ask  feature empowers users to create visualizations instantly by asking natural language questions, simplifying data exploration without needing technical expertise or manual widget configuration. This Natural Language Query (NLQ) capability accelerates data exploration and insights generation without needing to manually create new Sisense widgets through the standard UI for each question or data relationship of interest.   User interface of the 'Simply Ask - Sample ECommerce' dashboard in the Sisense data analytics tool. A central search bar at the top allows users to 'Ask a question about your data.' Below the search bar is a large icon with a plus symbol, inviting users to ask questions about dashboard data. On the right, there is a list of 'Available fields' categorized under Commerce, Category, Country, and Formulas, with individual fields like Brand ID, Amount, Age Range, and Revenue. A 'Done' button is highlighted at the bottom. However, these automatically generated widgets differ from standard widgets in that they do not trigger typical widget scripting events , which poses a challenge for applying custom modifications. This limitation can hinder the application of standard dashboard and widget scripting customizations directly to these widgets. Fortunately, it is possible to modify NLQ-generated widgets programmatically by using JavaScript to manipulate the HTML of the fully rendered widgets without relying on Sisense-specific widget scripting events. This approach involves observing changes in the Simply Ask modal DOM and applying custom code when new widgets are rendered. This article explores how to implement such customizations, providing code examples and notes to help customize NLQ-generated Simply Ask widgets.   Understanding the Context Standard Sisense widgets support a variety of events (such as `beforequery` , `processquery` , `ready` , etc.) that run custom code at specific points in the widget’s query and rendering lifecycle, allowing developers to inject functionality and modifications seamlessly. However, NLQ-generated widgets within the Simply Ask modal do not trigger these events. This means that any scripts using these events will not directly apply to NLQ widgets until they are pinned (added) to the dashboard as standard Sisense widgets. When an NLQ widget is pinned using the dashboard pin UI button in the Simply Ask modal, it becomes a native Sisense dashboard widget, fully compatible with standard scripting. However, for scenarios where it is desired to apply customizations immediately within the Simply Ask modal for new generated NLQ widgets, without pinning the widget, code-based customization is still possible.   Solution Overview To modify NLQ-generated widgets within the Simply Ask modal, JavaScript provides an effective solution through the use of `MutationObserver` and direct manipulation of HTML elements. Specifically, you can: Use JavaScript to observe changes in the DOM Element : By setting up a standard JavaScript `MutationObserver` , you can detect when new widgets are rendered in the Simply Ask modal DOM. Apply custom modifications to the widget's HTML : Once the observer detects a new widget, you can manipulate the HTML elements using standard JavaScript or jQuery, including adjusting the styling. Implementing the Solution Setting Up a Mutation Observer First, the code must detect when a new widget is rendered in the Simply Ask modal. You can do this by observing changes in the modal's main content area using a Javascript  MutationObserver . The code for this MutationObserver  can run within the dashboard script, like all dashboard scripts, this will be specific to the current dashboard. This MutationObserver  could also exist in plugin code if this customization is designed to run on all dashboards with Simply Ask enabled, without modifying the dashboard script for each dashboard. This flexibility allows custom modifications to be reusable across different dashboards by placing the `MutationObserver` in plugin code or specific to one dashboard by placing it in that dashboard's script.   dashboard.on('domready', function () { // Listen for clicks on the Simply Ask button $('button.nlq-main-button').click(function () { setTimeout(function () { // Select the widget container area of the Simply Ask modal const elementToObserve = $('div.nlq-widget__main-content')[0]; // Create a MutationObserver instance const observer = new MutationObserver(function () { // Call your custom function when a change in DOM is detected customizeNLQWidget(); }); // Start observing the DOM for mutations observer.observe(elementToObserve, { subtree: true, childList: true }); }, 1000); // Delay to ensure the modal is fully rendered }); }); ​     Explanation : Event Listener : The script runs on the click event using an event listener on the Simply Ask UI button ( `nlq-main-button` ) which opens the Simply Ask NLQ modal. Delay with setTimeout : A short delay ensures that the Simply Ask modal is fully rendered before setting up the observer. MutationObserver:  Observes the NLQ-generated widget HTML element using the selector `nlq-widget__main-content` for any changes, specifically when new child nodes are added (i.e., when a new widget is generated on an NLQ query). subtree : Ensures that mutations to child elements of the main content area are also detected. childList : Watches for changes in the direct children of the observed element (e.g., when new widgets are added). Callback Function : When a mutation is observed, the `customizeNLQWidget` function is called to apply custom modifications. This can be any function or series of functions.   Example Customization Function The function that applies the desired modifications to the NLQ widget can be defined with any valid JavaScript. In this example, the function will modify the pivot header cells' styling.   const customizeNLQWidget = () => { const NLQ_MAIN_CONTENT_SELECTOR = 'div.nlq-widget__main-content'; const HEADER_CELL_SELECTOR = 'td.table-grid__cell--row-0'; // Apply CSS styles to the pivot table header cells $(`${NLQ_MAIN_CONTENT_SELECTOR} ${HEADER_CELL_SELECTOR}`).css({ 'background-color': 'blue', 'color': 'white', }); }; ​   Explanation : Selectors : The function targets the pivot table header cells within the NLQ modal. These can be any valid CSS selector, in this example they are: `NLQ_MAIN_CONTENT_SELECTOR` : Selects the main content area of the NLQ widget. `HEADER_CELL_SELECTOR` : Selects the header cells of a pivot table type widget. CSS Styling : Applies custom CSS styling to change the background color to blue and the text color to white.   Combining the Code   For clarity, here's the combined code that brings together the observer setup and the example custom code customization function:   dashboard.on('domready', function () { $('button.nlq-main-button').click(function () { setTimeout(function () { const elementToObserve = $('div.nlq-widget__main-content')[0]; const observer = new MutationObserver(function () { customizeNLQWidget(); }); observer.observe(elementToObserve, { subtree: true, childList: true }); }, 1000); }); }); const customizeNLQWidget = () => { const NLQ_MAIN_CONTENT_SELECTOR = 'div.nlq-widget__main-content'; const HEADER_CELL_SELECTOR = 'td.table-grid__cell--row-0'; $(`${NLQ_MAIN_CONTENT_SELECTOR} ${HEADER_CELL_SELECTOR}`).css({ 'background-color': 'blue', 'color': 'white', }); };   Customization Examples The example above modifies the header cells of a pivot table. However, you can extend the `customizeNLQWidget` function to perform various other customizations, such as: Adding Icons or Indicators : Inject visual cues for critical data points or thresholds directly into cells. Conditional Formatting: Automatically highlight data cells based on values (e.g., flagging negative values in red). Interactive Elements : Add clickable elements, like links or buttons, to enable dynamic user interactions, such as drilling into specific data. Content Modification : Alter or append units, currency symbols, or other data-specific indicators to ensure clarity and consistency in data presentation. Example: Conditional Formatting Based on Values   const customizeNLQWidget = () => { const NLQ_MAIN_CONTENT_SELECTOR = 'div.nlq-widget__main-content'; const DATA_CELL_SELECTOR = 'td.table-grid__cell--data'; $(`${NLQ_MAIN_CONTENT_SELECTOR} ${DATA_CELL_SELECTOR}`).each(function () { const cellValue = parseFloat($(this).text()); if (cellValue < 0) { $(this).css('color', 'red'); } else if (cellValue > 0) { $(this).css('color', 'green'); } }); }; ​   Explanation : Data Cells Selector : Targets all data cells in the pivot table. Value Parsing : Converts the cell text to a numeric value. Conditional Styling : Applies red color to negative values and green color to positive values. Considerations and Best Practices Scope : Ensure your selectors are specific enough to avoid unintended modifications to other parts of the dashboard. Compatibility Testing : Regularly test your customizations across different Sisense versions to ensure that changes in CSS classes or HTML structures don't break the functionality. CSS Class Updates : CSS class names can change between Sisense versions; keeping CSS selector strings as variables allows for easy updating if needed. Conclusion While NLQ-generated widgets in Sisense's Simply Ask feature do not support standard widget scripting events, the ability to observe DOM changes and manipulate HTML allows for extensive customizations. With JavaScript, you can enhance widget appearance, add interactive functionality, and more—all within the Simply Ask modal, providing a tailored user experience. This approach allows maintaining consistency in dashboards and provides a more customizable experience for users interacting with NLQ-generated visualizations.   Further Reading Sisense Javascript Reference   Sisense Scripting Sisense Simply Ask NLQ

      Jeremy Friedel
      Jeremy FriedelPosted 1 year ago
      0
               
      • 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
               
      • Sisense AdministrationChevronRightIcon

      How to properly reboot a k8s node(s)

                                       

      How to properly reboot a k8s node(s) As part of maintaining a healthy and robust Kubernetes (K8s) cluster, occasional reboots of nodes might be necessary. Whether for system updates, hardware maintenance, or other reasons, it's essential to follow a structured process to ensure minimal disruption to running workloads. Below is a step-by-step guide on safely rebooting nodes within a Kubernetes cluster, covering both Red Hat Enterprise Linux (RHEL) and Ubuntu systems. Considerations: Sequential Draining: In a 3-node cluster where 2 nodes are for app/query and 1 is for the build, nodes must be drained individually. This ensures that there is always a node available for serving requests. Sequential Reboot: Another node cannot be drained until the previous one is fully booted and functioning. At any given time, must be 2 nodes available to maintain cluster stability. Preparation for Builds: Before starting to drain nodes, it is required to ensure that no builds are running and all scheduled build jobs are suspended. This prevents any disruptions to the build processes during the node rebooting process. Wait for Pod Migration: It is essential to wait until all pods are successfully moved to another node and are up and running before stopping K8s control plane resources using Docker commands. Failure to do so could result in some services not being fully migrated, leading to unexpected issues after rebooting a node. Verification Before Next Step: Once a node is rebooted and uncordoned, before draining the next node, ensure that all pods are running and move to the uncordoned node. This ensures that the cluster maintains its desired state and prevents any disruptions to ongoing workloads. Step 1: Identify Nodes Firstly, you need to identify the nodes within your Kubernetes cluster. You can do this by running the following command: kubectl get nodes This command will provide you with a list of all nodes along with their current status. Step 2: Drain the Node(s) Before rebooting a node, it's crucial to gracefully evict all the pods running on that node to ensure they are rescheduled elsewhere in the cluster. To do this, you can use the kubectl drain command. For instance: kubectl drain <node_name> --ignore-daemonsets --delete-emptydir-data Replace <node_name> with the name of the node you want to drain. The --ignore-daemonsets flag ensures that system daemons (e.g., monitoring agents) running as DaemonSets are not evicted. The --delete-emptydir-data flag removes data stored in emptyDir volumes associated with the pods. Step 3: Stop Kubernetes Services (RHEL) For Red Hat Enterprise Linux (RHEL) systems, it's necessary to stop certain Kubernetes-related Docker containers before rebooting the node. Execute the following commands with root access: docker stop kubelet kube-proxy kube-scheduler kube-controller-manager etcd kube-apiserver systemctl stop docker Stopping these services ensures that Kubernetes components are gracefully shut down before the node is rebooted. Step 3: Stop Kubernetes Services (Ubuntu) On Ubuntu or other Debian-based systems, Kubernetes components are typically managed as systemd services. Therefore, you would handle stopping and starting these services differently. You would typically use commands like systemctl stop <service_name> to stop services. For example, on an Ubuntu system, you might stop Kubernetes components like this: sudo systemctl stop kubelet kube-proxy kube-scheduler kube-controller-manager etcd kube-apiserver Step 4: Reboot the Node Now, you can proceed with rebooting the node using your system's standard reboot command. Step 5: Verify Docker Service After the node has rebooted, ensure that the Docker service has started successfully: systemctl status docker Step 6: Start Kubernetes Services (RHEL) Once Docker is up and running, you need to ensure that the essential Kubernetes services are also running. Execute the following commands: docker start kubelet kube-proxy kube-scheduler kube-controller-manager etcd kube-apiserver Verify that all necessary containers are running using: docker ps | grep kube Step 6: Start Kubernetes Services (Ubuntu) After the node has rebooted, you can start the Kubernetes services on Ubuntu using systemd commands: sudo systemctl start kubelet kube-proxy kube-scheduler kube-controller-manager etcd kube-apiserver Step 7: Uncordon the Node After confirming that the node is back online and all Kubernetes services are running, you can mark the node as schedulable again: kubectl uncordon <node_name> This command allows the Kubernetes scheduler to resume placing pods on the node. Step 8: Verify Node Status Finally, confirm that the node is back in a ready state and available for scheduling pods: kubectl get nodes This command should display all nodes in the cluster, with the previously rebooted node now showing as ready. By following these steps, you can safely reboot nodes within your Kubernetes cluster while minimizing disruption to your running workloads.

      Vlad Solodkyi
      Vlad SolodkyiPosted 1 year ago
      0
               
    • Blog banner
      • Widget & Dashboard ScriptsChevronRightIcon

      Advanced Pivot Widget Scripting - Combining Custom JAQL and the Pivot 2.0 API

                                                                                                       

      Advanced Pivot Widget Scripting - Combining Custom JAQL and the Pivot 2.0 API While the Pivot Table Widget Type is a highly customizable and flexible Sisense widget for representing data in tabular form, certain use cases may be best achieved through custom code and scripting . The Pivot 2.0 JavaScript API facilitates the modification of existing pivot table cells, including updating cell values and adding data to cells not present in the initial results. This article reviews a use case where pivot table values are replaced with specific string text, substituting non-descriptive integers (in this case, the integer "1"). Although adding an additional row dimension could accomplish this visualization, it was not desired in this case due to the existing extensive nesting of three-row dimensions and columns. Adding a fourth row would significantly increase blank spaces in the table, reducing information density and readability. The replacement value will be retrieved from the data source via a second JAQL API request , as the dimension data replacement values are not present in the widget's initial raw results. JAQL is the query language used by all native Sisense widgets to fetch data from data sources. While the JAQL request and code to find the correct value for each cell are specific to this data source, table, and use case, the principles and methods—combining custom JAQL requests and using the Pivot 2.0 JavaScript API—are versatile techniques applicable to many other use cases. This particular widget script can serve as a general example. Other use cases might be simpler but will employ the same techniques. Each half of this solution can be used independently as examples, depending on the specific use case: one half being a secondary JAQL query based on the initial query, and the other matching the cell value to specific raw result values, regardless of their origin. To better illustrate the specific use case, which aids in understanding the code and the widget table from which this request originated, this example involves three rows in the pivot widget, one column, and one value. The relationships between the rows were not 1:1. For example, a "Division" contains multiple "Assets," each with multiple dates and descriptions. Each "Date" has multiple "Description" and "Value" pairs, with each "Description" corresponding to only one "Value." Each "Date" would have multiple "Description" and "Value" pairs. Somewhat unusually these "Descriptions" and "Values" are always 1:1 in the datasource for a specific date (and other rows), no"Description" would correspond to more than one "Value" and the "# of unique Values" would always be "1" in the table. In this pivot table, "Description" is a column, not a row. Below is a screenshot showing some of these "Description" and "Value" pairs, in a different table widget. The "Values" can be both non-numeric strings and integers in text form.   Before any code is applied via widget script a column in the pivot table widget in question appears as below, where any value if present would be "1", as there is one unique value present. The script retrieves the corresponding values from the data source via JAQL and replaces the "1" with the appropriate value. In this example, the values are temperatures, not counts of unique values, as shown below.     Each replacement involves finding the matching value for a given description of the current value. There will always be only one matching value for each description (the description being the columns in this table). Each row will contain multiple descriptions, and each row (which is a date) will not contain all descriptions, so some columns will be blank. The dimensions used for this query are very similar to the current widget metadata dimensions, so these will be copied and used as a template. The " beforequery " event is used to run this additional query just before the main JAQL query of the widget.     // Replace "# of Unique Values" with matching cell contents using custom JAQL call // Script runs before JAQL query sent to fetch data for widget widget.on('beforequery', function (_, widgetObject) { // Copy of current widget JAQL for modification for custom JAQL call let customJAQL = [...widgetObject.query.metadata];   The initial setup involves copying the current widget JAQL for modification for the custom JAQL call. Dimensions and panels not relevant to the request are removed and replaced with relevant ones. Any relevant filters, whether custom and defined in the script, or based on existing widget filters or dashboard filters present may also be added to filter the query values returned from the secondary JAQL request. The current metadata panels of a widget can be logged for assistance in finding the relevant dimensions with the console command:   prism.activeWidget.metadata.panels   The full path to finding a relevant dimension name is:   prism.activeWidget.metadata.panels[panelIndex].items[itemIndex].jaql.dim   Below is one example of replacing and modifying the JAQL payload.   // Replace JAQL of existing widget for values needed to find matching string contents to replace "# of Unique Values" cells // Modify as needed for future use to match names of dimensions as needed customJAQL[3] = { "jaql": { "dim": "[Brand.Brand]" } } customJAQL[4] = { "jaql": { "dim": "[Category.Category]" } }   Now that the modified JAQL query payload is ready, the API request can now be made, via the standard JAQL API endpoint.   // Full query object copy let customQuery = { ...widgetObject.query } // Set query metadata to modified metadata customQuery.metadata = customJAQL; // Function to make a custom JAQL Request function jaqlAPI(jaql) { // Use $internalHttp service if exists const $internalHttp = prism.$injector.has("base.factories.internalHttp") ? prism.$injector.get("base.factories.internalHttp") : null; // Ajax configurations const ajaxConfig = { url: "/api/datasources/" + encodeURIComponent(jaql.datasource.title) + "/jaql", method: "POST", data: JSON.stringify(jaql), contentType: "application/json", dataType: "json", async: false }; // Use $internalHttp service // else use default ajax request const httpPromise = $internalHttp ? $internalHttp(ajaxConfig, true) : $.ajax(ajaxConfig); // Return response return httpPromise.responseJSON; }; // Make custom JAQL call to return replacement string values let response = jaqlAPI(customQuery);   The Sisense internalHTTP library is used to make this request, as   shown in the examples in this article . This approach can help avoid many request header-related issues when making secondary JAQL requests in a widget or dashboard script. Using the existing widget query and adapting it, instead of creating an entirely new query from scratch, can be a useful technique in various other use cases, this is primarily for convenience and is identical to defining the equivalent JAQL payload in full within the script. The relevant values are now returned from the data source via the JAQL response and can be used to replace the values in the pivot cells with the correct values. Next, a function is defined to match the individual cells with the corresponding replacement value.   // Search for matching value from custom JAQL request with string replacement value for "# of Unique Values" cells function findMatchFromResponse(jaqlResponseValues, cellColumns) { // For date comparisons const parseDate = (dateString) => new Date(dateString).toISOString(); // Loop through JAQL response from custom call to find matching values for current cell for (let row of jaqlResponseValues) { // Set to false whenever any column does not match current cell let foundMatch = true; // Check columns ignores column after index 3, column 4 contains string match if previous three match for (let i = 0; i < 4; i++) { // For date columns convert to date ISO strings and compare if (cellColumns[i].name.includes("(Calendar)")) { if (parseDate(row[i].data) !== parseDate(cellColumns[i].member)) { // If all columns do not match go to next row in response foundMatch = false; break; } } else { // If not date string compare values directly if (row[i].data !== cellColumns[i].member) { // If all columns do not match go to next row in response foundMatch = false; break; } } } // If all columns before column index 4 all match, current row is a match and column index 4 contains the corresponding string contents if (foundMatch) { // Return contents to replace "1" in cell, stop search as match found return row[4].data; // Return the value of the fourth item in the matching row } } // Return null if no match is found in custom JAQL response return null; }   This function can now be used to match rows between the primary and secondary JAQL responses, which have different dimensions but share some row dimensions. The second function parameter corresponds with the specific format the Pivot 2.0 API uses to return values about the current column of the cell, with some modifications. To handle data dimensions, the function converts dates to Javascript programmatic date-type objects. This ensures that date formatting differences do not prevent finding an identical match. Other values are compared directly without conversion. This general logic flow can be adapted to find matching rows between any two JAQL requests with overlapping dimensions. Depending on the exact use case, slight modifications may be necessary. Similar code can be used not only for other pivot table use cases but also, for example, to modify tooltips by adding additional dimensions not present in the widget, for widget types that include tooltips, such as bar and column chart widgets. This particular function uses hard-coded column indexes, which can be changed to adaptive variables or different indexes as needed. Using the Pivot 2.0 API, the cell contents can now be replaced with the previously defined findMatchFromResponse function and the secondary JAQL responses. The Pivot 2.0 API provides the current cell's metadata (such as the exact name of the column), and position in the table (including row and column indexes), and allows overriding the existing value with a new value. The rowIndex: ['member'] ensures that only value cells are affected; no header or direct row value cells are impacted, so no additional conditions are needed to check if the current cell is a value cell. The cell metadata array, which contains information about the cell's row position, is modified by adding the cell's column to the same object. This provides a single array containing all the information needed to use the findMatchFromResponse function to find the matching replacement value with the already retrieved new values.   // Use Pivot 2.0 API to replace cell contents using findMatchFromResponse function to find values // Runs when table renders, but can be set in any widget event before widget.transformPivot({ rowIndex: ['member'] }, function (metadata, cell) { // All "# of Unique Values" are aimed to be "1" in current dataset and table if (!isNaN(cell.value)) { // Copy of current cell values, contains cell position in pivot except "Column" panel let metadataRows = [...metadata.rows] if (metadata.columns && metadata.columns.length > 0) { // Add current cell "Column" panel position metadataRows.push({ "name": "", "member": metadata.columns[0].member }) // Matching contents from custom JAQL for current cell position let newCellContent = findMatchFromResponse(response.values, metadataRows); // If match found if (newCellContent != null && newCellContent.length != 0) { // Replace "1" with matching string value from custom JAQL call cell.content = newCellContent; } else { cell.content = " "; } } } })   This type of functionality can be used independently of this particular instance. For example, the code could be adapted to use a constant dictionary included in the script instead of a secondary JAQL request. Cell styling with CSS style rules can also be added simultaneously with the cell content replacement. Here is the full script:   // Replace "# of Unique Values" with matching cell contents using custom JAQL call // Script runs before JAQL query sent to fetch data for widget widget.on('beforequery', function (_, widgetObject) { // Copy of current widget JAQL for modification for custom JAQL call let customJAQL = [...widgetObject.query.metadata]; // Replace JAQL of existing widget for values needed to find matching string contents to replace "# of Unique Values" cells // Modify as needed for future use to match names of dimensions as needed customJAQL[3] = { "jaql": { "dim": "[Brand.Brand]" } } customJAQL[4] = { "jaql": { "dim": "[Category.Category]" } } // Full query object copy let customQuery = { ...widgetObject.query } // Set query metadata to modified metadata customQuery.metadata = customJAQL; // Function to make a custom JAQL Request function jaqlAPI(jaql) { // Use $internalHttp service if exists const $internalHttp = prism.$injector.has("base.factories.internalHttp") ? prism.$injector.get("base.factories.internalHttp") : null; // Ajax configurations const ajaxConfig = { url: "/api/datasources/" + encodeURIComponent(jaql.datasource.title) + "/jaql", method: "POST", data: JSON.stringify(jaql), contentType: "application/json", dataType: "json", async: false }; // Use $internalHttp service // else use default ajax request const httpPromise = $internalHttp ? $internalHttp(ajaxConfig, true) : $.ajax(ajaxConfig); // Return response return httpPromise.responseJSON; }; // Make custom JAQL call to return replacement string values let response = jaqlAPI(customQuery); // Search for matching value from custom JAQL request with string replacement value for "# of Unique Values" cells function findMatchFromResponse(jaqlResponseValues, cellColumns) { // For date comparisons const parseDate = (dateString) => new Date(dateString).toISOString(); // Loop through JAQL response from custom call to find matching values for current cell for (let row of jaqlResponseValues) { // Set to false whenever any column does not match current cell let foundMatch = true; // Check columns ignores column after index 3, column 4 contains string match if previous three match for (let i = 0; i < 4; i++) { // For date columns convert to date ISO strings and compare if (cellColumns[i].name.includes("(Calendar)")) { if (parseDate(row[i].data) !== parseDate(cellColumns[i].member)) { // If all columns do not match go to next row in response foundMatch = false; break; } } else { // If not date string compare values directly if (row[i].data !== cellColumns[i].member) { // If all columns do not match go to next row in response foundMatch = false; break; } } } // If all columns before column index 4 all match, current row is a match and column index 4 contains the corresponding string contents if (foundMatch) { // Return contents to replace "1" in cell, stop search as match found return row[4].data; // Return the value of the fourth item in the matching row } } // Return null if no match is found in custom JAQL response return null; } // Use Pivot 2.0 API to replace cell contents using findMatchFromResponse function to find values // Runs when table renders, but can be set in any widget event before widget.transformPivot({ rowIndex: ['member'] }, function (metadata, cell) { // All "# of Unique Values" are aimed to be "1" in current dataset and table if (!isNaN(cell.value)) { // Copy of current cell values, contains cell position in pivot except "Column" panel let metadataRows = [...metadata.rows] if (metadata.columns && metadata.columns.length > 0) { // Add current cell "Column" panel position metadataRows.push({ "name": "", "member": metadata.columns[0].member }) // Matching contents from custom JAQL for current cell position let newCellContent = findMatchFromResponse(response.values, metadataRows); // If match found if (newCellContent != null && newCellContent.length != 0) { // Replace "1" with matching string value from custom JAQL call cell.content = newCellContent; } else { cell.content = " "; } } } }) })   When the script is applied to the widget, the values in the pivot table that were previously indistinguishable "1"s (for "# of unique values") are now replaced with the relevant values for each column, matching the corresponding description-value pair. This achieves the desired functionality, and the pivot table now displays the data as intended. This example is likely more complex than the typical use case, but it provides a comprehensive widget script example. Multiple parts of this script can be used in isolation to suit specific needs. The Pivot 2.0 JavaScript API is a powerful tool for creating highly customized data visualizations. By leveraging its capabilities, developers can customize pivot tables, enhancing the utility and readability of the data visualization. Whether for simple or complex use cases, the flexibility offered by the Pivot 2.0 Javascript API enables a wide range of customizations to meet various data visualization requirements. Share your experience in the comments!     

      Jeremy Friedel
      Jeremy FriedelPosted 1 year ago
      0
               
    • Blog banner
      • How-Tos & FAQsChevronRightIcon

      Converting an Existing Sisense Widget to a Dynamic ComposeSDK Widget Component

                                                                                                                       

      Converting an Existing Sisense Widget to a Dynamic ComposeSDK Widget Component Using either the widget Sisense API's , or the Sisense widget JS API object , it is possible to determine all components and parameters of a widget required to create an equivalent dynamic ComposeSDK widget component. This approach allows the extraction of all components and parameters of a widget, enabling the creation of an equivalent dynamic ComposeSDK widget component without directly referencing or relying on a specific widget or dashboard ID. The metadata of an existing widget contains all the information needed to create a dynamic version of an existing Sisense widget. It is also possible to use an existing widget ID and dashboard ID to render an existing widget in ComposeSDK , but this does not take full advantage of the capabilities of ComposeSDK to generate new widgets directly.  This method ensures flexibility, as it doesn't depend on the continued presence of the widget on the Sisense server.  The metadata of an existing widget holds all the necessary information to craft a dynamic version. While it's possible to use an existing widget ID and dashboard ID to render the widget in ComposeSDK, this doesn't fully exploit the capabilities of ComposeSDK for generating new widgets directly. The prism widget object  encompasses the metadata of all components essential for the widget. By utilizing the metadata panels object accessible in the JavaScript developer console, one can extract all relevant data about an existing widget for conversion to a dynamic ComposeSDK widget: Using this general JS widget object path, all metadata panels can be converted in sequence to dynamic ComposeSDK form:   prism.activeWidget.metadata.panels[panelIndex].items[itemIndex].jaql   The initial widget metadata in native Sisense, viewable using the above JS code using the JS widget API, will return all the information needed for conversion to a dynamic ComposeSDK widget. The initial widget to be converted into native Sisense in this article is below:   This above JS code using the JS widget API object will return all the values needed to convert to a dynamic ComposeSDK widget. From the information gathered from the first two panels, one can determine both the category dimension and the value used. The visible aggregation type  such as the sum method, can also be identified. Translating this into standard ComposeSDK syntax would be equivalent to:   dataOptions={{ category: [DM.Commerce.Gender], value: [measures.sum(DM.Commerce.Revenue)], }}   Similarly, the filter state of a dashboard or widget filter can be extracted and converted to dynamic ComposeSDK form. The dimension of the filter and the member state can be extracted to form the filter parameter of the chart. The addition of a filter to the chart, as reflected in the widget metadata panels, can be represented in ComposeSDK as follows: Using the dimension and member values from the panels, the ComposeSDK equivalent would be:   filters={[filters.members(DM.Commerce.Gender, ["Male"])]}   Applying filters to the widget will yield identical results in both ComposeSDK and native Sisense. Before the widget filter is applied (these screenshots are ComposeSDK widgets): and after: The final ComposeSDK dynamic widget code in this case is:   <Chart dataSet={DM.DataSource} title={"GENDER BREAKDOWN"} chartType={"pie"} dataSource={DM.DataSource} dataOptions={{ category: [DM.Commerce.Gender], value: [measures.sum(DM.Commerce.Revenue)], }} filters={[filters.members(DM.Commerce.Gender, ["Male"])]} />   This code can be dynamically modified, and all parameters can be changed to variables or React props, allowing dynamic changes using the full power of ComposeSDK. Additional filters or dimensions can also be added as needed. This fundamental principle of conversion can be applied to any other widget type or widget with significantly more parameters and dimensions. Widget Styling and other ComposeSDK widget parameters can also be added as needed with ComposeSDK.

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago
      0