Bikeshare technical workshop: Embedding Jaspersoft in web applications

Embedded Analytics with TIBCO Jaspersoft®


Embedding Workshop

This workshop will show how Jaspersoft is embedded and used in the bikeshare web application. The same approaches can be applied to your own application.

The bikeshare application has a React/Typescript based user interface. There is a Typescript helper class that can be used in other Typescript based web frameworks like Angular.

The steps to embed Jaspersoft in your app are:

  1. Install the Jaspersoft visualize.js JavaScript library and JasperReports Server theme into the application pages

  2. Define a helper class to:

    • manage the configuration of visualize.js to connect and login to a JasperReports Server instance, including any single sign on management

    • provide an API to visualize.js to allow easy integration of visualize.js into your development

  3. Use the helper to:

    • Login to JasperReports Server

    • Execute visualize.js API calls

    • Embed JasperReports Server screens in an iFrame

    • Call JasperReports Server REST services


Install Jaspersoft components to support embedding

The JasperReports Server visualize.js JavaScript library needs to be included in your web pages. See Best practices for embedding Jaspersoft in web applications: Loading visualize best practices for general advice. You can get the library from a JasperReports Server installation.

JasperReports Server version visualize.js location
before 7.2.0 http://<domain>/jasperserver-pro/optimized-scripts/client/visualize.js
7.2.0 http://<domain>/jasperserver-pro/optimized-scripts/visualize/visualize.js

or you can get the visualize.js file directly from a JasperReports Server deployment.

In bikeshare, we have included the visualize library into the React UI public directory and added a script tag to public/index.html to load it.

https://github.com/TIBCOSoftware/js-bikeshare-demo/blob/Sherman-for-the-1st-workshop/UI/public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <script type='text/javascript' src="./visualize.js"></script>
    <script type="text/javascript" src='./GeoAnalytics.js'></script>
    <div id="root"></div>
  </body>
</html>

Additionally, the Jaspersoft visualizations use a set of "theme" CSS, fonts etc. You can retrieve a theme from the JasperReports Server you connec to, or load the them into your application. For bikeshare, we have included the theme files in the application under public/css/jasper/default.


visualizeHelper: an API wrapper

https://github.com/TIBCOSoftware/js-bikeshare-demo/blob/Sherman-for-the-1st-workshop/UI/src/helpers/VisualizeHelper.tsx

This class wraps all the interactions with visualize, exposing a simple API.

One of the main tasks of the visualizeHelper is to authenticate against JasperReports Server. All interactions with the JasperReports Server are based on "who" is logged in. At the start, you can use simple user name/password authentication. but for production, you should authenticate using some form of single sign on. Authentication for JasperReports server is documented in the Authentication Cookbook (7.2.0) Many authentication frameworks can be used for single sign on, including SAML, OAuth, OpenId and custom authentication through JasperReports Server and Spring Security APIs. Authentication with visualize is documented here: Visualize.js login (7.2.0)

Your JasperReports Server URL can point to a full domain or a proxy. See Visualize.js connection configuration best practices

The login process creates a user session in the JasperReports Server which is held by a cookie in the browser.

The following chunk of VisualizeHelper sets the configuration of visualize to connect to the given JasperReports Server, using token based authentication, and use the "default" theme in public/css/jasper. Token based authentication is a simple, self contained authentication based on encryption.

  /**
   * Token-Based Authentication Login
   * @param userToken
   * @param jaspersoftServerUrl
   */
  login(userToken: string, jaspersoftServerUrl: string) {
    this.viz.config({
      server: jaspersoftServerUrl,
      scripts: 'optimized-scripts',
      theme: {
        href: 'css/jasper/default'
      },
      auth: {
        loginFn: (properties: any, request: any) => {
          return request({
            url: jaspersoftServerUrl,
            headers: {
              pp: userToken
            }
          });
        }
      }
    });
  }

Embed Jaspersoft visualizations and dashboards

Now we have visualize.js and the VisualizeHelper included and configured in the app, we can use its API to embed visualizations from the JasperReports Server into your pages.

Visualize Guide 7.2 - report

Visualize Guide 7.2 - ad hoc views

Visualize Guide 7.2 - dashboards

The manager dashboard page in bikeshare embeds reports :

https://github.com/TIBCOSoftware/js-bikeshare-demo/blob/Sherman-for-the-1st-workshop/UI/src/pages/Dashboard/Dashboard.tsx

A visualizeHelper global variable is created and imported into pages as needed.

import { visualizeHelper } from '../../helpers/VisualizeHelper';

In a React based page, you can embed reports and interact with their hyperlinks:

Visualize Guide 7.2 - hyperlinks

  /* Helper function to embed a report/visualization in page */
  displayReport(
    reportName: string,
    containerId: string,
    params: any,
    linkOptions: any = {}
  ) {
    return visualizeHelper.getReport(
      `/path/to/Report/in/JasperReports/Server/repository/${reportName}`,
      containerId,
      params,
      linkOptions
    );
  }
 
  /* display the visualization and handle clicks with a function */
  this.displayReport('FM_Dashboard_KPIS', 'kpi-report', params, {
    events: {
      click: this.changeDetailsReport
    }
  })
 
  /* click handler updates a visualization in a particular DIV */
  changeDetailsReport = (e: any, link: any) => {
    e.preventDefault();
    /* run a new report in the 'in-need-report' DIV */
    this.displayReport(link.href, 'in-need-report', this.getParams());
    this.setState({ kpiDetailReport: link.href });
  };
 
  /* DIVs referred to in displayReports */
  render() {
    ....
    <div className={'grid__row dashboard-body__KPI'}>
      <div className={'grid__column-12 grid__column-m-4'}>
        <div
          id={'kpi-report'}
        />
      </div>
    </div>
 
    <div className={'grid__row dashboard-body__report-row'}>
      <div className={'grid__column-8 grid__column-m-4'} >
        <div id={'in-need-report'} className={'dashboard-body__report-container'}></div>
      </div>
      <div className={'grid__column-4 grid__column-m-4'}>
        <div
          id={'peak-trip-report'}
          className={'dashboard-body__report-container'}
        />
      </div>
    </div>
    ....
  }

Dashboards and ad hoc views can also be embedded with visualize.js.

visualizeHelper.getAdHocView(this.state.selectedReportUri, 'targetDIV')
        .then((success: any) => {
                var adhocView: any = success.adhocView;
                var result: any = success.success;
                this.setState({ currentAdhocView: adhocView });
});
 
 
visualizeHelper.getDashboard(this.state.selectedReportUri, 'targetDIV')
        .then((success: any) => {
                var dashboard: any = success.dashboard;
                var result: any = success.success;
                this.setState({ currentDashboard: dashboard });
});

The input controls of a report and the filters on an ad hoc view can be automatically rendered.

    /* render the input controls in the 'inputControl' DIV */
    await visualizeHelper.getInputControl(
        this.state.selectedUri,
        'inputControl',
        {}, 
        {
            /* when the controls change, change the report/ad hoc view */
            change: this.changeReport
        }
    ) .then((success: any) => {
        var inputControls: any = success.inputControls;
        var filters: any = success.success;
        this.setState({ currentFilters: inputControls });
    }) .catch((err) => {
        console.log('I get called:', err.message);
    });
 
  /* Run the report/ad hoc view with changed parameters/filters.
     This will cause the updated visualization to be displayed in the original DIV */
 
  changeReport = (params: any, error: any) => {
    if (!error && this.state.currentAdhocView != null) {
        this.state.currentAdhocView.params(params).run();
    }
  }

JasperReports Server screens in iFrames

There are web user interfaces within the core JasperReports Server that can easily be used within your web application through iFrame embedding. It is suggested that you use your own user interface to embed report/visualizations, dashboards and ad hoc views. The Jaspersoft dashboard designer and ad hoc editor can only be embedded with iFrames.

The JasperReports Server screens that can be embedded are defined through the HTTP API Ultimate Guide 7.2 - Repository HTTP API

You should use themes Administrator Guide 7.2-Themes to make the JasperReports Server screens synchronize with your application look and feel.

If you are using visualize.js, authentication against JasperReports Server is managed for you.

You can use a HTML iframe directly in your user interface as follows:

const EditAdhoc = (props: Props) => {
  const search = props.location.search;
  const params = new URLSearchParams(search);
  const uri = params.get('resource'); 
 
  /* edit the ad hoc view at the given URI */
  let url = '/jasperserver-pro/flow.html?_flowId=adhocFlow&decorate=no&theme=bike_share&resource=' + uri;
 
  return (
    <>
      <NavBar />
      <div className={'adhoc-container'}>
        <div className={'grid adhoc-view'}>
          <div className={'grid__row'}>
            <div className={'grid__column-12 grid__column-m-4'}>
 
              /* use the URL defined above */
 
              <iframe
                className={'adhoc-frame'}
                src={url}
              />
            </div>
          </div>
        </div>
      </div>
    </>
  );
};

Data for your UI from JasperReports Server

JasperReports Server can provide your application with a variety of data that can be fed into your user interface. These data services can replace manual creation of microservices you would have to write by hand.

  • JSON or CSV output from reports via the JasperReports Server REST API
  • Results from input control queries via visualize.js
  • Search results from the JasperReports Server repository via visualize.js

