cancel
Showing results for 
Search instead for 
Did you mean: 
JeremyFriedel
Sisense Team Member
Sisense Team Member

This is a Sisense.js Demo built with React, which includes functionality such as changing filters and dashboards with native web components. This can be used as a reference or starting point for building and customizing Sisense.js applications with custom styling and functionality. 

Screen Shot.png

React Sisense.js Demo

Getting Started

  • Make sure CORS is enabled and the location/port your hosting this applicaiton on is whitelisted on Sisense instance, documentation on changing CORS settings is here. 
  • Open the terminal (on Mac/Linux search for terminal, on Windows search for Powershell, Command Prompt, or Windows Subsystem for Linux if installed ) and set path to folder containing project (using cd command).
  • Open the config file in the folder src folder and set Sisense Server URL to the IP or domain name of your Sisense server and set the dashboard id's and optionally filters keys.
  • Run the command.

 

 

npm install && npm start

 

 

If npm and node is not installed, install them using the package manager or using nvm (Node version manager). Nvm can be installed by running this command in a terminal

 

 

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash

 

 

  • Once install script is finished, follow any instructions listed in terminal, restart or open a new terminal and run these command to install node and npm:

 

 

nvm install node
nvm install-latest-npm

 

 

To start hosting the site, run this command:

 

 

npm install && npm start

 

 

The default location is IP of hosting server on port 3000.

If the IP address of the server hosting this program is 10.50.74.149 for example, then enter 10.50.74.149:3000 in the address bar of a browser to view application. if hosted locally enter your ip on port 3000 or enter

localhost:3000

To use a different port, set the port variable in terminal using this command:

 

 

export PORT=NEW_PORT_NUMBER

 

 

and then run npm start. To run in the background use PM2 or system.

Requirements

Npm (and Node) - Installs all requirements

Access to a Sisense instance

 

Description

This is a demonstration of some of the capabilities of Sisense.js and Sisense. It is a React single-page application using Typescript that can switch between multiple dashboards, loading all widgets in those dashboards to individual elements (created on dashboard switch). Multiple dashboard filters can be changed (and multiple members in a filter) using a native React dropdown bar to filter the data with the new filtered data visualized with the widgets in the dashboard. Changes made to filters using widgets in Sisense.js using right click are reflected in dropdown filter.

 

Connecting to Sisense

 

 

