Skip to content

Instantly share code, notes, and snippets.

@bseddon
Last active November 17, 2022 15:16
Show Gist options
  • Save bseddon/cd438c1746e490cd88989a19e6765091 to your computer and use it in GitHub Desktop.
Save bseddon/cd438c1746e490cd88989a19e6765091 to your computer and use it in GitHub Desktop.
How to create a data request in Dundas BI with selections set using view parameters

Data presented on a dashboard is mediated by one or more metric sets. A metric set defines some data set with rows and columns. A user needs only to drag a metric set onto a dashboard to begin using that data. But what happens behind the scenes? What if you want to use a metric set in code? Read on.

Get data

When the Dundas UI needs data it retrieves it using the REST API call for GetData. This REST call uses a POST request. The request body is a JSON array of one or more DataRequest objects. More about these in a moment.

If successful, the response will be an array of DataResponse objects. There should be a response object for every request object.

Request object

The core of retrieving data is creating an array of valid data requests. The REST API document defines the fields expected in a data request but not how the field values should be generated. The process of generating a request object's field values is the focus of this page.

Dashboard

Each data request is a request to access some data from a specific metrics set. So the first step is to collect all the metric sets used by a dashboard. It follows, then, that the first task is to get hold of a dashboard and there a two ways to do this. The first is to use the dashboard service to get a dashboard instance using it's UUID:

let dashboardService = dundas.context.getService("DashboardService");
let dbc = dashboardService.getDashboardById("<some UUID>");

Behind the scenes this uses the REST API getbyids call.

If the JavaScript is being used within Dundas then a better alternative is to use the JS API:

let subcontainer = dundas.context.baseViewService.currentView.control.adapters
    .find(ad => ad instanceof dundas.view.controls.SubCanvasViewContainer);
let subsubcontainer = subcontainer.currentView.control.adapters
    .find(ad => ad instanceof dundas.view.controls.SubCanvasViewContainer);
let dbc = subsubcontainer.currentView.control;

This second approach has two advantages. Firs the dashboard is already downloaded so there is no need for a REST call. Second, the dashboard instance retrieved is the one being shown so any control values are included as view parameters.

Metric sets

Once a reference to a dashboard has been obtained, the metric sets used need to be identified. Note that in the code below it is assumed all the relevant metric sets are only in one dashboard. However, it may be there are nested dashboards on sub-canvases that need to be taken into account.

The first step is to interrogate the dashboard adapters (UI widgets) to access their metric set bindings. This doesn't get the metric set instance, instead it retrieves the ids of all the metric sets being used:

let metricSetIds = dbc.adapters
    .reduce((carry, a) => {
        return carry.concat(a.metricSetBindings.map(binding => binding.metricSetId))
    }, [])
    .filter((id, index, self) => self.indexOf(id) === index);

Once the ids are known, the metric set instances can be retrieved using the metric set service. The service function getMetricSetById returns a promise so the array of promises needs to be handled. Ideally 'await' will not be used and the 'done' function of the 'all' function will be used instead. But in the interests of showing the essential process 'await' is used.

The reduce array function is used to convert the simple array of metric set into hashtable where each key is the metric set id.

let metricSetService = dundas.context.getService('MetricSetService');
let metricSets = (await Promise.all(metricSetIds.map(id => metricSetService.getMetricSetById(id))))
        .reduce((carry, metricSet) => {
                carry[metricSet.id] = metricSet;
                return carry;
            }, {});

Adapter metric set bindings link metric sets and parameter ids so by collecting them then parameter ids used for filtering can be collected next. This function returns a list of unique ids because all metric set binding ids are unique.

let metricSetBindingIds = dbc.adapters
    .reduce((carry, a) => {
        a.metricSetBindings
            .map(metricSetBindings => carry.push(metricSetBindings.id));
        return carry;
    }, []);

Now the metric set binding ids can be used to identify the view parameters of the dashboard that are involved in the filtering of the metric set. The 'elementParameterLinks' array of each view parameter identifies each metric set with which it is associated. The array also identifies the id of the parameter in the metric set. This information is used to create an object that associates the view parameter filter criteria with each metric set parameter.

let parameterIds = dbc.viewParameters
    .reduce((carry, vp) => {
        vp.elementParameterLinks
            .filter(epl => metricSetBindingIds
                .indexOf(epl.metricSetBindingId) >= 0 && carry[epl.parameterId] == undefined)
            .map(epl => carry[epl.parameterId] = vp.parameterValue);
        return carry;
    }, {})

The final step before creating the data request object is to create a hashtable to associate metric sets (via their ids) and their used parameters. There may be other, unused ones.

// Create a list of the parameters indexed by metric set id
let requestParameterIds = Object.keys(metricSets)
    .reduce((carry, metricSetId) => {
        carry[metricSetId] = []
        metricSets[metricSetId].parameters
            .filter(parameter => parameterIds[parameter.id] !== undefined)
            .map(parameter => carry[metricSetId].push(parameter.id))
        return carry;
    }, {});

Data requests

A data request object is created for each metric set. Where a property is set to '{...}' it means the value is an object and the object's properties take their default values so add nothing to this explanation.

The relevant part is the creation of the 'parameterValues' array. This is an array of filter values that will be used by the server to restrict the data returned for the respective metric set. In this case, the view parameter filter criteria are beig applied to each metric set parameter. However, the view parameter has one id while the metric set parameters have another so after the values are applied for each metric set parameter the parameter id must be replaced. Because arrays and objects are passed by reference, only assigning the reference and then changing the id means all copies of the reference will have the id of the last parameter. So its necessary to make each parameter have their own copy of the view parameter's 'parameterValue' field. To do this the parameter value is converted to JSON and converted back.

// Create the request objects
let generatedRequests = Object.keys(metricSets)
    .map(metricSetId => {
        let metricSet = metricSets[metricSetId]
        let metricSetParameterIds = requestParameterIds[metricSetId]
        return {
            "__classType": "dundas.data.Request",
            "pagingOptions": {...},
            "clientData": metricSet.friendlyName,
            "objectId": metricSetId,
            "options": {...},
            "overrides": {...},
            "parameterValues": metricSetParameterIds
                .map(metricSetParameterId => {
                    let result = JSON.parse(JSON.stringify(parameterIds[metricSetParameterId]));
                    result.parameterId = metricSetParameterId;
                    return result;
                }),
            "parameterValueTextCategory": "Simplified",
            "shapingOptions": {...},
            "viewId": dbc.viewId
        }
    })

With the data request set up the data request can be made. This is done using the data retrieval service.

let dataRetrievalService = dundas.context.getService("DataRetrievalService");
let dataSet = await dataRetrievalService.getData( generatedRequests );

As mentioned at the beginning, this service uses the REST API call for GetData to return an array of DataRequest instances.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment