In an earlier article, we talked about hosting Wyn Enterprise in a web application with the help of an iFrame.

In this article, we will go one step further into the embeddable BI capabilities. We'll look at integrating Wyn Enterprise to provide embedded BI to end-users. To achieve this, we will use a React application with the help of Wyn's API endpoints.

Before we dive into the article, let us take a step back and look at what embedded BI is and why has it become essential in today's applications.

Data Insights from Embedded BI

Before embedded BI was popular, analytics tools were required to get insights from data, but such tools were separate from existing business processes for end-users. This forced end-users to switch between typical day-to-day business applications and an external analytics tool.

This partition between business processes and analytics decreased efficiency, increased time to value, and was frustrating throughout the entire organization.

Embedded BI resolves this issue entirely, as it provides an integration of business intelligence tools within a business application for enhanced user experience. Embedded BI provides capabilities such as Reporting, Dashboards, Data Visualizations, and Analysis all together in a single business application so there's no need to navigate to a separate Analytics/BI tool.

Wyn Enterprise is a business intelligence software that delivers collaborative self-service reporting & analytics, data visualization, and data moderation. Wyn Enterprise uses GraphQL as the query language to call its API endpoints and return the requested data.

This article assumes you already have a pre-existing React application. If not, you can run the following npm commands to get setup quickly:

npx create-react-app my-app
cd my-app
npm start

Here is a look at how the application will look at the end of this article:

Embedding Wyn Enterprise in an Application via API - Part I

This is the list of Wyn API functions we will cover in this article:

  1. Authentication
  2. Get Reports List
  3. Get Report Info
  4. View Report
  5. Export Report
  6. Get Dashboards List
  7. Get Dashboard Info
  8. View Dashboard

1. Authentication

Every secure application requires authentication, including Wyn Enterprise. The authentication process in Wyn is simple and straight-forward. The authentication endpoint requires a username, password, client_id, and a client_secret.

The username and password are the same that you use to login to the Wyn portal. The client_id is 'integration' by default unless you've changed it in the configuration.

To obtain the client_secret, you can access the URL '{serverUrl}/management#client-management' and get the client_secret corresponding to the 'integration' client_id.

You must be logged in as the admin to access the above URL (you might have to edit the client to view the complete client_secret).

These are all a part of the request's body and the endpoint returns an access token which is then used in all subsequent API calls to ensure the required permissions are granted.

API call:
Endpoint – {serverUrl}/connect/token

Method: POST

Content-Type: application/x-www-form-urlencoded

Body: grant_type=password&username=<username>&password=<password>&client_id=integration&client_secret={client_secret} 
Example Request:
export async function GetAccessToken(url, user, password) {

    const endpoint = `${url}/connect/token`; 

    const resolveResponse = async (response) => {

        const jsonResponse = await response.json();

        if (jsonResponse.error) return null;

        return jsonResponse.access_token;

    } 

    return await fetch(endpoint, {

        headers: {

            'Content-Type': 'application/x-www-form-urlencoded',

            Accept: '*/*',

        },

        method: 'post',

        body: `grant_type=password&username=${user}&password=${password}&client_id=integration&client_secret=eunGKas3Pqd6FMwx9eUpdS7xmz`,

    }).then(await resolveResponse).catch(error => {

        alert(error);

        return null;

    });

} 
Example Response:
      {  
          "access_token": "nf489aafehf39uadfkjhacf98uashf9hfiasef89aqoduxn",  
          "expires_in": 315360000,  
          "token_type": "Bearer"  
      }      

2. Get Reports List

Once the authentication is successful, the next step would be to show a list of documents that are stored on your Wyn server that the user can select from. Let's look at the API endpoint to show a list of all the reports that exist on the Wyn server.

The call to the server is made using a GraphQL query along with the access token that was fetched through the Authentication API endpoint.

GraphQL query:
          `query: 'query { documenttypes(key:"rdl") { documents{ id, title, type } } }'`

The ‘key’ attribute is what determines the type of document that will be returned as part of the response and the ‘documents’ array determines the list of fields to be returned with their values.

For reports, the 'key' attribute's value is 'rdl'. There are other keys e.g. dbd (for dashboards), dsc (for datasource), dataset (for dataset), etc, which will be discussed later in this article or in a subsequent article. Additional fields can be added to the 'documents' array if required, e.g. created, created_by, modified, modified_by, effective_ops, etc. See below for information on the GraphQL documentation.

API call:
Endpoint – {serverUrl}/api/GraphQL?token={token}

Method: POST

Content-Type: application/json