Data from Reports

JasperReports created with Studio can be configured to produce precisely defined CSV, Excel (XLS) and JSON output.

http://jasperreports.sourceforge.net/sample.reference/jasper/index.html#csvmetadataexport

http://jasperreports.sourceforge.net/sample.reference/jasper/index.html#xlsmetadataexport

http://jasperreports.sourceforge.net/sample.reference/subreport/index.html#jsonmetadataexport

Reports can provide all the data access, formatting, aggregation and security controls that would have to be done manually in your application backend microservices.

The output can be easily consumed by your user interface controls.

Data requests from the browser can use the JasperReports Server REST API REST API 7.2-reports When using visualize.js, authentication for the REST services is managed. Avoid CORS problems by using a proxy. JasperReports Server proxy best practices

An example of using report output in visualizations are the maps in the bikeshare user interface. When a map is displayed, a reports REST call is made to get the relevant data. See the attached franchisestatusdata,jrxml or JSON Data reports

/* the JSON data reports in the JasperReports Server repository */
 
const mapDataLocations: any = {
  Region: '/rest_v2/reports/public/Bikeshare_demo/Reports/Data/RegionStationData.json',
  Franchise: '/rest_v2/reports/public/Bikeshare_demo/Reports/Data/FranchiseRegionStatusData.json'
};
 
/* call the JasperReports Server reports REST service */
  async getMap() {
    // Clear Map
    this.setState({displayedMap: ''});
 
    let displayMap = this.state.selectedFilters.Region.value === '~NOTHING~' ? 'Franchise' : 'Region';
    try {
      let mapData = await JasperReportsService.get(mapDataLocations[displayMap], {
        params: {
          Franchise: this.state.selectedFilters.Franchise.value,
          Region: this.state.selectedFilters.Region.value,
          session_id: this.props.sessionId
        }
      });
      if (displayMap === 'Franchise') {
        this.setState({ franchiseMapData: mapData.data, displayedMap: displayMap })
      } else {
        this.setState({ regionMapData: mapData.data[0], displayedMap: displayMap })
      }
    } catch (e) {
      console.error(e);
    }
  }
 
/* JasperReportsService.tsx: used to execute REST calls via axios */
 
import axios from 'axios';
 
export default axios.create({
  baseURL: process.env.REACT_APP_JASPERSERVER_URL,
  responseType: "json",
  withCredentials: true
});

Example response (Franchise with multiple regions):

[{
        "system_id": "BA",
        "region_id": "12",
        "center_lat": 37.812726987248396,
        "center_lon": -122.25393685,
        "name": "Oakland",
        "percent_stations_in_need": 7.5
    }, {
        "system_id": "BA",
        "region_id": "13",
        "center_lat": 37.8382444,
        "center_lon": -122.28589760920258,
        "name": "Emeryville",
        "percent_stations_in_need": 60.0
    }, {
        "system_id": "BA",
        "region_id": "14",
        "center_lat": 37.864978722953396,
        "center_lon": -122.27609962073363,
        "name": "Berkeley",
        "percent_stations_in_need": 21.6
    }, {
        "system_id": "BA",
        "region_id": "3",
        "center_lat": 37.76746875407544,
        "center_lon": -122.4197443574667,
        "name": "San Francisco",
        "percent_stations_in_need": 29.1
    }, {
        "system_id": "BA",
        "region_id": "5",
        "center_lat": 37.33272752146631,
        "center_lon": -121.89331755,
        "name": "San Jose",
        "percent_stations_in_need": 84.3
    }
]

Data from input controls

Input controls attached to reports can be use via visualize run parameterized queries and return results to your app. The default user interface of input controls can be ignored.

  /* run the input controls on a given report to get data and do not diaply on screen */
  getFilterData = () => {
    return visualizeHelper.getInputControl(filterDataICUri, '');
  };
 
 
  /* when the UI starts, getFilterData */
  componentDidMount() {
    // Load Filters, Reports, and Maps in order
    this.getFilterData()
      .then((success: any) => {
        this.setFilters(success);
        this.getMap();
        this.getReports();
      });
  }
 
  /* save the filter data in the UI */
  setFilters(success: any) {
    console.log(success);
    let filterList = Object.assign({}, ...(success.success.map((item: any) => {
        return (
          {
            [item.id]: {
              title: item.label,
              id: item.id,
              options: item.state.options
            }
          }
        );
      }
    )));

Visualize.js Guide 7.2 - Input Controls

Input controls can be interactive in a hierarchical manner to deal with relationships like Country > State > City.

Administrator Guide 7.2 - Cascading Input Controls

A custom control that uses cascading input controls. https://github.com/sgwood63/VisualizeSamples


Feedback
randomness