Demo UI App Structure

This article provides a walk through the demo UI App solution and its components.

When you clone the demo UI App, the solution structure looks like this:

gtmhub-demo-plugin
|
└─── src/
|   |
|   └─── index.html
|   |
|   └─── script.js
|
└─── README.md
|
└─── ngrok.js
|
└─── package.json
|
└─── server.js
|
└─── webpack.config.js
|
└─── .gitignore

It can be logically broken down into the following components:

  • Web application
    • UI
    • Logic
  • Local HTTP Server
  • Ngrok logic
  • Script bundling logic

Web Application

In its essence each Quantive Results UI App represents a web application. Our demo UI App has this logic defined in the /src folder. There you will find the presentation layer implemented in the index.html web page and the logic that interacts with the presentation layer and creates a task in Quantive Results is implemented in script.js.

Presentation layer

Our presentation layer represents a single HTMl document. In the <head> section we define the page title and some meta attributes:

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Quantive Results plugin demo</title>

The <head> section also includes the styles that apply to the UI elements:

<style>
      button {
        padding: 0.6em;
        border-radius: 1em;
        border: none;
        background-color: cadetblue;
        color: white;
      }
    </style>
  </head>

In the <body> of the HTML page we have a text box which will capture the user inout for the desired Task name, and a button which triggers the Task creation.

<body>
	<input  id="taskName" type="text" name="TaskName">
    <button id="btn">Create a task to current item</button>
    <script src="/bundle.js"></script>
  </body>

To extend the demo app presentation layer just make the desired changes to the index.html file.

NOTE Notice the script tag which is loading the bundled javaScript code and any libraries you use from your script.js. The demo UI App is preconfigured to use webpack to bundle all static resources used in the project and load them optimally on your web page. We'll talk more about it later in this guide.

UI App Logic layer

The behavior of our UI App is implemented in script.js. It uses NodeJs and the Quantive Results UI Apps SDK to achieve the following:

  1. Import and initialize the Quantive Results UI Apps SDK:
     import { initialiseSdk } from "@gtmhub/sdk";
     const sdk = initialiseSdk({ pluginId: "my-plugin-id" });
    
    In the SDK initialization method we are also specifying our UI App Id (pluginId). This Id is later used when submitting your UI App to the Quantive Results marketplace and must be a unique identifier.
  2. Get the button and input objects from the HTML document, so we can work wit them in our JS code
    const btn = document.getElementById("btn");
    const taskNameTxtBox = document.getElementById("taskName");
  1. Attaching to the button click event, where we:
    • use the UI Apps SDK getCurrentItem method to get the current Objective or Key Result the UI app is loaded for
    • use the UI Apps SDK createTask method to create a Task and set its parent to be the current item
btn.onclick = async () => {
  const currentItem = await sdk.getCurrentItem();

  sdk.createTask({ name: `update-goal-${taskNameTxtBox.value}`, parentId: currentItem.id, status: "todo" });
};

Local HTTP Server

Your UI App needs an HTTP server so its static contents can be served. You can use any solution that suits best your use case scenario. For the purposes of this demo UI App, we have used express framework. The logic is implemented in the server.js file in the root of the project:

const express = require("express");
const app = express();
const path = require("path");
const cors = require("cors");

app.use(cors());
app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, `src/index.html`));
});

app.get("/hello-world", (req, res) => {
  res.send("<h3>Hello world plugin is working!</h3>");
});

app.use(express.static("dist"));
app.use(express.static("public"));

const port = 5000;

app.listen(port, () => console.log(`Listening on http://localhost:${port}/hello-world`));

We define the starting path of our application to be the /src/index.html file and a static response of Hello world plugin is working on the /hello-world route for testing purposes. We also specify that the app runs on port 5000 on localhost. This way when you say:

npm start

In your terminal the express app is being served on http://localhost:5000 as per the above configuration.

For your convenience, the demo UI App also loads the express cors middleware which enables you to define additional configuration in case you UI App will be making requests to external (other than *.quantive.com) URLs. Alternatively, you can leverage the UI Apps SDK Requests to make CORS requests without the need to explicitly configure anything.

Ngrok logic

The demo UI App comes with ngrok preconfigured. This enables you to get an ngrok public URL any time you run the demo UI App. This logic is implemented in ngrok.js in the root of the project:

const ngrok = require("ngrok");
const port = 5000;

(async () => {
  const url = await ngrok.connect(port);
  console.log(`Your ngrok url is: ${url}`);
  console.log(`Copy the url and use it in your plugin manifest file.`);
  console.log(`Note: remember whenever ngrok url changes to update your manifest file with the new url!`);
})();

It imports the ngrok npm package and establishes a tunnel that forwards any traffic on the public URL ngrok dynamically generates to the specified port, in our case 5000 on localhost.

Webpack bundling

The demo UI App uses webpack to bundle all static resources used in the project and load them optimally on your web page. This logic is defined in the webpack.config.js file in the root of the project:

const path = require("path");

module.exports = {
  entry: "./src/script.js",
  mode: "development",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [".js", ".tsx", ".ts"],
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};

We have configured our demo UI app to bundle our solution source code from /src/script.js and any packages it imports and generate the minimized and optimized output in /dist/bundle.js. This way, the only reference we must make in our HTML document is to /dist/bundle.js and all the necessary modules will be loaded in the browser. This is a good practice approach, instead of having to take care of loading each individual library as a <scrip> tag in our HTML, and is much less error-prone. You can safely leverage on this mechanism and extend the demo UI app logic. Any packages you load in your script.js will be taken care of by the webpack bundling logic. For more information about using webpack, refer to Webpack - Getting Started