// Loads Sisense.js from Server, saves Sisense App copy in window as object to indicate already loaded to prevent loading twice
const loadSisense = () => {
    // If Sisense is already loaded stop
    if (window.sisenseAppObject) {
        return;
    }
    // Loads sisense app object into app variable, edits being saved to dashboard set to false
    window.Sisense.connect(config.SisenseUrl, config.saveEdits)
        // Sisense app object
        .then((app: SisenseAppObject) => {
            // loads Sisense app object into window object, so connect is only run once, alternative is export and import
            window.sisenseAppObject = app;
            // Calls loadDashboard after Sisense is connected, uses initial widget and dashboard variables
            loadDashboard(config.DashboardConfig[0].DashboardID, config.DashboardConfig[0].DimArray);
        })
        // error catching and log in console
        .catch((e: Error) => {
            console.error(e);
        });
}
Loading Dashboard and Creating Container Element for each Widget
// Function to load dashboard taking dashboard id as parameter to load dashboard. Every widget in dashboard is rendered into an element created in loop, looks for parent element with ID 'widgets'. On calling again existing widgets are replaced by new ones.
const loadDashboard = (dashboardID: string = '') => {
    // if empty dashboard id return
    if (dashboardID === '') {
        return;
    }
    // load dashboard into being active dashboard
    window.sisenseAppObject.dashboards.load(dashboardID)
        // after load dashboard is in dash variable
        .then((dash: DashObject) => {
            window.currentDashObject = dash
            // array of loaded widgets
            // let widgetArray: Array<String> = prism.activeDashboard.widgets.toArray().map(
            let widgetArray: Array<String> = dash.$$widgets.$$widgets.map(
                function (widget: Widget) {
                    // widget id for loading
                    return widget.id
                }
            );
            // set state with loaded dashboard if prism loaded
            if (widgetArray.length > 0) {
                this.setState((state, props) => {
                    return {
                        dashboardID: dash.id,
                    };
                });
            }
            // get widgets element
            let widgetsElement: HTMLElement | null = document.getElementById(`widgets`);
            // type checking
            if (widgetsElement === null) {
                return;
            }
            // erase previous widgets
            widgetsElement.innerHTML = '';
            // loop through array of widget arrays, loads them into containers by id with first in widget1, second in widget2 and so on
            widgetArray.forEach((widget, index) => {
                // check if they exist, type checking 
                if (widgetsElement === null) {
                    return;
                }
                // element to load widget into later
                let widgetElement: HTMLElement | null = document.createElement("div");
                // Class included index for widget rendering later
                widgetElement.classList.add(`widget${index + 1}`, 'widget')
                // add empty div to widgets parent div
                widgetsElement.appendChild(widgetElement)
                // get widget and filter elements by ID
                let filterElement: HTMLElement | null = document.getElementById("filters");
                // check if they exist, type checking 
                if (widgetElement === null || filterElement === null) {
                    return;
                }
                // Clear widget and filter elements from previous render
                filterElement.innerHTML = '';
                // put widget in container element
                dash.widgets.get(widget).container = widgetElement;
                // Renders filter in HTML element with id of filters
                dash.renderFilters(filterElement);
                // reloads and refresh dashboard
            });
            dash.refresh();
        })
        // error catching and log in console
        .catch((e: Error) => {
            console.error(e);
        });
}
Changing a Dashboard Filter
// Change a dashboard filter, takes dim to filter by, and new values in filter.
const changeDashboardFilter = (dashboard: dashboard, dim: string, newFilterArray: Array<String>) => {
    // Find matching filter and to make changes to
    let filterToChange = dashboard.filters.$$filters.find(item => item.jaql.dim === `[${dim}]`);
    // If filter is undefined create a new filter
    if (filterToChange === undefined) {
        // Create the filter options
        let filterOptions = {
            // Save to dashboard
            save: false,
            // Refresh on change
            refresh: true,
            // If filter already used, make changes to that filter instead of creating new ones
            unionIfSameDimensionAndSameType: true
        };
        //  Create the jaql for the filter
        let jaql = {
            'datatype': 'text',
            'dim': dim,
            'filter': {
                // Multiple items can be selected
                'multiSelection': true,
                // New filter items
                'members': newFilterArray,
                'explicit': true
            },
        };

        // Create the filter jaql object
        let applyJaql = {
            jaql: jaql
        };
        // Set the new filter using update function
        dashboard.$$model.filters.update(applyJaql, filterOptions);
    }

    if (filterToChange && filterToChange.$$model.jaql.filter) {
        let members = filterToChange.$$model.jaql.filter.members;
        // Check if members exist
        if (members !== undefined) {
            // Set members to new selected filter
            filterToChange.$$model.jaql.filter.members = newFilterArray;

            // Save the dashboard
        // dashboard.$$model.$dashboard.updateDashboard(dashboard.currentDashObject.$$model, "filters");
        dashboard.filters.update(filterToChange, { refresh: true, save: false });

            // Refresh the dashboard
            // dashboard.refresh();
        }
    }
}

 

 

Clearing a filter

Clearing a filter is done by simply setting the members to an empty array, which disables the filter.

 

 

changeFilter([]);

 

 

 

Monitor For Filter Change to Ensure Dropdown Accurately Displays Filter Change

 

 