Body: {GraphQL query} 
Example Request:
const getReportsList = (token: string, serverURL: string) => async () => {

    const graphQuery = {

        query: 'query { documenttypes(key:"rdl") { documents{ id, title } } }'

    };

    const response = await httpHelper.postJson(`${serverURL}/api/GraphQL?token=${token}`, graphQuery); 

    if (response && response.requestError) {

        alert(response && response.requestError);

        return;

    } 

    const { data: { documenttypes } } = response;

    return documenttypes[0].documents;

} 
Example Response:
{

  "data": {

    "documenttypes": [

      {

        "documents": [

          {

            "id": "4b7e3f22-41e1-409e-bd22-c36d4a734e54",

            "filename": "TestPageReport.rdlx",

            "title": "TestPageReport",

            "type": "rdl"

          },

          {

            "id": "6b8d4j22-41e1-409e-bd22-cadjsf48afj4",

            "filename": "TestReport2.rdlx",

            "title": "TestReport2",

            "type": "rdl"

          },

        ]

      }

    ]

  },

  "errors": null

}

3. Get Report Info

Now that we have a list of all the reports, we want to select a report to view it within the application. Before we view the report, the report may require some parameters. This is where the 'reportInfo' API endpoint comes in handy. The reportInfo API endpoint returns a list of parameters (if any) and their default values based on the report's Id, hence making it easy for us to send the values of the parameter(s) for subsequent queries. Only the report Id and the access token are required as part of the GraphQL query.

GraphQL query:
    query: `query { reportInfo(reportId: "${id}") { name, parameters { name, validValues { values { label, value } } defaultValue { values }} } }` 
API call:
Endpoint – {serverUrl}/api/GraphQL?token={token}

Method: POST

Content-Type: application/json

Body: {GraphQL query} 
Example Request:
const getReportInfo = (token: string, serverURL: string) => async (id: string): Promise<ReportingTypes.ReportInfoResponse | null> => {

    const graphQuery = {

        query: `query { reportInfo(reportId: "${id}") { name, parameters { name, validValues { values { label, value } } defaultValue { values }} } }`

    }; 

    const response = await httpHelper.postJson(`${serverURL}/api/GraphQL?token=${token}`, graphQuery);

    if (response && response.errors) {

        alert(response && response.errors[0] && response.errors[0].message);

        return null;

    } 

    const { data: { reportInfo } } = response; 

    return reportInfo as ReportingTypes.ReportInfoResponse;

} 
Example Response:
{

  "data": {

    "reportInfo": {

      "name": "CashFlowReport",

      "parameters": [

        {

          "name": "DateRange",

          "validValues": {

            "values": [

              {

                "label": "This Calender Year",

                "value": "calenderYear"

              },

              {

                "label": "This Month",

                "value": "month"

              },

              {

                "label": "Last Month",

                "value": "lastMonth"

              },

              {

                "label": "This Quarter",

                "value": "quarter"

              },

              {

                "label": "This Fiscal Year",

                "value": "fiscalYear"

              }

            ]

          },

          "defaultValue": {

            "values": [

              "calenderYear"

            ]

          }

        }

      ]

    }

  },

  "errors": null

}

4. View Report

Now we'll view the report in our application. We will use an iFrame for the purpose of displaying the report within our application. The API call to view a report is a three-step process.

The first step is to call the 'render' endpoint with the selected report Id. This call runs the report on the server and returns a job Id.

The second step is to use this job Id to call the 'job' API endpoint which returns the status of the document and the document ID once it has been rendered on the server. Finally, the document Id is used to call the 'exportDocument' endpoint to get the exported document's URL which in turn is set as the source of iFrame and displayed to the user.

The 'exportDocument' endpoint is explained later in this article.

GraphQL query:
            1.  query: `mutation { render(reportId: "${id}", renderPayload: ${renderPayloadString}) { jobId } }`

            2.  query: `query { job(jobId: "${id}") { status, documentId, error } }`

            3.  query: `mutation { exportDocument(documentId: "${id}", exportExtension: "${exportExtension}", renderPayload: ${renderPayloadString}) { resultUrl, verificationUrl } }`
API call:
Endpoint – {serverUrl}/api/GraphQL?token={token}

Method: POST

Content-Type: application/json

