Just released: Developer preview of Microsoft Teams bot integration for Now Assistant. Try with your Teams ×

Create a Connector with OAuth

As described in this article about Connector template, Connectors can be personalized. Personalization is most often used with dynamic personalized data that may change frequently. For example, a Connector that fetches your individual tasks from a project management application.

To allow access to this personalized data, most APIs generally ask for OAuth authentication. Therefore, in this guide we're going to create a Connector template and a Connector that uses OAuth authentication to fetch information on open issues on Gitlab. We're then going to display them in a Now Assistant Card. Let's begin!

To follow this guide you should already have a Gitlab account although the process of creating the Connector using other APIs should be similar.

Please refer Gitlab API documenation

Creating a Connector template

We have covered the process of creating a Connector template in a previous template, however for this guide we'll have to add a few changes.

First of all, when creating the folder for our Connector template we're going to select the 'Connector with OAuth2' template:

Chose the template for creating the Connector

Creating a Connector with the OAuth 2 template pre-sets it with settings for OAuth 2 authorization. We're going to add some data to the _definitions.yaml file to make sure that new Connectors using this template will be set correctly.

  1. We'll set the Connector template title to be 'Gitlab.com'.
  2. We'll add the Url endpoint for our API's OAuth 2 authorization which in this case is https://GitLab.com/oauth/authorize.
  3. We'll add the Url endpoint for our API's OAuth 2 access token retrieval which in this case is https://GitLab.com/oauth/token.
  4. Finally, we'll specify which scopes can be selected for the Connector, which in this case is the api scope.

You can find out more about Gitlab's scopes and API here.

Our final _definitions.yaml file now looks like this:

ConnectorTitle: GitLab.com
AccessCodeServiceEndpoint: https://GitLab.com/oauth/authorize
AccessTokenServiceEndpoint: https://GitLab.com/oauth/token
ApiEndPoint: https://GitLab.com/api/v4
UserName: hide
UserPassword: hide
ClientId: required
ClientSecret: required
AccessCodeServiceUrl: hide
AccessTokenServiceUrl: hide
Personalized: !!bool true
ApiKey: hide
ServiceUrl: hide
ServiceUrlHint: ''
XML2JSON: hide
ImpersonateOAuth: required
Interface: hide
Custom1: hide
Custom1Label: Custom Field 1
Custom1Hint: ''
Custom2: hide
Custom2Label: Custom Field 2
Custom3: hide
Custom2Hint: ''
Custom3Label: Custom Field 3
Custom3Hint: ''
Scopes:
- ScopeName: api
  ScopeDescription: API Access
  IsDefault: false

Adding a custom service

By default every Connector has a proxy service that can be set to retrieve the necessary data and send it to a Component. However, what if we wish to have something a bit more specific? For example, in this instance we want to have a service that only returns open issues instead of returning us all the data from the API. To do that we can create a custom service.

Creating a custom service requires 2 steps:

  1. Create a YAML file to hold the new service configuration.
  2. Create the Javascript file that will be run when the service is invoked.

Custom service configuration

Inside the Connector folder we're going to create a new YAML file named _service.myopenissues:

Create a file for custom service configuration

Service configuration files follow a naming convention whereby they must start with a _service prefix.

Next, we're going to edit this file and add an InterfaceName and Description properties:

InterfaceName: items
Description: My Open Issues

Description is the name that will be displayed when selecting the Service for the Component and interface name specifies what name will be used to pass the API data into the Component.

Creating the Connector

Next, we're going to create a Connector that will use our previously made template. The full process is also covered in the previous article, however we are going to make some changes.

In the 'Connector type' input we're going to search for our created Connector by title and select it. We will then add the details such as the client id, client secret, scope and create the Connector:

To get Client Id and Client secret from Gitlab Application, please register our connector in Gitlab oauth Applications by using the OAuth ReturnUrl which shown below the Connector Type.

Add a new Connector

Insert your Gitlab application id and secret in place of <gitlab-client-id> and <gitlab-client-secret>.

Custom service logic

By default a custom service is expected to have a Javascript file that is run when the Connector is invoked. This file's name should be the same as the service configuration file but without the prefix. In our case we'll create a new file named myopenissues.js.

It would be a good idea to handle the API related logic in a separate file, therefore we'll also create a new Javascript file, which we'll name api.js. Our Component's myopenissues.js file will use api.js to make an API request and will then pass the response data into our Component by setting the activity.Response.Data.

Within the api.js file we want to be able to make a HTTP request to our Gitlab API, thus we'll create methods to do that. We'll start with this code in myopenissues.js:

const api = require('./api');


module.exports = async function (activity) {

    try {

        api.initialize(activity);  

        const response = await api('/issues?state=opened');

        activity.Response.Data = response.body;

    } catch (error) {

        // return error response
        var m = error.message;    
        if (error.stack) m = m + ": " + error.stack;

        activity.Response.ErrorCode = (error.response && error.response.statusCode) || 500;
        activity.Response.Data = { ErrorText: m };

    }

};

