Blog Post

Use Case Gallery
5 MIN READ

Comprehensive framework for menu item removal in Sisense

JeremyFriedel's avatar
JeremyFriedel
Sisense Employee
09-29-2025

Overview

Companies and organizations often wish to control which Sisense menu options are visible to different role types of users. Sisense administrators or software developers may require menu items such as Edit Script or Embed Code, while business users should potentially see a more streamlined interface with only the essential options visible in the menu. Customizing these menus can be an important part of both governance and usability, ensuring that users are not viewing nonrelevent menu items. Code modifying menu's in Sisense can be present in both widget and dashboard scripts, and plugins. Custom menu items, added by plugins and scripts, can also be removed for the relevant user types.

Challenge

Removing menu items in Sisense requires intercepting the beforemenu event and filtering items out before the menu is displayed. This can be straightforward for most options, but there are several complexities to account for:

  • Menu items can be identified by different properties (caption, command.title, description, or tooltip).
  • Simply reassigning menuArgs.settings.items to a new array does not always work, since Sisense components and other listeners may hold references to the original array.
  • In some cases, items may be injected into the menu after it is already open. The dashboard co-authoring Hide option is an example of this edge case, requiring a second filtering pass to remove consistently.

The requirement was to create a robust framework that could handle these variations, while also supporting conditional filtering based on user type.

Sisense exposes the property prism.user.baseRoleName, which identifies the base role of the logged-in user. This allows developers to apply conditional logic when customizing menus. For example, users with the role "super" are administrators who may need access to advanced actions such as Export or Embed Code, while users with the role "consumer" are viewers who should only see a simplified set of options. By checking this property, menu filtering can be adapted to match governance rules and usability needs for each audience.


For example, administrators (prism.user.baseRoleName = "super") may retain advanced options, while viewers (prism.user.baseRoleName = "consumer") see a simplified set of actions.

Solution

A comprehensive framework was developed with the following key techniques:

  • Configuration-driven filtering: Administrators can specify which object properties to inspect for matching strings, and which menu items to hide, making the script flexible and maintainable.
  • In-place mutation of the menu array: Instead of replacing the array, the script updates it in place with splice. This ensures all references across the Sisense application reflect the updated menu.
  • Two-phase filtering: The filter runs immediately during the beforemenu event to catch standard items, and again with setTimeout to handle menu items injected later, such as the Hide option, which is an unusual case.
  • Conditional visibility by user role: By checking prism.user.baseRoleName, the script can apply different filtering rules for administrators and consumers. This ensures the right balance of power and simplicity depending on the user type.

Full Implementation

 

/*
 * Hide specified Sisense menu items at both the dashboard and widget level.
 * All configuration is defined inside the beforemenu function for clarity
 */
prism.on("beforemenu", function (event, menuArgs) {
    // no menu items to process
    if (!menuArgs?.settings?.items) {
        return;
    }

    /**
     * Configuration
     *
     * fieldsToCheck: choose which object parameters to inspect on each menu item.
     *   Set any field to false to skip it.
     *
     * itemsToHide: case-insensitive list of labels to remove from the menu.
     *   Provide readable strings, such as "embed code" or "get url for embedding".
     */
    const configuration = {
        fieldsToCheck: {
            caption: true,
            commandTitle: true,
            description: true,
            tooltip: true
        },
        itemsToHide: [
            "delete",
            "duplicate",
            "export",
            "edit script",
            "embed code",
            "get url for embedding",
            "simply ask (nlq)",
            "exploration paths",
            "widget filters indication",
            "widget affects dashboard filters",
            "dashboard settings",
            "hide widget header",
            "hide dashboard for non-owners",
            "featured on mobile app",
            "show dashboard in the featured dashboards list in the mobile app.",
            "navver.dashboard_menu_options.restore_dashboard",
            "switch to administrator view",
            "share the assistant",
            "change data source",
            "hide widget header",
            "hide"
        ]
    };

    /**
     * Prepare a fast lookup set from the configured hide list.
     * Comparison is case-insensitive and ignores extra whitespace.
     */
    const hiddenLabelSet = new Set(
        (configuration.itemsToHide || [])
            .map(formatLabelForComparison)
            .filter(Boolean)
    );

    /**
     * Standardize a label for comparison:
     * - Trim whitespace
     * - Convert to lowercase
     * Returns undefined for non-strings or empty values after trimming.
     */
    function formatLabelForComparison(rawValue) {
        if (typeof rawValue !== "string") {
            return undefined;
        }
        const trimmedLowercase = rawValue.trim().toLowerCase();
        return trimmedLowercase.length ? trimmedLowercase : undefined;
    }

    /**
     * Gather all label values from the menu item according to the configuration.
     * Returns an array of standardized, non-empty strings.
     */
    function getLabelsForMenuItem(menuItem) {
        const labels = [];

        if (configuration.fieldsToCheck.caption) {
            const captionLabel = formatLabelForComparison(menuItem.caption);
            if (captionLabel) {
                labels.push(captionLabel);
            }
        }

        if (configuration.fieldsToCheck.commandTitle) {
            const commandTitleLabel = formatLabelForComparison(menuItem.command?.title);
            if (commandTitleLabel) {
                labels.push(commandTitleLabel);
            }
        }

        if (configuration.fieldsToCheck.description) {
            const descriptionLabel = formatLabelForComparison(menuItem.desc);
            if (descriptionLabel) {
                labels.push(descriptionLabel);
            }
        }

        if (configuration.fieldsToCheck.tooltip) {
            const tooltipLabel = formatLabelForComparison(menuItem.tooltip);
            if (tooltipLabel) {
                labels.push(tooltipLabel);
            }
        }

        return labels;
    }

    /**
     * Decide whether a menu item should be hidden.
     * If any menu item parameter matches an entry in the hidden label set, the item is removed.
     */
    function shouldHideMenuItem(menuItem) {
        const labels = getLabelsForMenuItem(menuItem);
        return labels.some(function (label) {
            return hiddenLabelSet.has(label);
        });
    }

    /**
     * Recursively filter menu structures:
     * - Remove items that match the hide list.
     * - If an item has children (sub-menu), filter those sub-menus as well.
     */
    function filterMenuItemsRecursively(menuItems) {
        return (menuItems || [])
            .filter(function (menuItem) {
                return !shouldHideMenuItem(menuItem);
            })
            .map(function (menuItem) {
                if (Array.isArray(menuItem.items)) {
                    menuItem.items = filterMenuItemsRecursively(menuItem.items);
                }
                return menuItem;
            });
    }

    // Apply the filtering to the base array of menu items
    (function runFilter() {
        const filteredItems = filterMenuItemsRecursively(menuArgs.settings.items);
        // replace the contents of the original array so any existing references see the filtered result
        menuArgs.settings.items.splice(0, menuArgs.settings.items.length, ...filteredItems);
    })();

    // Run the filter again after other beforemenu listeners finish
    setTimeout(function () {
        const filteredItems = filterMenuItemsRecursively(menuArgs.settings.items);
        menuArgs.settings.items.splice(0, menuArgs.settings.items.length, ...filteredItems);
    }, 0);
});

 

Outcome

With this framework, companies and organizations can reliably hide sensitive or unnecessary menu items while keeping the interface clear and consistent for end users. Because it mutates the menu array in place and applies filtering twice, it covers both standard and rare late injected menu items. By using prism.user.baseRoleName as a condition, administrator users can still view all menu items where needed, while viewer or designer level users  see only the options relevant to them. This makes the solution comprehensive, flexible, and aligned with the exact organization requirements for menu items.

 

 

 

Updated 09-29-2025
Version 2.0

1 Comment

  • JeremyFriedel's avatar
    JeremyFriedel
    Sisense Employee

    For a styling change related to the is Use Case, when removing a large amount of menu items the menu may become narrower, to set the menu width to a set width use the widget, dashboard script below, or include it within a plugin. This can also be used in scenarios where no menu items are removed, but the menu is preferred to be wider.

    Set Width of Sisense Menu Items Manually:

    //Set Width of Sisense Menu Items Manually:
    widget.on("initialized", function onInitialized(w, args) {
      // CSS to inject, increase Sisense menu width, change "225px" with desired width
      const css = `
      .menu-host .menu-item-host .menu-content,
      .menu-host .menu-item-host .menu-item,
      .menu-host .menu-item-host .menu-item.mi-separator,
      .menu-item-container {
        width: 225px;
        min-width: 225px;
        max-width: 225px;
      }
      `;
    
      const styleId = "sisense-custom-menu-width-style";
    
      // Prevent duplicate injection
      if (document.getElementById(styleId)) {
        return;
      }
    
      const styleEl = document.createElement("style");
      styleEl.id = styleId;
      styleEl.type = "text/css";
      styleEl.appendChild(document.createTextNode(css));
    
      document.head.appendChild(styleEl);
    });
    

     

Related Content

Related Content