Body: {GraphQL query} 
Example Request:
const renderDocument = (token: string, serverURL: string) => async (reportID: string, exportExtension: string, renderPayload: ReportingTypes.RenderPayload) => {

    const getJobID = async (id) => {

        const renderPayloadString = JSON.stringify(renderPayload).replace(/"([^(")"]+)":/g, "$1:");

        const graphQuery = {

            query: `mutation { render(reportId: "${id}", renderPayload: ${renderPayloadString}) { jobId } }`

        }; 

        const response = await httpHelper.postJson(`${serverURL}/api/GraphQL?token=${token}`, graphQuery); 

        if (response && response.errors) {

            alert(response && response.errors[0] && response.errors[0].message);

            return;

        } 

        const { data: { render: { jobId } } } = response;

        return jobId;

    } 

    const getDocumentID = async (id) => {

        const graphQuery = {

            query: `query { job(jobId: "${id}") { status, documentId, error } }`

        }; 

        const response: ReportingTypes.ResponseJobStatus = await httpHelper.postJson(`${serverURL}/api/GraphQL?token=${token}`, graphQuery);

        if (response && response.errors) {

            alert(response && response.errors[0] && response.errors[0].message);

            return null;

        } 

        const { data: { job: { status, documentId, error } } } = response;

        if (status === "ERROR") {

            alert(error);

            return null;

        } 

        if (status === "RUNNING") {

            return getDocumentID(id);

        } 

        return documentId;

    } 

    const jobID = await getJobID(reportID);

    const documentID = await getDocumentID(jobID); 

    if (!documentID) return { documentID: null, documentURL: null }; 

    let renderDocumentPayload: ReportingTypes.RenderDocumentPayload = {

        interactiveActions: renderPayload.interactiveActions,

        settings: renderPayload.settings,

    };

    // override Print option, for disabling Content-Disposition as attachment

    renderDocumentPayload && renderDocumentPayload.settings && renderDocumentPayload.settings.push({ key: 'Print', value: 'true' });

    const documentURL = await exportDocument(token, serverURL)(documentID, exportExtension, renderDocumentPayload);

    return { documentID, documentURL };

}

Example Response:

1. Job ID
{

    "data": {

        "render": {

            "jobId": "51c18ab0-5557-4635-93fc-fedba01a7dc2"

        }

    }, "errors": null

}
2. Document ID
{

    "data": {

        "job": {

            "status": "SUCCESS", "documentId": "a532662d-j24l-4486-b6c9-f70ae237fa81",

            "error": null

        }

    }, "errors": null

}
3. Document URL
{

    "data": {

        "exportDocument": {

            <span data-ref="0157386a-b6f5-4270-b52a-28d235a1aec7">"resultUrl"</span>: "/api/workerJob/997ea20c-u7s3-4e8c-8bdf-92f7482s077b",

            "verificationUrl": "/api/workerJob/928ad40c-e5c4-4gh3-8csf-92asd3f3077b/verify"

        }

    }, "errors": null

}

The 'resultUrl' is what will be used as the source of the iFrame. The type of the exported document is determined by the 'exportExtension' argument passed to the 'exportDocument' endpoint. The 'verificationUrl' can be optionally used for checking export errors after reading result stream.

Direct URL

There's another approach to display the report in an iFrame. This alternate approach is to set the source of the iFrame to the following url with the required report ID and the access token.

{serverUrl}/reports/view/${reportId}?token=${token}

5. Export Report

In any business application, especially those with BI and analytical tools, it is important to provide export functionality for further analysis and to share with stakeholders and colleagues. Once the report has been displayed, you can export it to the desired format using the 'exportReport' API endpoint.

Wyn's API provides the capability to export reports to the following formats:

  • Excel
  • Image
  • HTML
  • PDF
  • CSV
  • JSON
  • DOCX

The exportReport endpoint returns a URL to the exported document, which is then opened in a new window to be downloaded.

GraphQL query:

              query: `mutation { exportReport(reportId: "${id}", exportExtension: 
"${exportExtension}", renderPayload: ${renderPayloadString}) { resultUrl, verificationUrl } }`

The endpoint requires the reportId, the export format (Excel and PDF) and a renderPayload. The renderPayload is an array of {key, value} pairs which contain the report parameters as keys with their corresponding value(s) for parameterized reports.

API call:

Endpoint – {serverUrl}/api/GraphQL?token={token}

Method: POST

Content-Type: application/json

Body: {GraphQL query} 

Example Request:

const exportReport = (token: string, serverURL: string) => async (id: string, exportExtension: string, renderPayload: ReportingTypes.RenderPayload) => {

    const renderPayloadString = JSON.stringify(renderPayload).replace(/"([^(")"]+)":/g, "$1:");

    const graphQuery = {

        query: `mutation { exportReport(reportId: "${id}", exportExtension: "${exportExtension}", renderPayload: ${renderPayloadString}) { resultUrl, verificationUrl } }`

    }; 

    const response = await httpHelper.postJson(`${serverURL}/api/GraphQL?token=${token}`, graphQuery); 

    if (response && response.errors) {

        alert(response && response.errors[0] && response.errors[0].message);

        return;

    }

    const { data: { exportReport: { resultUrl } } } = response;

    return resultUrl;

} 

