Introduction
The ThinkIQ platform offers a rich set of options for folks that want to write code and build stuff around their digital twins. In this tutorial we want to focus on Browser Scripts and specifically on how we can go about building them using Visual Studio Code. Browser Scripts generate custom pages and can be built using the mini-IDE that ships with the platform. Why would we want to use VS Code for this? Because it allows you to test your code off-platform while accessing platform data, version control it with GitHub, keep multiple files in sync as your projects grow, and use the raw power of one of the worlds most favorite IDE's. Finally, you can chose to publish your application either on your platform or off-platform, wherever you wish. "SaaS 4 SaaS", if you will.
Technology Stack
The ThinkIQ platform allows you to create code in SQL, PHP, or Python. By definition PHP includes HTML, JS, and CSS. We can access and manipulate the model from within the platform using a couple of SDK's (you guessed it: PHP and Python), straight up SQL, or GraphQL. To build pages off-platform we will use the GraphQL endpoint and plain HTML/JS/CSS. The platform's UI makes extensive use of Bootstrap and Vue.js - so we'll stick with those as well.
Goal
In this tutorial we want to build a simple page that shows the Units of Measure that are included in our platform. To achieve this, we will complete the following steps:
- Setup a new VS Code project
- Include JS code that can obtain a authentication token from the platform
- Include JS code that can make GraphQL calls to the platform
- Build an html page that includes a Vue.js app that displays Units of Measure obtained from the platform
- Deploy the page in the platform using the mini-IDE
The final result will be a simple report that looks like this:
Getting Started
We'll start from scratch by creating a new folder and opening it in VS Code. Nothing like a fresh start.
Next, let's create 3 files:
- getToken.js - we need a function that can obtain a JWT access token from an authenticator in the platform
- graphQLHelpers.js - we need a function that can make web request calls to the platform's GraphQL endpoint
- uom.html - this is where we put our html content and vue.js JavaScript code
Authentication
If we want to call the GraphQL API of the platform, we need to accompany web requests with a jwt token, which we obtain from the platform's authenticator. There are 2 mutation calls that are needed to accomplish this: first we use our ClientID, Role, and UserName to obtain a challenge. Then we use the challenge, along with our ClientID and ClientSecret to obtain the jwt access token. We only need this method when we access GraphQL from outside the platform. When we deploy our code to the platform in our final step we won't include this code.
More detailed instructions on how to setup an authenticator can be found here.
The complete getToken.js file looks like this:
getTokenAsync = async () => {
// required authenticator metadata
const authenticator = {
"graphQlEndpoint": "https://platformname.cesmii.thinkiq.net/graphql",
"clientId": "Name of Authenticator",
"clientSecret": "Secret of Authenticator",
"userName": "User Name",
"role": "platformname_ro_group"
};
// settings for our graphql post calls
const settings = {
method: 'POST',
headers: { "Content-Type": "application/json" }
};
// first query is to obtain a challenge
const authRequestQuery = `
mutation authRequest {
authenticationRequest(input: {authenticator: "${authenticator.clientId}", role: "${authenticator.role}", userName: "${authenticator.userName}"}) {
jwtRequest {
challenge
message
}
}
}
`;
const challenge = '';
// second query is to obtain a token
authValidationQuery = (challenge) => {
return `
mutation authValidation {
authenticationValidation(input: {authenticator: "${authenticator.clientId}", signedChallenge: "${challenge}|${authenticator.clientSecret}"}) {
jwtClaim
}
}
`;
}
settings.body = JSON.stringify({ query: authRequestQuery });
let fetchAuthRequestQueryResponse = await fetch(authenticator.graphQlEndpoint, settings);
let authRequestQueryResponse = await fetchAuthRequestQueryResponse.json();
settings.body = JSON.stringify({ query: authValidationQuery(authRequestQueryResponse.data.authenticationRequest.jwtRequest.challenge) });
let fetchAuthValidationQueryResponse = await fetch(authenticator.graphQlEndpoint, settings);
let authValidationQueryResponse = await fetchAuthValidationQueryResponse.json();
return authValidationQueryResponse.data.authenticationValidation.jwtClaim;
}
Making GraphQL Calls
We need a function that can make web requests to the GraphQL endpoint of the ThinkIQ platform. We'll spend a little extra time here to setup the code so it will work both on the platform as well as off-platform. We use a isInsideJoomla variable for this. Off-platform we use false, and when we deploy our code in the final step we flip it to true.
There are 2 important things to know about using the platform's GraphQL endpoint:
- The endpoint is '/api/graphql/' for usage on the platform and the full endpoint's url 'https://platformname.cesmii.thinkiq.net/graphql' off-platform.
- On platform we pass form data. Off-platform, however, we go with a standard json payload.
The complete graphQLHelpers.js file looks like this:
// for external usage with bearer token use false.
// inside joomla use true
const isInsideJoomla = false;
// the internal api route is /api/graphql/
// the external api route is ...thinkiq.net/graphql
const apiRoute = isInsideJoomla ? '/api/graphql/' : 'https://platformname.cesmii.thinkiq.net/graphql';
makeRequestAsync = async (query) => {
let settings = { method: 'POST', headers: {} };
if (isInsideJoomla) {
// inside joomla we use form data
let formData = new FormData();
formData.append('query', query);
settings.body = formData;
} else {
// externally we use json data
settings.headers['Content-Type'] = 'application/json';
settings.body = JSON.stringify({ query: query });
}
if (!isInsideJoomla) {
// externally we need to bring a bearer token
// use helper js function to obtain bearer token with authenticator
settings.headers.Authorization = `Bearer ` + await getTokenAsync();
}
// make call to obtain graphql response
let fetchQueryResponse = await fetch(apiRoute, settings);
return await fetchQueryResponse.json();
}
Building the Vue.js App
Once we have our helper files in place there isn't all that much left to make a Vue.app work on a html page. After inserting the three sections below, your page should work and show quantities from your platform. Feel free to debug and step through the js code line by line using your browsers developer tools and also take a look at the web request. Here are the sections that make up our html file:
Header Section
We place script tags into the header if we don't want them to carry over into the platform once we deploy our code. In this example this includes Vue.js and our getToken.js file.
The <head> section of our html file looks like this:
<head>
<title>Units of Measure</title>
<!-- Vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- these are scripts that are loaded for offline development only -->
<script src="/getToken.js"></script>
</head>
Body
Not much to see here. We have a title block and a nested listing of quantity kinds and their respective units of measure.
The html part of the <body> looks like this:
<h1 class="text-center">Units of Measure in the ThinkIQ Platform</h1>
<div id="app">
<div v-for="aQuantity in quantities">
<h4>{{aQuantity.displayName}}</h4>
<div v-for="aUom in aQuantity.measurementUnits" style="padding-left: 50px;">
{{aUom.displayName}}
</div>
</div>
</div>
Script
In the script section of our html file we have our Vue.js code, and we also load the graphQLHelpers.js file. The json structure from our GraphQL response is kept as is and bound directly against the html markup. To obtain the query we used the GraphiQL playground that ships with the platform - super easy.
The script part of the <body> looks like this:
<!-- these are scripts that are loaded for offline development -->
<script src="/graphQLHelpers.js"></script>
<!-- these are scripts that are loaded for offline development -->
<script>
var app = new Vue({
el: "#app",
data() {
return {
quantities: [],
}
},
mounted: function () {
this.getQuantities();
},
methods: {
getQuantities: function () {
let this_vue = this;
let query = `
query MyQuery {
quantities {
displayName
measurementUnits {
displayName
}
}
}
`;
makeRequestAsync(query).then(result => {
this_vue.quantities = result.data.quantities;
});
}
},
});</script>
Deploying the App to the ThinkIQ Platform
You can now upload your code into the platform. Remember, you don't need the getToken.js file.
Let's start with the graphQLHelpers.js file:
- Create a new script of type php headless script using the "blank script" template.
- Paste the js code inside <script></script> tags.
- Take notice of the complete script file name in the blue header section of the mini-IDE-IDE, for instance File: graphql_helpers_10123.php
- Flip the isInsideJoomla variable to true.
Now to the uom.html file:
- Create a new script of type php browser script using the "blank script" template.
- Paste the content between the <body></body> tags into your script file.
- Replace the reference to the graphQLHelpers.js file with the php code below, including your own file name of the helpers script.
- Get those media/com_thinkiq/js/dist files loaded. Lots of good stuff in there.
- You want to replace your "new Vue" with "new core.Vue" - you get Vue with the tiq.core.js package.
<script src="/media/com_thinkiq/js/dist/tiq.core.js"></script>
<!--
<script src="/media/com_thinkiq/js/dist/tiq.components.js"></script> -->
<!--
<script src="/media/com_thinkiq/js/dist/tiq.charts.js"></script> -->
<?php require_once JPATH_ROOT .'/scripts/graphql_helpers_10123.php';
?>
That's it. You can now preview your page, copy the url, and create a menu item for your page.
Trouble Shooting
- make sure the GraphQL endpoint is operational
- try to obtain an access header token
- try to get some data returned using GraphiQL
- authentication now works on behalf of the signed in user
- there is a GraphQL role that is given to users, make sure the role fits the use
- F12 a lot. Use the browsers debug tools to monitor web requests and js execution.
Finally
- The technique described in this tutorial works really well and we've used it to build really slick SPA's that ride on the platform's excellent data infrastructure.
- In addition to using Vue.js, our team is fairly dialed into using plotly for charts and diagrams. Plotly lives in the tiq.charts.js package.
- Make sure to load an empty browser script to see what you get loaded by default. Feel free to rely on Bootstrap, JQuery, and FontAwesome - they load by default.