GMail extension : create a gitlab issue

Context

At please-open.it, we use Gitlab for almost anything from code to documentation, reports, and also managing our “TODO list”.

Gitlab.com is a highly expandable product with their https://docs.gitlab.com/ee/api/rest/ from building custom UI (we have our own timetracker, based on milestones and issues), use CI as “cron tasks” (we are replacing those with n8n.io ) and now we added an interaction directly from Gmail.

Gitlab can receive emails and create directly issues : https://docs.gitlab.com/ee/user/project/issues/create_issues.html#by-sending-an-email.

But it means that you have to enable this feature on each project and you have a specific mail address for each project. Does not fit with our needs.

Our goal was : a client sent me an email, I can have a list of all projects for this specific client and add the email (with modifications) directly as an issue.

Write Gmail extensions : very easy

From https://script.google.com/home/start?pli=1 create a new script.

A GMail extension is a new piece of UI available in the right panel in your inbox. You can generate the code from https://gsao-card-builder.web.app/.

This is how it looks like for us :

alt text

This will create your card we will use later.

appscript.json description file defines the trigger function, services used and the needed scopes for the user authentication :

{
  "timeZone": "Europe/Paris",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "dependencies": {
    "enabledAdvancedServices": [
      {
        "userSymbol": "Gmail",
        "serviceId": "gmail",
        "version": "v1"
      }
    ]
  },
  "oauthScopes": [
    "https://www.googleapis.com/auth/gmail.addons.current.message.action",
    "https://www.googleapis.com/auth/gmail.addons.current.message.metadata",
    "https://www.googleapis.com/auth/gmail.addons.current.message.readonly",
    "https://www.googleapis.com/auth/gmail.addons.execute",
    "https://www.googleapis.com/auth/script.external_request"
  ],
  "addOns": {
    "common": {
      "name": "email-to-crm",
      "logoUrl": "https://media.licdn.com/dms/image/C4D0BAQE-y2rFy8K1zw/company-logo_200_200/0/1630466071308/pleaseopenit_logo?e=2147483647&v=beta&t=30bZ16X1nvqgHL3ut-JZ_KQ_mZaD9CAvDz_3cUyshXI"
    },
    "gmail": {
      "contextualTriggers": [
        {
          "unconditional": {},
          "onTriggerFunction": "onGmailMessageOpen"
        }
      ]
    }
  }
}

In the Google Script file (Code.gs), from the event (e) and the GmailApp class we get all the context :

function onGmailMessageOpen(e) {
    var accessToken = e.gmail.accessToken;
    GmailApp.setCurrentMessageAccessToken(accessToken);

    // Get message details
    var messageId = e.gmail.messageId;
    var message = GmailApp.getMessageById(messageId);
    var subject = message.getSubject().replace("RE: ", "");
    var sender = message.getFrom();
    var senderEmail = sender.replace(/^.+<([^>]+)>$/, "$1");
    
    return createCard();
  }

Create the card

The code you generated before creates a card. This card is needed as a response for the “onGmailMessageOpen” function.

Each element has a “onChangeAction” (for the lists in our case) that calls a custom function you define. Example :

var groupSelectionInput = CardService.newSelectionInput();

groupSelectionInput.setFieldName('group')
groupSelectionInput.setTitle('Group')
groupSelectionInput.setType(CardService.SelectionInputType.DROPDOWN)

groupsListValues.forEach(function (item, index, array) {
    var selected = false;
    if(item.id == selectedGroupId){
    selected = true;
    }
    groupSelectionInput.addItem(item.name, item.id, selected)
});
groupSelectionInput.setOnChangeAction(CardService.newAction()
    .setFunctionName("handleGroupSelectChange")
);

Each handler must return a Card.

Use with gitlab

We have groups, each group is attached to a customer. Under this group, we create projects.

With the sender domain mail, we got the company name :

var company = senderEmail.split("@")[1].split(".")[0];

  function getGroupsForCompany(company){
    // Connect to gitlab : retrieve groups for email sender
    var headers = {
      'Private-token': '-----------'
    }
    var options = {
      'headers': headers
    }
    var page=1;
    var groupsOutput = new Array();
    var still_results = true;
    while(still_results){
      var response = UrlFetchApp.fetch("https://----------/api/v4/groups?search="+company+"&per_page=100&page="+page, options);
      const groups = JSON.parse(response.getContentText());
      if(groups.length < 100){
        still_results = false;
      }
      groups.forEach(function (item, index, array) {
        groupsOutput.push(item);
      });
      page ++;
    }
    return groupsOutput;
  }

If the group is not the right one or if there is no group, a click on the list will load all groups.

Then, we load projects from the group.

And… that’s it ! Just need a function for creating an issue and we are done. Of course, we put a link to the created issue :

function createIssue(action){
    var senderEmail = action.formInput.email;
    var subject = action.formInput.Subject;
    var content = action.formInput.Content;
    var groupId = action.formInput.group;
    var projectId = parseInt(action.formInput.projects);

    var headers = {
      'Private-token': '-------------'
    }

    content = content + "\n\n\n /add_contacts [contact:"+senderEmail+"]";
    var body = {
      'description': content,
      'title': subject
    }
    var options = {
      'headers': headers,
      'method': 'post',
      'payload': JSON.stringify(body),
      'contentType': 'application/json'
    }

    var response = UrlFetchApp.fetch("https://--------------/api/v4/projects/"+projectId+"/issues", options);
    const responseJson = JSON.parse(response.getContentText());
    console.log(responseJson.web_url);

    var builder = CardService.newCardBuilder();
    builder.setHeader(CardService.newCardHeader()
        .setTitle('Gmail to Gitlab issue')
        .setSubtitle('please-open.it')
        .setImageUrl(
            'https://media.licdn.com/dms/image/C4D0BAQE-y2rFy8K1zw/company-logo_200_200/0/1630466071308/pleaseopenit_logo?e=2147483647&v=beta&t=30bZ16X1nvqgHL3ut-JZ_KQ_mZaD9CAvDz_3cUyshXI'
            )
        .setImageStyle(CardService.ImageStyle.CIRCLE));
    builder.addSection(CardService.newCardSection()
        .setHeader('Issue créée')
        .setCollapsible(false)
        .setNumUncollapsibleWidgets(1)
        .addWidget(CardService.newTextParagraph()
            .setText('<a href="'+responseJson.web_url+'">'+responseJson.web_url+'</a>')));
    return builder.build();

  }

Conclusion

We mostly rely on Gitlab, but we sometimes need little tools that help us for productivity I.E our PDF generator from a CI job https://www.mathieupassenaud.fr/markdown-pdf/.

With Gitlab APIs, we have all the stuff we can expect for building those little tools that make all the difference in productivity.

Those projects are only designed for productivity and of course with less effort for development and maintenance.