Gitlab Issues in Google Sheets anzeigen / laden ( via gs script )

gitlab screen photo

Da wir bei EMPPLAN alle Zeiten mit Gitlab tracken, möchte ich diese Auswerten.  Aber die Funktionen innerhalb von Gitlab Community Edition sind recht begrenzt.

Da wurde ich auf ein kleines Script von Tobias Sell aufmerksam gemacht, dieser hat hier in seinem Blogeintrag sein Google App Script vorgestellt. Dieses Script haben wir dann getestet und festgestellt dass immer nur 20 Issues geladen werden ( was im Prinzip der 1. Seite entspricht was die API zurückgibt ).  

Das liegt daran dass die API per default ein Limit von 20 Einträgen besitzt wie man hier der Doku entnehmen kann.  

Also muss das bestehende Script um eine Paginierung erweitert werden im ersten Schritt.

Dazu ändern wir die Funktionen _gitlabIssues sowie gitlabProjectIssues leicht ab. Unten sehen wir die Funktion _gitlabIssues vor der Änderung.

function _gitlabIssues(url, fields){
  Logger.log(url);
  var result=UrlFetchApp
  .fetch(url, {
    "method" : "get",
    "headers": {'PRIVATE-TOKEN': PRIVATETOKEN},
  });
  
  var resultArray=[];
  var fields=fields.split(",");
  var resultObj=JSON.parse(result);
  for(var i=0; i<resultObj.length; i++){
    var issue=resultObj[i];
    var row=[];
    fields.forEach(field=>{
      row.push(_getValue(field, issue));
    });
    resultArray.push(row);
  }

  return resultArray;
}

function gitlabProjectIssues(group, params, fields) {
  return _gitlabIssues('https://'+SERVER+'/api/v4/projects/'+encodeURIComponent(group)+'/issues'+'?'+params, fields);
}

Zum testen Überschreiben wir das Argument params und fügen dort hinzu, `pagination=keyset`. Damit teilen wir gitlab mit dass wir keyset Paginierung nutzen möchten. Per default ist die Paginierung auf offset aktiviert da keyset nicht für alle endpoints vorhanden ist. Mehr dazu in der Doku der Gitlab API.  

Wenn wir das Script nun starten ist alles wie zuvor. Als nächstes ändern wir die Funktion _gitlabIssues ab um alle Seiten zur Anfrage zu laden und erst dann das gesamte Array mit allen Issues zurückzugeben.

Dazu müssen wir den Header der Antwort laden, in GS geht das mittels, `result.getAllHeaders`  und dann nutze ich das li package um die header Links auszulesen und als Objekt bereitzustellen. Da ich nicht weiß ob und wie man in GS npm Pakete Laden kann habe ich den source des li package einfach in das GS eingefügt. Siehe mein Repo auf Github

Abgeänderte Funktion _gitlabIssues:

async function _gitlabIssues(url, fields){
    var result=UrlFetchApp
        .fetch(url, {
            "method" : "get",
            "headers": {'PRIVATE-TOKEN': PRIVATETOKEN},
        });
    var li = returnLi();
    var resultArray=[];
    var fieldsOrig=fields; //copy of fields without modifications
    var fields=fields.split(",");
    var resultObj=JSON.parse(result);
    //all header fields as Object
    var headers = result.getAllHeaders();
    //li package https://www.npmjs.com/package/li
    var headerPages = li.parse(headers['Link']);

    for(var i=0; i<resultObj.length; i++){
        var issue=resultObj[i];
        var row=[];
        fields.forEach(field=>{
            let fieldValue=_getValue(field, issue);
            if(Array.isArray(fieldValue)) {
                fieldValue=fieldValue.join(" ,");
            }
            row.push(fieldValue);
        });
        resultArray.push(row);
    }

    //gitlab pagination
    if(headerPages["next"]) {
        let subArr = await _gitlabIssues(headerPages["next"], fieldsOrig);
        resultArray=resultArray.concat(subArr);
    }
    return resultArray;
}

Die Paginierung, also das ablaufen aller Seiten ist dieser Teil:

if(headerPages["next"]) {
        let subArr = await _gitlabIssues(headerPages["next"], fieldsOrig);
        resultArray=resultArray.concat(subArr);
    }

Da ich die keyset paginierung nutzen möchte aber auch noch die Möglichkeit offen halten  weitere Params hinzufügen, jedoch nicht jedesmal die Paginierungs-Params, "pagination=keyset", einliefern möchte. Habe ich noch eine kleine Helferfunktion geschrieben welche prüft ob pagination in params enthalten ist. Falls nicht dann soll dies einfach ergänzt werden und dann das ganze als URL-String zurückliefern.

Helferfunktion (funktionen) :) :

//simple replacement for URLSearchParams
String.prototype.addQuery = function(obj) {
    return this + Object.keys(obj).reduce(function(p, e, i) {
        return p + (i == 0 ? "" : "&") +
            (Array.isArray(obj[e]) ? obj[e].reduce(function(str, f, j) {
                return str + e + "=" + encodeURIComponent(f) + (j != obj[e].length - 1 ? "&" : "")
            },"") : e + "=" + encodeURIComponent(obj[e]));
    },"");
}

//add  params for pagination if missing
function _extendPagination(params)  {
    let paramObject = _buildObjectFromUrlString(params);
    if(!paramObject["pagination"]){
        paramObject["pagination"]="keyset";
    }
    if(!paramObject["order_by"]){
        //updated_at label_priority  due_date title weight relative_position
        paramObject["order_by"]="created_at";
    }
    return "".addQuery(paramObject);
}

Da in Appscript URLSearchParams nicht funktioniert hat, habe ich diesen Ersatz gefunden und eingebaut. (String.prototype.addQuery)

Den Aufruf von gitlabIssues wird dann wie folgt abgeändert, Beispiel anhand von _gitlabIssues:

function _gitlabIssues(group, params, fields) {
    return _gitlabIssues('https://'+SERVER+'/api/v4/projects/'+encodeURIComponent(group)+'/issues'+'?'+_extendPagination(params), fields);
}

Ich hoffe dieses Update zum Original-Script hat den ein oder anderen etwas weitergeholfen.