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
    product features
      • Product Feedback ForumChevronRightIcon
      Allow Multiple Providers for Sisense Intelligence
                                               
      Fred Ortmann
      Fred OrtmannPosted 1 week ago
               
      0
               
    • Blog banner
      • News & UpdatesChevronRightIcon

      Every Metric Happens Somewhere: Why the “Where” Dimension Is the Missing Layer in Modern Analytics

                                               

      In our recent QBeeQ webinar, Every Metric Happens Somewhere, we explored why geospatial context is no longer a niche feature for specialized industries. It’s a core analytical dimension and one that can dramatically elevate engagement, insight discovery, and decision-making. You can view the recording , or keep scrolling for the highlights and key takeaways from the webinar. The Problem: Dashboards Without Geography Miss Patterns Traditional dashboards rely heavily on tables, bar charts, and line graphs. They show totals, trends, and rankings well. But they struggle to reveal spatial relationships. Consider common business questions: How many claims do we have? What’s our revenue by region? Which territories are underperforming? These are valuable questions, but they’re incomplete without geographic context. When you introduce the where dimension, new insights emerge: Clusters that only appear spatially Boundary effects between adjacent territories Pockets of unusually high or low performance Regional anomalies masked in aggregated totals From Niche Feature to Core Capability Maps were once considered specialized and useful for specific industries such as logistics, real estate, or field operations. That’s no longer true. Revenue, risk, compliance, performance, claims, customer distribution – these all happen somewhere. Geography cuts across industries. Approximately 80% of enterprise data already contains a spatial component. Most organizations simply aren’t leveraging it. Instead of placing a map as a static, standalone widget, leading teams are making it central to the analytical experience. Research shows interactive dashboards increase engagement and insight discovery. When you layer interactivity (zooming, filtering, panning) on top of geography, users uncover patterns faster and spend more time exploring. But friction often gets in the way. The Friction: Where Native Mapping Falls Short Many teams start with basic mapping tools that allow simple point plotting or polygon mapping. That works until analysis shifts to become more strategic. Common limitations appear quickly: Needing both points and polygons on the same map Wanting density visualizations or clustering Requiring multiple layers Enabling advanced drill behavior Supporting large-scale or time-based data When mapping tools can’t support these needs, one of three things happens: Maps sit unused on dashboards. Designers revert to traditional charts. Spatial thinking never becomes core to analysis.  Remove frictions with the QBeeQ mapping solutions SuperMap: Practical Spatial Analytics for Everyday Use The SuperMap is built for operational decision-making, understanding what’s happening right now, and acting on it. This plugin includes a range of flexible, scalable features, delivered as a zero-code solution, designed for both dashboard builders and end users. Multi-Layer Mapping (Points + Polygons) - Overlay geographic territories such as counties or states with individual location points, allowing each layer to have its own KPIs, category breakdowns, and sizing logic for richer comparative analysis. Heatmaps and Radius-Based Points - Color polygons by one performance metric while simultaneously sizing map points by another, delivering multi-dimensional insight within a single, unified view. Clustering with Drill-In Behavior - As users zoom in, clustered points automatically separate to reveal individual locations, with clusters capable of displaying proportional category breakdowns such as hospitals, police stations, and post offices. Advanced Tooltips - Enhance map interactivity with tooltips that go beyond a single value by displaying multiple KPIs, raw metrics, and calculated insights to support deeper exploration. Jump-to-Dashboard Navigation - Enable users to click on a territory to instantly filter the entire dashboard or navigate directly to a more detailed analytical view for focused investigation. Measure Switching - Allow users to toggle between different KPIs on the same map without duplicating visuals, reducing dashboard clutter while increasing analytical flexibility. Geographic Hierarchy - Support seamless geographic drill-down across states, counties, zip codes, and other levels—either directly within the map interface or through a structured dropdown selection. Deck.gl Map: High-Performance, Large-Scale Exploration While the Super Map focuses on operational clarity, Deck.gl is designed for scale and trends over time. For teams working with event streams, logistics data, or large geospatial datasets, Deck.gl provides performance without sacrificing interactivity. This plugin enables: Arc layers Hex bin density visualizations 2D and 3D polygon layers High-volume point plotting Hierarchical spatial analysis The Bigger Picture: Making “Where” a First-Class Dimension Time has long been treated as a foundational analytical dimension. Geography deserves the same status. When you: Align metrics to business geography Enable exploration instead of passive observation Remove friction from advanced spatial analysis Combine interactivity with spatial context You don’t just make dashboards prettier. You make them more useful. Every metric happens somewhere.  When you show that somewhere clearly, insights accelerate and decisions improve. At QBeeQ , we believe geography should be a first-class dimension in every analytical experience. Our mapping solutions are designed to remove friction, scale with your data, and make spatial insight accessible to every dashboard builder and decision-maker. Because when every metric happens somewhere, QBeeQ helps you see exactly where it matters most. QBeeQ is data consulting firm and also a Sisense Gold Implementation Partner. 

      Mia Isaacson
      Mia IsaacsonPosted 2 months ago
      0
               
    • Blog banner
      • APIsChevronRightIcon

      Connection Tool - Programmatically Remove Unused Datasource Connections, and List All Connections

                                                                                                                                                                       

      Connection Tool - A Tool to Programmatically Remove Unused Datasource Connections, and List All Connections     Managing connections within your Sisense server can become complex over time, if there are a large number of connections, and connections are often added, and replace earlier datasource connections. In some scenarios unused connections can accumulate, potentially cluttering the Connection Manager UI with no longer relevant connections. Although unused connections typically represent minimal direct security risk, it's considered best practice to maintain a clean, organized list of connections, and in some scenarios it can be desired to remove all unused connections. Sisense prevents the deletion of connections actively used in datasources, safeguarding your dashboards and datasources from disruptions. However, inactive or "orphaned" connections remain after datasources are deleted or a connection is replaced, potentially contributing to unnecessary UI complexity in the connection manager UI. Connections can be of any type Sisense supports, common types include various SQL connections, Excel files, and CSV files, as well as many data providers, such as Big Panda. This tool can also be used to list all connections, with no automatic deletion of unused connections. Introducing the Sisense Connection Prune Tool The Sisense Connection Prune Tool is a Python-based Sisense API based tool designed to programmatically identify and delete unused connections. It generates a CSV report listing all connections and their associated datasources, streamlining your connection management process. If desired, it can automatically remove all unused connections automatically from a Sisense server.   Using the Tool, sourcing the Virtual Environment, generating the Connection CSV   CSV Output of Used Connections and Associated Datasources   CSV opened visually to view as Table, Excel and other programs and text editors can open CSV files   Sisense Connection Prune Tool README Here's the full README included with the tool: # Sisense Connection Prune Tool A command-line tool to list used data connections and prune unused Sisense connections via CSV. It allows you to generate a CSV file of all connections and their dependencies, then delete those connections if needed, after removing the connections to keep from the CSV. ## Features - **Dry Run Mode**: Simulate deletions without making any changes. - **CSV-Based Flow**: Easily inspect and list connections to remove before deletion. - **Logging**: Extensive logs if needed. - **Error Handling**: Clear and descriptive messages for issues encountered during execution. ## Usage 1. **Activate the Virtual Environment** After downloading this project folder, activate the Python virtual environment bundled with it that includes all Python dependencies. - **Windows**: `venv\Scripts\activate` - **macOS/Linux**: `source venv/bin/activate` 2. **Configure the Tool** Open the `config.yaml` file and set your Sisense server URL, bearer token, CSV file path, and log file path. For example: ```yaml server_url: "https://your.sisense.server" bearer_token: "your_bearer_token_here" dry_run: true csv_file_path: "connections.csv" log_file_path: "connection_tool.log" ``` - **server_url**: The URL of your Sisense instance. - **bearer_token**: Your Sisense API token for authentication. - **dry_run**: If set to `true`, deletions will be simulated (no real deletions). - **csv_file_path**: Where the CSV file should be created and read from. - **log_file_path**: Where log file will be stored. 3. **Run the Tool** ```bash python3 ConnectionPruneTool.py ``` You will be prompted to choose an option: 1. **Generate connection CSV** - Fetches all Sisense connections. - Immediately removes (or simulates removing, if `dry_run` is `true`) any connection with no dependencies. - Writes all remaining connections and their dependencies to the CSV file. - **Important**: Inspect the CSV file and remove lines for any connections you want to **keep**. 2. **Delete connections from CSV list** - Reads the CSV file. - Removes or simulates removing each connection still listed. - Provides a summary report of which connections were deleted or bypassed. 4. **Review the Logs** Check the file specified in `log_file_path` for a record of all actions taken or simulated if needed. This is helpful for understanding what happened during each run and diagnosing any issues. ## Example Workflow 1. **Generate CSV** ```bash python3 ConnectionPruneTool.py # Choose option 1 when prompted ``` After generation, open the CSV file and **delete rows** corresponding to any connections you want to **keep**. 2. **Delete Connections** ```bash python3 ConnectionPruneTool.py # Choose option 2 when prompted ``` The tool will read the CSV and delete the remaining listed connections (or simulate deletion, if `dry_run` is enabled). ## Notes - Unused connections are removed automatically in step 1, without a CSV step - To keep a connection, remove its line from the CSV before proceeding with deletion. - If `dry_run` is set to `true`, no actual deletions will occur, only simulated logs and printed messages. - The log file will be cleared at the start of each run, so be sure to review or archive logs (or change log file name in config), if needed. ​ This is a command-line tool to list used data connections and prune unused Sisense connections, in general and via a CSV list. It allows a user with a data admin or higher bearer token to generate a CSV file of all connections and their dependencies, then delete those connections if needed, from the remaining connections in the CSV. Example Output: Deleted unused connection: Old_DB_Connection (ID: 123abc) CSV file generated at connections.csv. It contains 25 row(s) of active connections. Please review and remove lines for connections you want to keep before running deletion step by running tool again. Remaining lines will be deleted in deletion mode. Summary Report: Total lines in CSV (active used connections): 25 Deleted Unused Connections: - Old_DB_Connection No connections were bypassed.   API Endpoints Used Retrieve connections: GET /api/v2/connections Retrieve dependencies: GET /api/v2/connections/{connection_id}/getAllDependencies Delete connection: DELETE /api/v2/connections/{connection_id} Full Code connections.py - Uses Sisense API endpoints to: Fetch all connections (GET /api/v2/connections). Retrieve datasource dependencies for a specific connection (GET /api/v2/connections/{connection_id}/getAllDependencies). Delete a specific connection (DELETE /api/v2/connections/{connection_id}).     from helperFunctions import load_config, api_get, api_delete config = load_config() headers = {"Authorization": f"Bearer {config['bearer_token']}"} base_url = config["server_url"] # Retrieve all connections from Sisense def get_all_connections(): endpoint = f"{base_url}/api/v2/connections" return api_get(endpoint, headers) # Retrieve dependencies of a specific connection def get_connection_dependencies(connection_id): endpoint = f"{base_url}/api/v2/connections/{connection_id}/getAllDependencies" return api_get(endpoint, headers) # Delete a specific connection def delete_connection(connection_id): endpoint = f"{base_url}/api/v2/connections/{connection_id}" return api_delete(endpoint, headers) helperFunctions.py - Uses the Requests library to handle API requests (GET/DELETE). Catches and logs API errors. Uses PyYAML to read the config.yaml file for configuration.     import yaml import requests import logging # Load configuration from YAML file def load_config(): with open("config.yaml", "r") as file: return yaml.safe_load(file) # Configure logging settings def setup_logging(log_file): logging.basicConfig( filename=log_file, level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", ) # Perform a GET request with error handling def api_get(endpoint, headers): try: response = requests.get(endpoint, headers=headers, verify=False) response.raise_for_status() return response.json() except requests.RequestException as e: logging.error(f"GET request failed: {e}") print(f"Error during GET request: {e}") return None # Perform a DELETE request with error handling def api_delete(endpoint, headers): try: response = requests.delete(endpoint, headers=headers, verify=False) response.raise_for_status() return response except requests.RequestException as e: logging.error(f"DELETE request failed: {e}") print(f"Error during DELETE request: {e}") return None ConnectionPruneTool.py - Serves as the entry point for the entire tool. Implements a CLI for user interaction. Invokes connections.py and helperFunctions.py to retrieve, analyze, and optionally delete Sisense connections. Uses Pandas to build a DataFrame of connection details and writes the results to a CSV file. Reads back the CSV file to remove selected connections after manual edits. Manages logs and prints summary reports to the terminal. import pandas as pd import logging import os from connections import ( get_all_connections, get_connection_dependencies, delete_connection, ) from helperFunctions import load_config, setup_logging # Load config yaml config = load_config() setup_logging(config["log_file_path"]) # Clear log file at start open(config["log_file_path"], "w").close() dry_run = config["dry_run"] csv_path = config["csv_file_path"] def generate_connections_csv(): """ Retrieves all connections from Sisense, deletes any that have no dependencies (unless in dry_run mode), and writes the remaining used connections and their dependent data models to a CSV file. """ # Create or clear existing CSV open(csv_path, "w").close() connections = get_all_connections() if connections is None: logging.error("Failed to retrieve connections.") print("Failed to retrieve connections.") return data = [] deleted = [] bypassed = [] # Go through each connection returned from the API for conn in connections: dependencies = get_connection_dependencies(conn["oid"]) if dependencies is None: logging.warning( f"Failed to retrieve dependencies for: {conn['name']} ({conn['oid']})" ) print( f"Failed to retrieve dependencies for: {conn['name']} ({conn['oid']})" ) bypassed.append(conn["name"]) continue # If no dependencies, optionally delete the connection (not in dry-run) if not dependencies: action_msg = "[Dry Run] Would delete" if dry_run else "Deleted" log_str = ( f"{action_msg} unused connection: {conn['name']} (ID: {conn['oid']})" ) print(log_str) logging.info(log_str) if not dry_run: try: response = delete_connection(conn["oid"]) # If response is None, treat it as a failed deletion if response is None: logging.error( f"Failed to delete unused connection: {conn['name']} ({conn['oid']})." ) print( f"Error deleting unused connection: {conn['name']} ({conn['oid']})" ) bypassed.append(conn["name"]) else: deleted.append(conn["name"]) except Exception as e: logging.error( f"Exception while deleting unused connection: {conn['name']} ({conn['oid']}) - {e}" ) print( f"Error deleting unused connection: {conn['name']} ({conn['oid']})" ) bypassed.append(conn["name"]) else: # If the connection is used, add each dependency row to CSV. # Use a fallback value if 'title' or 'oid' is missing. for dep in dependencies: dep_title = dep.get( "title", "NULL TITLE Share Datasource with associated Bearer Token user to include in CSV", ) dep_oid = dep.get("oid", "NULL OID") data.append( { "Connection Name": conn["name"], "Connection ID": conn["oid"], "Elasticube/Data Model Name": dep_title, "Elasticube/Data Model ID": dep_oid, } ) # Create a DataFrame of only the used connections (with datasource dependencies) df = pd.DataFrame(data) df.to_csv(csv_path, index=False) row_count = len(df) logging.info(f"Generated CSV at {csv_path}") print( f"CSV file generated at {csv_path}. " f"It contains {row_count} row(s) of used connections.\n" "Please review and remove lines for connections you want to keep " "before running deletion step by running tool again. Remaining lines will be deleted in deletion mode." ) # Summaries for auto-deleted (unused) connections print("\nSummary Report:") print(f"Total lines in CSV (active used connections): {row_count}") if deleted: print("\nDeleted Unused Connections:") for d in deleted: print(f" - {d}") else: print("\nNo connections were deleted in this step.") if bypassed: print("\nBypassed Connections:") for b in bypassed: print(f" - {b}") else: print("\nNo connections were bypassed in this step.") def delete_connections_from_csv(): """ Reads the CSV (remaining lines after user review, removing lines for connections to keep), then deletes each connection listed. If a delete fails or returns None, the connection is logged and added to 'bypassed'. """ if not os.path.exists(csv_path): print( "CSV file does not exist. Please generate it first or fix the config path." ) return df = pd.read_csv(csv_path) deleted = [] bypassed = [] for _, row in df.iterrows(): conn_id = row["Connection ID"] conn_name = row["Connection Name"] action_msg = "[Dry Run] Would delete" if dry_run else "Deleted" log_str = f"{action_msg} connection: {conn_name} ({conn_id})" print(log_str) logging.info(log_str) if not dry_run: try: response = delete_connection(conn_id) if response is None: logging.error( f"Failed to delete connection: {conn_name} ({conn_id})" ) print(f"Error deleting connection: {conn_name} ({conn_id})") bypassed.append(conn_name) else: deleted.append(conn_name) except Exception as e: logging.error( f"Exception while deleting connection: {conn_name} ({conn_id}) - {e}" ) print(f"Error deleting connection: {conn_name} ({conn_id})") bypassed.append(conn_name) # Print summary report print("\nSummary Report:") if deleted: print("Deleted Connections:") for d in deleted: print(f" - {d}") else: print("No connections were deleted.") if bypassed: print("\nBypassed:") for b in bypassed: print(f" - {b}") else: print("No connections were bypassed.") if __name__ == "__main__": # If there is no CSV file found, select step 1 automatically if not os.path.exists(csv_path): print( "No CSV file found; defaulting to generating connections CSV. " "Correct config if CSV file name has changed." ) generate_connections_csv() else: choice = input( "Choose an option:\n" "1 - Generate connection CSV\n" "2 - Delete connections from CSV list\n" "Enter your choice (1 or 2): " ) if choice == "1": generate_connections_csv() elif choice == "2": delete_connections_from_csv() else: print("Invalid option. Please enter '1' or '2'.") Conclusion By automating the detection of inactive connections and simplifying their removal, the Sisense Connection Prune Tool reduces clutter in the Sisense server Connection Manager UI while minimizing the risk of unintentionally impacting active datasources. Whether you opt for a dry-run mode to review potential deletions in a generated CSV file, or to simply list connections, or proceed with the full removal of unused connections, this tool offers a clear, flexible, and reliable approach to keeping your connections organized. A full copy of the tool, is attached below.        

      Jeremy Friedel
      Jeremy FriedelPosted 1 year ago • Last reply 3 months ago
      4
               
    • Blog banner
      • How-Tos & FAQsChevronRightIcon

      Adding additional dimensions to the Scatter Map widget tooltip

                                                       

      Adding additional dimensions to the Scatter Map widget tooltip Additional dimensions can be added to the hover tooltip of the Scatter Map widget type, beyond the default limit of three, to include more information from other dimensions about a location in a Scatter Map widget. This can be accomplished by using a combination of the widget's before query event  to add additional dimension data for the hover tooltips and the  before datapoint tooltip event to incorporate these dimensions into the tooltip. This method of modifying the query using the "beforequery" event can also be applied to all other widget types   // Add extra parameters to be used in tooltips by modifying query widget.on('beforequery', function (se, ev) { // Initial number of widget metadata panels excluding filter panel widget.initialPanelSizeExcludingFilterPanel = ev.query.metadata.filter((panel) => { return panel.panel !== "scope" }).length; // Extra dimensions to show in tooltip, should return a single result, include as many as needed, just add to array // Jaql Objects can be copied from other widgets from the prism.activeWidget.metadata.panels via the browser console // Modify JAQL as needed, title of JAQL is used in tooltip and can be modified to any string widget.extraDimensionJAQL = [ { "jaql": { "table": "Category", "column": "Category ID", "dim": "[Category.Category ID]", "datatype": "numeric", "merged": true, "agg": "count", "title": "Unique Category ID" } }, { "jaql": { "table": "Country", "column": "Country ID", "dim": "[Country.Country ID]", "datatype": "numeric", "merged": true, "agg": "count", "title": "Unique Country ID" } }, ] // Add to default widget query the extra dimensions to be used in tooltips ev.query.metadata = ev.query.metadata.concat(widget.extraDimensionJAQL) }); // Add extra dimensions added with beforequery object to ScatterMap tooltip widget.on("beforedatapointtooltip", (event, params) => { // Convert query results to include only the additional dimensions, and formatted for tooltip template var onlyAdditionalDimensions = widget.queryResult.$$rows.map((withoutDefaultDimensionOnlyAdditional) => { // Remove the default dimensions, first part of row result array var withoutDefaultDimensionOnlyAdditional = withoutDefaultDimensionOnlyAdditional.slice(widget.initialPanelSizeExcludingFilterPanel) // Format for tooltip template, include title from JAQL var extraDimensionObj = withoutDefaultDimensionOnlyAdditional.map((extraDimensionValue, index) => { // Use extraDimensionJAQL for label in tooltip return { "text": extraDimensionValue.text, "title": widget.extraDimensionJAQL[index].jaql.title } }) return extraDimensionObj }); // Object to store extra dimensions params.context.marker.extraDimension = {}; // Use matching queryIndex for tooltip of additional dimensions params.context.marker.extraDimension.arr = onlyAdditionalDimensions[params.context.marker.queryIndex]; // Template for tooltip, modify as needed params.template = ` <div class='geo-text'>{{ model.marker.name }}</div> <div class='measure-holder' data-ng-if='model.measuresMetadata.sizeTitle'> <div class='measure-title slf-text-secondary'>{{ model.measuresMetadata.sizeTitle }}:</div> <div class='measure-value'>{{ model.marker.sizeObj.text }}</div> </div> <div class='measure-holder' data-ng-if='model.measuresMetadata.colorTitle'> <div class='measure-title slf-text-secondary'>{{ model.measuresMetadata.colorTitle }}:</div> <div class='measure-value'>{{ model.marker.colorObj.text }}</div> </div> <div class='measure-holder details-measure-holder' data-ng-if='model.measuresMetadata.detailsTitle'> <div class='measure-title slf-text-secondary'>{{ model.measuresMetadata.detailsTitle }}:</div> <div class='measure-value' data-ng-if="!model.marker.detailsObj.arr">{{ model.marker.detailsObj.text }}</div> <div class="details-wait" data-ng-if="model.marker.detailsObj.pendingDetails"></div> <div data-ng-if="model.marker.detailsObj.arr"> <div class="details-value" data-ng-repeat="a in model.marker.detailsObj.arr">{{a.text}}</div> <div class="details-counter" data-ng-if="model.marker.detailsObj.hasMore"> <div class="details-counter">...</div> <div class="details-counter"> {{'smap.ttip.firstres'|translate:(args={count:model.marker.detailsObj.arr.length})}}</div> </div> </div> </div> <div data-ng-if="model.marker.extraDimension.arr"> <div data-ng-if='model.measuresMetadata.colorTitle'> <div class='measure-holder' data-ng-repeat="a in model.marker.extraDimension.arr"> <div class='measure-title slf-text-secondary'>{{a.title}}:</div> <div class="measure-value extra-dimension-value">{{a.text}}</div> </div> </div> </div>`; });    The JAQL can be changed to any valid JAQL object , and the tooltip template can also be further modified.

      Jeremy Friedel
      Jeremy FriedelPosted 2 years ago • Last reply 6 months ago
      3
               
    • Blog banner
      • News & UpdatesChevronRightIcon

      Outer joins (preview) - Release notes

                                                       

        Introduction An outer join (left, right, full) combines data from two tables, including all matching rows and any unmatched rows from one or both tables, filling in NULL for missing data. Analytical platforms use outer joins to achieve: Broader analytical capabilities : Ensure all relevant data is visible, even if there is no exact match in another table (e.g., view all products, including products with no sales). Gaps identification : Easily spot data integrity issues and missing information or relationships, which is crucial for analysis and reporting. Outer joins in Sisense Outer joins are available beginning with 2025.4 as a preview feature (turned off by default). It is planned to be released as beta in 2026.1.1. Sisense has chosen to expose it through its data modeling attributes, placing stronger emphasis on data governance controls compared to the approaches taken by other market alternatives. Important - Outer joins are not yet ready for production, and thus are not officially supported yet. We recommend using it for testing purposes, on a dev environment only. If you’d like to test it on your own models, you’ll need to first enable the following flag: Admin → Server & Hardware → System Management → Configuration → 5 clicks on the logo → Base Configuration → Query → query.outerJoins.enabled = true Once done, you can access it through the data tab, inside any data model, when editing any table relationship. The Join type drop-down (see the screenshot below) is where you can control it. A build/publish action must be performed after changes to see them reflected in the dashboard. The default value selected is “Default”, which, for now, stands for “Inner join” as was always used in Sisense before. In the future, it might be able to inherit other flexible join behaviors from an upper-level setting/product, so by keeping that option selected, you are allowing it to stay flexible. To enforce an inner join at any time in the future, select “Inner join” explicitly. Example data - To test the outer join, use a data model with data integrity issues, such as Sample Ecommerce, which contains countries in the Dim table (Country) that do not exist in the Fact table (Ecommerce). For example, if you perform a full join between the 2 tables, build the model, and expose it in a dashboard widget, country.country ID 199 Tahiti will appear, side by side with N/A or NULL values in the ecommerce columns. Without an outer join, Tahiti would not appear at all, because there is no matching data in the ecommerce table. Known issues and limitations Planned to be addressed in 2026.1.1: Analytical engine as a prerequisite - The outer join feature is designed to work exclusively with the Analytical Engine (AE). During the preview phase of that feature, if AE is not used in a query, and the query performs a fallback (due to a “compatibility mode” setting), only inner joins will be performed, even if the table relationship indicates otherwise. Starting from 2026.1 and onwards, using and editing a join type for a relationship will be disabled if the analytical engine setting of the model is set for “compatibility mode”, rather than for “Analytical Engine”. Filter propagation issue - Filters are usually translated into WHERE statements, and are applied immediately on the source base tables, before any table join is performed. This is safe and even optimal when using inner joins. When an outer join is used, this behavior may be unsafe, as the result may still include data from tables that are not part of the filtered table. For example: Starting from 2026.1 and onwards, those WHERE statements will be propagated above the joined tables if the filters belong to the non-preserved table(s). Data security risks - Some data security features behave like filters, and although they are not exposed in the “Analyze SQL Query” output, they are implemented on top of it and may suffer from the same filter propagation symptom mentioned above. In addition, not all data security use cases were covered thoroughly before the preview version was released, and while it will be a focus of the next release, please verify it based on your own data security rules, and share with us any concerns or use cases that should be double-verified. Perspectives inheritance issue - Perspectives usually inherit the relationship attributes set in the root level of the data model. Until the next version is out, it is not yet implemented for the join type attribute, and thus needs to be defined individually per perspective. Relationship’s pane fixed visualization order - When defining a relationship between Table X and Table Y, the current interface chooses which table will be presented on the left side of the pane, and which on the right. This fixed order means that you may need to adjust the join type to achieve the desired semantic join. For example, if you want to achieve the semantic result of Table Y LEFT JOIN Table X, but the relationship visualization order is (Table X, Table Y), you should flip it and select the “RIGHT JOIN” type instead. We recognize that having to manually flip the join type can be counterintuitive, but please note that there is no limitation on the desired result, which can still be achieved in any visualized order. Planned to be addressed in future versions: Filtering NULL values in widgets - There is no current option to filter out NULL values that are created as a result of an outer-joined data set, as Sisense does not yet offer result set filters. Circular reference ambiguity - When there are multiple ways to reach from table X to table Y, the system will choose the shortest path that takes into account any active filter and required data points. That means that sometimes, mainly based on filter usage, the path of joined tables performed from table X to table Y may change. And while one path may define an outer join to be used, the other path may not define it. That is not a new behavior, and it may not be an issue if the data modeler considered it, so just make sure to take it into account. Join type flipping in query time - There could be a situation where a data model relationship is defined as Table X LEFT join Table Y, but the widget query performs Tables Y RIGHT join Table X. The result will still be the same, but for query planning purposes, Sisense might switch the join type used in the SQL to be consistent with previous query plans and ensure semantic equivalence. Feedback that we are looking to get In order to improve and deliver a much more mature version of the feature in 2026.1.1 and after, we will be highly appreciative if feedback from you, our dear users, is shared with us. Even partial feedback would be appreciated! We’ve made a list of questions to brainstorm around it, but any open feedback is welcome, and we’ll be happy to receive it as well. To share it, feel free to pass your feedback to your CSM, and/or directly to our product manager, who’s leading this initiative: Morli Ben David at morli.bendavid@sisense.com . Clarity & Naming: Is the join type interface clear and easy to understand at a glance? If you were training a new user, what aspect of the UI would you anticipate causing the most confusion? Default Behavior: Does the default join type (currently assumed to be Inner) meet your expectations, or should the platform suggest a different default based on the data relationship, or based on any other approach? Data Integrity Checks: Did any of the resulting dashboards or widgets built on the new outer-joined relationship display unexpected values, duplicates, or missing data that you did not see with the previous (inner join only) model? Query Performance: After building the model with Outer Joins, were the resulting dashboard queries faster, slower, or comparable to what you would normally expect for a similar level of data complexity? Stability: Were there any unexpected crashes, freezes, or data rendering issues when modeling with or querying data sets built using Full, Left, or Right Joins? Maturity: Given the known issues mentioned above are going to be resolved, which other missing capabilities are must-haves? Is it mature enough to go to production already? User Training/Documentation: What is the one piece of information or training material that would best help you explain this new feature's value to your data team or end-users? Missing Capabilities (Gaps) : Now that you have this control, what is the next most critical data modeling control or feature you feel Sisense is missing? It can be either in the data page or in other areas impacted, such as the dashboard/etc.  

      Morli Ben David
      Morli Ben DavidPosted 7 months ago
      0
               
    • Blog banner
      • News & UpdatesChevronRightIcon

      Use case gallery: Your ticket to smarter insights

                                       

      Show off your work, help others, and earn a cash reward At Sisense, we know our users are doing incredible things with the platform, and we think your work deserves to be seen. That’s why we’re excited to announce the Use Case Use Cash Event, a limited-time opportunity to earn cash by sharing your best dashboards, visualizations, and implementations in the Sisense Community’s Use Case Gallery. What is the Use Case Gallery? The Use Case Gallery is a curated space showcasing real solutions built with out-of-the-box Sisense. Each post in the gallery includes a short visual demo, a description of the solution, and step-by-step instructions to help others recreate it. It’s a space for users to learn from each other, and now, it’s also a place where you can earn money for sharing your work. How to Participate in the Use Case Gallery Event 🗓️ Submission window: The month of August 2025 💸 Reward: 1 lucky submission will win $250 from a raffle! What we’re looking for: A 2–4 second animated GIF or video showing your solution in action A short description of what your solution does and why it’s useful Instructions that help others replicate your work (screenshots, code, links) Need inspiration? Check out this great example . Have a creation you want to share?  How to create a Use Case Gallery post: Here is an example of a properly formatted submission: https://community.sisense.com/blog/use-case-gallery/a-guide-to-creating-bullet-charts/28014 Thumbnail: Create a short (2-4 seconds) video or animated .gif file that shows what your creation does. Description: Write a short 1-2 paragraph description of what it does Instructions: Provide detailed instructions that people can follow to replicate your design, including screenshots and specific code or a link to a public GitHub repo. Reach out to DRay or jpacheco . We will work with you to create the content and get it posted! Why Participate? Recognition – Your work will be featured and celebrated Reward – Every submission is a ticket in the raffle for $250! Impact – Help other Sisense users learn and build faster Whether you're a power user or a first-time poster, this is your moment to shine. We can’t wait to see what you’ve built. ➡️ Submit your use case now   Sweepstakes terms and conditions:  No purchase necessary. Open to Sisense users who submit an original use case to the Sisense Community Use Case Gallery between August 1–31, 2025. Limit one entry per approved submission. One (1) winner will be selected at random to receive a $250 USD prize. The winner will be notified by email and must respond within five (5) days to claim the prize; otherwise, an alternate winner may be selected. The prize will be delivered within 30 days of the sweepstakes’ close. Only name and email will be collected. For full official rules, visit:  https://www.sisense.com/sisense-sweepstakess-official-rules/

      Community_Admin
      Community_AdminPosted 10 months ago
      0
               
    • Blog banner
      • News & UpdatesChevronRightIcon

      Have you heard about Sisense Intelligence?

                                       

      AI that builds with you: Meet Sisense Intelligence If you’re an app builder or product manager embedding analytics into your products, you know today’s users expect more: intuitive insights, smart visualizations, and fast answers– all without leaving the product experience. That’s where Sisense Intelligence comes in. Sisense Intelligence is our new suite of AI-powered capabilities designed to accelerate every stage of the analytics journey– from development to insight delivery– all within the Sisense platform. Whether you're a product leader, developer, or data expert, these features are built to help you create seamless, intelligent analytics experiences at scale. What’s inside Sisense Intelligence? A unified framework of powerful tools, including: Assistant : A conversational interface for building dashboards and exploring data with natural language. Narrative : Auto-generated summaries that highlight key takeaways from charts and widgets. Forecast & Trend : Tools to spot patterns and predict what’s ahead. Explanation : Pinpoint drivers of change across key metrics. These features are connected by a common goal: to help builders move faster, deliver smarter, and create product experiences users love. Want to go deeper? Join us for a live webinar on June 5 at 11:00 AM ET: Register now → Build with AI: What’s new (and what’s next) in the Sisense platform See how AI-powered analytics can accelerate your product strategy– schedule a demo . If you're an existing Sisense customer, reach out to your Customer Success Manager. Can't wait? Watch this 90-second video highlighting our newest AI capabilities: Visit trust.sisense.com for security details.

      Community_Admin
      Community_AdminPosted 1 year ago
      0
               
    • Blog banner
      • News & UpdatesChevronRightIcon

      New academy content for administrators!

                                                                                       

      New academy content for administrators! We are pleased to announce the launch of 45 ALL-NEW COURSES that are AVAILABLE NOW for Administrators! These courses will help Admins of all skill levels by expanding their knowledge, providing hands-on opportunities, and covering all of our LATEST features and best practices (approximately 6 to 7 hours of content and hands-on practice). Our new courses are based on brand-new data and assets, divided into microlearning units. They are interactive and accessible! ( yes, yes, we finally have captions! ) To get access to the new Administrators learning path, CLICK HERE and then click on the blue Get Started button to register. If you are new to the Sisense Academy, I encourage you to make an account and sign up for courses based on your role. This is just the beginning of new content releases in Sisense Academy as we are working hard behind the scenes on the next set, and we look forward to sharing more with you soon! This is a continuation of the Academy Content Refresh. During January 2025, we launched 25 ALL-NEW COURSES that are AVAILABLE NOW for Data Designers, and during March 2025, we launched 15 ALL-NEW COURSES that are AVAILABLE NOW for Dashboards Designers. I hope to see you all in the Academy!

      iyyar_sg
      iyyar_sgPosted 1 year ago
      0
               
    • Blog banner
      • Widget & Dashboard ScriptsChevronRightIcon

      Redirect users to different dashboards based on dashboard filters

                                                                                                                               

      Redirect users to different dashboards based on dashboard filters This article  discusses and shares the full code of a  dashboard script  that redirects users to a different dashboard ID based on the user's  filter selections or initial loaded filter state. In the particular example shared in this article, the script checks whether the selected date filter (either from a members filter or a filter date range) includes an earlier date than the earliest date in the current dashboard's data source. If this is the case, the script redirects the user to a specified alternate dashboard, preserving any additional URL segments and query parameters in the URL. Any other type of filter state can also be used to determine when the script should redirect, including non-date filters using similar scripts. Example Use Case Limited vs. Full Dataset : One dashboard data source might serve only recent data, while another dashboard data source stores all historical data. If a user selects a date that pre-dates one dataset, this script will seamlessly redirect them to the alternate dashboard that includes older data. Improved User Experience : Instead of displaying empty or invalid data for older dates, users are smoothly guided to the correct “all data” view dashboard. Full Script     /** * Main function that checks a single-level date filter of day-level granularity * (either 'members' or 'from/to') for a single selected by variable date dimension. The dashboard script determines * whether the selected date is earlier than the earliest date in the current dashboard’s * datasource. If so, the function redirects to another dashboard while preserving the rest * of the URL parameters and path segments. * * Use Case: * - One dashboard has a datasource with only recent data (no older dates). * - Another dashboard has a datasource with all historical data. * - If a user selects a date earlier than the “recent” dataset supports, the script redirects * them to the “all data” dashboard, retaining any query parameters or path info in the URL. */ function checkAndRedirectIfNeeded() { /** * Toggle console logging for clarity or debugging. */ const enableLogging = true; /** * The dimension string for the date filter in the current dashboard's datasource. * Example: "[Commerce.Date (Calendar)]". * This code checks a single-level date filter of day-level granularity for this dimension. Change as needed */ const dateDim = "[Commerce.Date (Calendar)]"; /** * The alternate dashboard ID to which a user is redirected if they select a date * older than what the current datasource includes. This dashboard ID replaces the current * dashboard ID in the URL, preserving any extra path or query parameters. */ const alternateDashboardId = "67bebb6863199f002a7b0906"; /** * Logs to the console only if enableLogging is true. * @param {...any} msgs - Items to log. */ function log(...msgs) { if (enableLogging) { console.log(...msgs); } } /** * Searches the dashboard's filters for a single-level date filter matching dimStr. * Returns the filter object if found, otherwise null. * @param {string} dimStr - The date dimension to look for. */ function findDateFilter(dimStr) { if (!dashboard.filters || !dashboard.filters.$$items) { log("No filters accessible on this dashboard."); return null; } for (const filter of dashboard.filters.$$items) { if (filter.jaql && filter.jaql.dim === dimStr) { log("Found date filter for dimension:", dimStr); return filter; } } return null; } /** * Builds a minimal JAQL query to fetch the earliest date (sorted ascending) from the specified dimension. * Uses the current dashboard's datasource, and returns only 1 row (count: 1). * * This ensures we get the earliest available date in the dataset for the dimension in question, * typically returned as an ISO-like string in the response data. * * @param {string} dimStr - The dimension string representing the date field. */ function buildEarliestDateQuery(dimStr) { return { datasource: dashboard.datasource, metadata: [ { jaql: { dim: dimStr, datatype: "datetime", level: "days", sort: "asc" } } ], count: 1 }; } /** * Runs the JAQL query using Sisense's internal HTTP service if available, otherwise jQuery.ajax. * The request is made synchronous (async: false). If the internal service doesn't exist, fallback is standard AJAX. * * @param {Object} jaql - The JAQL query object. * @returns {Promise} - Resolves with the server response or rejects on error. */ function runHTTP(jaql) { const $internalHttp = prism.$injector.has("base.factories.internalHttp") ? prism.$injector.get("base.factories.internalHttp") : null; const ajaxConfig = { url: `/api/datasources/${encodeURIComponent(jaql.datasource.title)}/jaql`, method: "POST", data: JSON.stringify(jaql), contentType: "application/json", dataType: "json", async: false, xhrFields: { withCredentials: true } }; return $internalHttp ? $internalHttp(ajaxConfig, false) : $.ajax(ajaxConfig); } /** * Replaces only the dashboard ID in the current URL with alternateDashboardId, * preserving the rest of the URL (query parameters, path segments, etc.). * If the current dashboard ID is not found, the redirect is canceled. */ function redirectToAlternateDashboard() { const currentDashId = dashboard.oid; if (!currentDashId) { log("Cannot determine current dashboard ID. Redirect canceled."); return; } const currentUrl = window.location.href; if (!currentUrl.includes(currentDashId)) { log("Current URL does not contain the expected dashboard ID. Redirect canceled."); return; } const newUrl = currentUrl.replace(currentDashId, alternateDashboardId); log("Redirecting to alternate dashboard:", newUrl); // Perform the actual redirect window.location.href = newUrl; } /** * Main logic flow: * 1) Fetch the earliest date in the dataset for dateDim by building and running a JAQL query. * 2) Locate the single-level date filter for dateDim in the dashboard filters. * 3) Check the user's selected date: * - If 'members' type, use the first member. * - If 'from/to' type, use 'from'. * 4) Compare the selected date (JS Date) to the earliest date (JS Date). * 5) If selected date is earlier, redirect to the alternate dashboard; otherwise, do nothing. */ const jaqlQuery = buildEarliestDateQuery(dateDim); runHTTP(jaqlQuery) .then(response => { if (!response?.data?.values?.length) { log("No data returned for earliest date query. No redirect needed."); return; } // The earliest date is stored in .data, typically an ISO-like date string (e.g. "2023-01-05T00:00:00"). const earliestDateString = response.data.values[0][0].data; log("Earliest date in the dataset:", earliestDateString); // Convert the earliest date string to a JS Date object for comparison const earliestDate = new Date(earliestDateString); if (isNaN(earliestDate.valueOf())) { log("Earliest date is invalid or unrecognized:", earliestDateString); return; } // Find the single-level date filter on the dashboard const dateFilter = findDateFilter(dateDim); if (!dateFilter || !dateFilter.jaql.filter) { log("Date filter not found or missing filter object. Skipping redirect check."); return; } let selectedDateStr = null; // If it's a 'members' type filter, use the first member, this is the earliest if ( Array.isArray(dateFilter.jaql.filter.members) && dateFilter.jaql.filter.members.length > 0 ) { selectedDateStr = dateFilter.jaql.filter.members[0]; log("Filter type: 'members'. Selected date string:", selectedDateStr); // If it has 'from' (and possibly 'to'), use 'from' } else if (dateFilter.jaql.filter.from) { selectedDateStr = dateFilter.jaql.filter.from; log("Filter type: 'from/to'. Selected 'from' date string:", selectedDateStr); } else { log("Filter is not recognized or has no selected date. Skipping redirect."); return; } // Convert the filter's date string to a JS Date const selectedDate = new Date(selectedDateStr); if (isNaN(selectedDate.valueOf())) { log("Selected date is invalid or unrecognized:", selectedDateStr); return; } // Compare for earliest date if (selectedDate < earliestDate) { log("Selected date is earlier than the earliest dataset date. Redirecting..."); redirectToAlternateDashboard(); } else { log("Selected date is within or after earliest dataset date. No redirect needed."); } }) .catch(error => { log("Error retrieving earliest date or comparing filter date:", error); }); } /** * Calls the main logic function after the dashboard is initialized. */ dashboard.on("initialized", function () { checkAndRedirectIfNeeded(); }); /** * Also calls the main logic function whenever filters change. */ dashboard.on("filterschanged", function () { checkAndRedirectIfNeeded(); });     How It Works Earliest Date Lookup The script first builds a small custom secondary JAQL query (sorted in ascending order, limited to one result) to determine the earliest date in the data source. It executes this query using Sisense’s internal service function, using the native Sisense cookies. This type of scripting functionality has been discussed in more detail in previous knowledge base articles, such as this article . Filter Inspection After fetching the earliest date, the script searches for a single-level date filter matching the configured date dimension. If the filter uses a   members   array, the script reads the first member. If the filter uses a   from and to range   structure, it uses the   from   date, as this is always the earliest. Comparison The script converts both the earliest date and the selected date into JavaScript   Date   objects. If the selected date is earlier than the earliest date, the script triggers a redirect. Redirect Logic Only the dashboard ID portion of the current URL is replaced with the specified alternateDashboardId. Path segments, query parameters, and any other pre-existing parts of the URL remain the same. Use Cases Limited vs. Full Dataset When one Sisense dashboard is restricted to recent data (for example, the last 30 days) and another includes all historical data. This script diverts users to the appropriate dashboard seamlessly. Consistent User Experience Avoids confusion when a date filter extends beyond a limited dataset’s timeframe. Instead of showing empty or invalid data, the user is sent to a more comprehensive dashboard. Multi-Dashboard Navigation Ensures the user can continue with the same URL parameters and path segments, simply swapping the restricted dashboard for the alternate “all data” one. Conclusion By employing this or similar scripts based on this form of customization (this example is more advanced in using a custom secondary JAQL request to fetch the first data value, but this is not necessary in many scenarios), it is possible to streamline date-based filter navigation or other filter-based navigation between dashboards or data sources, guaranteeing for example that if a date selection falls outside the scope of a limited dataset, the user automatically sees data in a broader data source dashboard. This functionality improves user experience and allows for wide customizability.  Example of a date selection including dates before the first date in dataset [A LT Text: A digital interface showing a table titled "Days in Date." Below the title, there are three dates listed: 11/26/20, 11/25/20, and 11/27/20. The date 11/26/20 is highlighted in yellow, along with a toggle switch on the right side of the image.] Example Output with logging variable enabled [ ALT Text: A screenshot of a computer terminal showing error messages related to date filters for a calendar dimension in a dataset. The messages indicate that a selected date of November 26, 2020, is valid while a date of November 25, 2020, is earlier than the earliest date in the dataset (November 11, 2020). The terminal displays a redirecting notice to an alternate dashboard.]  

      Jeremy Friedel
      Jeremy FriedelPosted 1 year ago
      0
               
    • Blog banner
      • News & UpdatesChevronRightIcon

      L2025.1 release notes, with dashboard AI assistant (Beta)!

                               

      This feature enables you to explore your data and generate queries through natural language questions powered by AI. The AI Assistant can suggest sample insights, provide narrative explanations in context, and produce data visualizations. See the full release notes here,  and learn more about the Sisense Dashboard AI Assistant here! We are excited for you to try the new Dashboard AI Assistant and would love to hear how you use it in the comments below!

      DRay
      DRayPosted 1 year ago
      0