We don't yet have the api.initialize and api functions yet, but we'll create them next.

API call logic

First of all, we need to make sure we import the dependencies required to make a HTTP request. We're going to use Javascript libraries get and agentkeepalive to handle the HTTP call and request headers:

'use strict';
const got = require('got');
const HttpAgent = require('agentkeepalive');
const HttpsAgent = HttpAgent.HttpsAgent;

Now we'll create the main function that will make the API call. We also want to export it so it's available to require from myopenissues.js. Since we want api.js to have access to the state of the Connector (activity) we'll also create a variable _activity and a method to initialize it:

let _activity = null;

function api(path, opts) {
    if (typeof path !== 'string') {
        return Promise.reject(new TypeError(`Expected \`path\` to be a string, got ${typeof path}`));
    }


    opts = Object.assign({
        json: true,
        token: _activity.Context.connector.token,
        endpoint: _activity.Context.connector.endpoint,
        agent: {
            http: new HttpAgent(),
            https: new HttpsAgent()
        }
    }, opts);

    opts.headers = Object.assign({
        'accept': 'application/json',
        'user-agent': 'adenin Now Assistant Connector, https://www.adenin.com/now-assistant'
    }, opts.headers);

    if (opts.token) {
        opts.headers.Authorization = `Bearer ${opts.token}`;
    }

    const url = /^http(s)\:\/\/?/.test(path) && opts.endpoint ? path : opts.endpoint + path;


    return got(url, opts).catch(err => {

        throw err;
    });
}

// add the initialization function to the exports
api.initialize = function(activity) {
    _activity = activity;
}

module.exports = api;

Notice how we set the authorization header with an access token from _activity.Context.connector.token. The Connector with OAuth 2 template is designed to simplify OAuth authorization workflow. When Now Assistant is authorized to call the API on the user's behalf, it automatically retrieves the API access token and makes it available in the Connector within _activity.Context.connector.token.

If we tested this code it would break as we haven't authorized our Connector to access the Gitlab's API. We'll do this next.

Creating the Card

To authorize the Connector we first of all need to create a Component that would use it, therefore we'll create a new Card. When creating a new Card in the 'Service' dropdown you should see and select the custom service we've created earlier to ensure that the Card is calling the right API:

Create a new Card that uses the custom service

Authorizing the application

To authorize our Connector we'll navigate to the Now Workplace → My Cards and pin our new Card to the Workplace:

Pin Card to the workplace

Then, if we navigate to the Now Assistant's workplace we should see that the Card asks us to authorize the Connector:

Card requests to authorize Gitlab application

Clicking on the Card should open Gitlab where you'll be able to sign in to your account and authorize the Connector:

Authorize Now Assistant on Gitlab

Note Error 461 → which means the current user needs to Authorize the access, this can be done inline on a card or by going to /App#!at-connectors/at-connectors

To test that our Connector can now receive data I've created a test issue on Gitlab. Let's now navigate to Development → API Documentation and find our Connector:

Find our Connector with custom service

Expanding it an clicking on 'Try it out!' shows an array of open Gitlab issues including my test issue:

Testing the api

Displaying the results on the Card

Now that we have the data we can finally create a template to display the data on the Card. However, our Gitlab API returns a large amount of data that we may not necessarily need. It could get a bit tedious and confusing to work with, therefore we'll create a function called convertIssues inside api.js, which will convert all of this data into a single array that we'll use in our Card called items. We'll add this code to api.js:

// convert response from /issues endpoint to 
api.convertIssues = function(response) {
    let items = [];
    let body = response.body;

    // iterate through each issue and extract id, title, etc. into a new array
    for(let i=0;i<body.length;i++) {
        let raw = body[i];
        let item = { id: raw.id, title: raw.title, description: raw.description, link: raw.web_url, raw: raw}
        items.push(item);
    }

    return { items: items };
}

Now, we'll change myopenissues.js to call that function on the response before returning data to the Card:

// convert response to items[]
activity.Response.Data = api.convertIssues(response);

We'll save both files and open our Gitlab Card in the Designer. If we navigate to the Settings tab we should see that our data is now nicely formatted and ready to be added to the Card:

Formatted API data

To display our Gitlab issues we'll add the following code to the Card's template:

<div class="layout-horizontal p prsm">        
        <div class="font-body1 layout-self-center layout-flex">
                <h2>My open Gitlab issues:</h2>
              {% for issue in items %}
                      <p>{{ forloop.index + 1}}. <at-link href="{{ issue.link }}" target="_blank">{{ issue.title }}</at-link><br/>
                          {{ issue.description }}
                      </p>
              {% endfor %}
    </div>        
</div>

This code will iterate through each issue in our array and create a link and a description. Finally, set Listeners for the content element to _all and modelRoot to state.model, so that it has access to the retrieved data. Let's save the Card and check how it looks.

Here's the final result:

null