Example Response:

{

    "data": {

        "exportReport": {

            "resultUrl": "/api/workerJob/8263c103-234d-44ec-83v7-0d8f488d5dac",

            "verificationUrl": "/api/workerJob/8263c103-234d-44ec-83v7-0d8f488d5dac/verify"

        }

    }, "errors": null

}

6. Get Dashboards List

Moving on from reports, let's take a look at dashboards. Dashboards have become quintessential in embedded BI and business applications. They provide an at-a-glance view of key performance indicators (KPIs) and help track and analyze various aspects of a business with the help of data visualizations, such as charts, gauges, matrices, and tables.

In Wyn Enterprise, we can get a list of all Dashboards using the same API endpoint as Reports ('documentTypes'), except the 'key' attribute in this case would be 'dbd', instead of 'rdl'. This specifies that we should return documents with extension 'dbd.'

This will return an array of all Dashboards existing on the Wyn server.

GraphQL query:

              query: 'query { documenttypes(key:"dbd") { documents{ id, title, type } } }'

API call:

Endpoint – {serverUrl}/api/GraphQL?token={token}

Method: POST

Content-Type: application/json

Body: {GraphQL query} 

Example Request:

const getDashboardsList = (token: string, serverURL: string) => async () => {

    const graphQuery = {

        query: 'query { documenttypes(key:"dbd") { documents{ id, title, type } } }'

    }; 

    const response = await httpHelper.postJson(`${serverURL}/api/GraphQL?token=${token}`, graphQuery); 

    if (response && response.requestError) {

        alert(response && response.requestError);

        return;

    } 

    const { data: { documenttypes } } = response;

    return documenttypes[0].documents;

} 

Example Response:

{

  "data": {

    "documenttypes": [

      {

        "documents": [

          {

            "id": "5d07566bf87n1z0001da661c",

            "filename": "Regional Revenue Analytics.dbd",

            "title": "Regional Revenue Analytics",

            "type": "dbd"

          },

          {

            "id": "5d0667d2k87x7b0001da6613",

            "filename": "Income Statement.dbd",

            "title": "Income Statement",

            "type": "dbd"

          },

          {

            "id": "5d020a23h58e7b0001da64b1",

            "filename": "HealthDashboard.dbd",

            "title": "HealthDashboard",

            "type": "dbd"

          },

        ]

      }

    ]

  },

  "errors": null

}

7. Get Dashboard Info

Once we get a list of all Dashboards, we can select a dashboard to view it. However, just like Reports, before we can view a Dashboard, we need to get information about any filters applied on the Dashboard through parameters.

The API endpoint to get the dashboard's info is 'document' along with other arguments as mentioned in the GraphQL query below. This API endpoint returns the list of parameters for the selected dashboard, along with the default values and information on the source of parameter values (e.g., if they are coming in from a dataset).

If the dashboard info has a dataset reference, we need to make another API call to the datasets API endpoint to fetch the available values for the parameters and show them in a dropdown. The datasets API endpoint requires the dataset Id and the column name as arguments.

The return value is the column name and an array of the distinct values in the column.

GraphQL query:

              query: `query { document(id: "${id}") { ... on Dashboard { parameters { name, prompt, hidden, dataType, dateOnly, multiValue, validValues { datasetReference{ datasetId, valueField }, values { label, value } }, defaultValue { values } } } } }`

API call:

Dashboard Info:
Endpoint – {serverUrl}/api/GraphQL?token={token}

Method: POST

Content-Type: application/json

Body: {GraphQL query} 
Dataset values:
Endpoint - {serverurl}/api/pivot/datasets/{datasetid}/column-entries?

Method: POST

Content-Type: application/json

Body: [{"columnId":"{columnname}","subColumnType":0}] 

Example Request:

1. Get Dashboard info:
const getDashboardInfo = (token: string, serverURL: string) => async (id: string): Promise<ReportingTypes.DashboardInfoResponse | null> => {

    const graphQuery = {

        query: `query { document(id: "${id}") { ... on Dashboard { parameters { name, prompt, hidden, dataType, dateOnly, multiValue, validValues { datasetReference{ datasetId, valueField }, values { label, value } }, defaultValue { values } } } } }`

    }; 

    const response = await httpHelper.postJson(`${serverURL}/api/GraphQL?token=${token}`, graphQuery);

    if ((response && response.errors) || (response.data.document.parameters && response.data.document.parameters.length === 0)) {       

        return null;

    }

    const { data: { document } } = response; 

    return document as ReportingTypes.DashboardInfoResponse;

}
Get Dataset values
const getDashboardDatasetValues = (token: string, serverURL: string) => async (datasetReference: ReportingTypes.DatasetReference): Promise<ReportingTypes.DatasetInfoResponse | null> => {

    for (var p in datasetReference) {

        if (p && datasetReference.hasOwnProperty(p)) {

            const queryData = [{ "columnId": `${datasetReference[p].valueField}` }]; 

            const response = await httpHelper.postJson(`${serverURL}/api/pivot/datasets/${datasetReference[p].datasetId}/column-entries?token=${token}`, queryData);

            if (response && response.requestError) {

               return null;

            }

            return response as ReportingTypes.DatasetInfoResponse;

        }

    } 

    return null;

}

Example Response:

1. Dashboard Info:
    {

        "data": {

            "document": {

                "parameters": [

                    {

                        "name": "Parameter1",

                        "prompt": "Parameter1",

                        "hidden": false,

                        "dataType": "String",

                        "dateOnly": false,

                        "multiValue": true,

                        "validValues": {

                            "datasetReference": {

                                "datasetId": "5d832bb6f23041001b31158a",

                                "valueField": "ShiftName"

                            },

                            "values": []

                        },

                        "defaultValue": {

                            "values": [

                                "Night",

                                "Morning"

                            ]

                        }

                    }]

            }

        }, "errors": null

    }

2. Dataset Values

[

    {

        "columnId": "ShiftName",

        "subColumnType": 0,

        "entries": [

            "Night",

            "Morning",

            "Afternoon",

            "Overall",

            ""

        ]

    }

]

8. View Dashboard

Once we have the parameter info and the values from the dataset, we can set the values of the parameters by choosing from the list of values from the drop down (if the parameter has default values or values from a dataset).

The step to view a dashboard involves generating the dashboard url with the dashboard ID and the parameter values.

Once the URL is generated, we can set it as the iFrame's source and view the dashboard on the screen.

URL:

{serverUrl}/dashboards/view/${dashboardId}?token=${token}&dp={"Parameter1":["ParameterValue"]}

Example Request:

const viewDashboard = (token: string, serverURL: string) => async (id: string, parameters: { [key: string]: Array<any> }) => { 

    var url = `/dashboards/view/${id}?token=${token}`;

    let dp = ""; 

    if (parameters && Object.keys(parameters) != null) {

        dp = "&dp={";

        var params = Object.values(parameters).reduce((k, v) => { return v });

        Object.entries(params).map(([key, val]) => {

            dp += Array.isArray(val) ? `"${key}":[${val.map(p => `"${p}"`).join(',')}],` : `"${key}":["${val}"],`;

        });

        dp = dp.slice(0, -1) + "}"; 

        return url + dp;

    }

    else

        return url;

}

Example Response:

"/dashboards/view/5d8c59f14eca2600019d9858?token=dcf5d2f0400968294982c2664814c1cda5b6027f15c2359ce440927dc6035b87&dp={"Parameter1":["Night","Morning"]}"

The Wyn API does not end here. There are several other API endpoints that will be covered in future articles. As Wyn Enterprise uses GraphQL for its API endpoints, GraphQL also provides a way to explore and run the API endpoints through an interactive tool called GraphiQL.

You can access GraphiQL on your Wyn Enterprise server using the URL "{wynserverURL}/graphiql". You must be logged in to the server to access this IDE.

A sample using the APIs explained above is available for download. Since it is a React application, you will need to run the following commands from the command prompt:

‘npm install’- to install required modules

‘npm run start’- to run the application

The first screen will ask you to enter your Wyn server's URL, the username, and password to log in. Once logged in, you will see a list of Reports and Dashboards. You can select the Report or Dashboard you would like to view.

The parameters panel will appear with a list of parameters (if any). Once you click on 'Run', the selected Report/Dashboard will be displayed in the iFrame on the right.

If it's a report, you can use the 'Save As' or 'Export' button at the bottom of the parameters panel to save and export the report.

For any questions on this article, please email Wyn.Experts@grapecity.com.

Are you interested in implementing an advanced BI system into your business?


Understand the Story Behind Your Data

Wyn is a web-based BI and data analytics platform that provides greater insight into your data.

Wyn offers built-in tools for report and dashboard creation, data governance, security integration, embedded BI, automated document distribution, and a business-user friendly interface for self-service business intelligence.

Test drive on your own data with our free evaluation.

You can also request a personalized demo to address your individual BI needs.