// Watch for element change to indicate filter changes and make change to dropdown if filters don't match displayed filter in dropdown
const watchForFilterChange = new MutationObserver((mutations) => {
    // If element changes
    mutations.forEach(mu => {
        // If not class or attribute change return
        if (mu.type !== "attributes" && mu.attributeName !== "class") return;
        // Find matching Filter to dropdown
        let filterToChange = (dashboard.filters.$$filters as Array<DashObject>).filter(element => element.jaql.dim === `[${dim}]`);
        // If filter values displayed and filter values active don't match each other, set displayed filter to match filter, filter value stays as is
        filterToChange.forEach((filter) => {
            if (filter.jaql.filter.members.sort().join(',') !== selectedFilterValues.sort().join(',')) {
                // Change state of filter values to new filter, sets displayed filter in dropdown in correct one.
                setSelectedFilterValues(filter.jaql.filter.members);
            }
        });
    });
});
// Array of element with 'widget-body' class (created by Sisense.js on widgets) for change to check for filter change
const widget_body_array = document.querySelectorAll(".widget-body")
// Watch 'widget-body' class for filter changed by other means, such as right click select on value
widget_body_array.forEach(el => watchForFilterChange.observe(el, { attributes: true }));

 

 

 

Component Details

  • Filter - One for each filter, gets dropdown values and other props for dropdown filter component, takes filter dim as prop, parent of Dropdown Filter component
  • Dropdown Filter - Renders individual filter dropdown, renders dropdown of one filter, handles filter changes
  • Clickable Button - Button that calls a function on click, props include text, color and function called
  • Input Number - Sets widgets per row, has up and down button as well as keyboard input, controlled input only accepts numbers, has default value in config file
  • Load Sisense - Loads Sisense, gets URL of server from config file, has load dashboard function, creates elements to load widgets into on dashboard load call
  • Sidebar - Collapsible Sidebar, on click loads dashboard, content of dashboard can be configured by config file
  • App - Parent component, has loading indicator before Sisense has loaded, contains all other components

 

Config Settings

  • DashboardConfig - Array of objects describing dashboards selectable in sidebar
    • DashboardLabel - text to show in expanded sidebar
    • DashboardID - Dashboard ID, get from url of dashboard in native Sisense
    • Icon - Icon to show in sidebar for dashboard
    • DimArray - Values to filter by
  • SisenseUrl - URL of Sisense server
  • initialDashboardCube - Title of initial dashboard to show
  • defaultSidebarCollapsed - Dashboard initial state, collapsed or not
  • defaultSidebarCollapsedMobile - Dashboard initial state, collapsed or not, on mobile
  • collapseSideBarText - Text shown on element that collapses sidebar*
  • hideFilterNativeText - Text to hide native embedded filter
  • useV1 - Use v1 version of Sisense script
  • defaultWidth - initial state of selector for widgets per row
  • saveEdits - Write back to Sisense changes made to filters, and any other persistent changes
  • loadingIndicatorColor - Color of loading indicator
  • loadingIndicatorType - Type of loading indicator, options from ReactLoading
  • sidebarBackgroundColor - Background color of sidebar
  • widgetMargin - Margin of individual widget

 

Available Scripts

In the project directory, you can run:

 

 

npm start

 

Runs the app in the development mode.

Open http://localhost:3000 to view it in the browser.

The page will reload if you make edits.

You will also see any lint errors in the console.

 

 

npm test

 

 

Launches the test runner in the interactive watch mode.

See the section about running tests for more information.

 

 

npm run build

 

 

Builds the app for production to the build folder.

It correctly bundles React in production mode and optimizes the build for the best performance.

The build is minified and the filenames include the hashes.

Your app is ready to be deployed!

See the section about deployment for more information.

 

 

npm run eject

 

 

Note: this is a one-way operation. Once you eject, you can’t go back!

If you aren’t satisfied with the build tool and configuration choices, you can eject at any time. This command will remove the single-build dependency from your project.

Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except eject will still work, but they will point to the copied scripts so you can tweak them. At this point, you’re on your own.

You don’t have to ever use eject. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However, we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.

Download and extract the zip below:

Rate this article:
Version history
Last update:
‎08-14-2023 10:16 AM
Updated by: