DEV Community

Higor Diego
Higor Diego

Posted on

API do Jira com golang

Quem nunca esqueceu de apontar as horas das tarefas, né!?

Alt Text

Já esqueceu de apontar as horas no quadro do jira ?

Se sim, esse artigo é para você!

Pois depois que fizer a implementação desse código, se verá livre desse problema.

O que é Jira ?

Jira é um software comercial desenvolvido pela empresa Australiana Atlassian. É uma ferramenta que permite o monitoramento de tarefas e acompanhamento de projetos garantindo o gerenciamento de todas as suas atividades em único lugar. Wikipédia


As vezes esquecem de colocar aquelas horas trabalhada nos cards do jira e levamos aquela "chamada" do Scrum master no dia seguinte.

Para que isso não aconteça é preciso fazer a integração da chave de autenticação, como veremos a seguir:

Primeiramente acesse a conta da atlassian, logo após abrirá a tela abaixo:

alt text

No setor indicado acima clique em "criar e gerenciar tokens de API". Essa ação irá levar a tela abaixo:

alt text

Conforme orientado acima clique em criar token de API. Abrindo assim, a tela seguinte:

alt text

Na opção de login insira um nome para o acesso, e clique no botão criar. Indo então para tela abaixo:

alt text

Pronto! Estamos com a chave de acesso para consumir a API do jira.

Depois do acesso à chave, teremos que seguir alguns passos para chegar no relatório das horas, que são estes:

  • Identificar o id do sua conta no jira.
  • Listar todas suas tarefas abertas.
  • Contabilizar o que reportou ou não na data escolhida.

O jira disponibiliza uma documentação para o consumo dos recursos, clicando aqui.

Faremos o passo a passo no curl, para entender as chamadas e os seus retornos.

Para identificar a conta id precisamos requisitar a seguinte API:

curl -u seu_email_aqui:seu_token_aqui --location --request GET 'https://seu_dominio_aqui/rest/api/3/users/search'
Enter fullscreen mode Exit fullscreen mode

O retorno esperado da chamada:

[
  {
    "self": "https://seu_dominio_aqui/rest/api/3/user?accountId=aqui_vc_tem_seu_account_id",
    "accountId": "aqui_vc_tem_seu_account_id",
    "accountType": "atlassian",
    "emailAddress": "higor@example.com.br",
    "avatarUrls": {
      "48x48": "https://example.com/",
      "24x24": "https://example.com/",
      "16x16": "https://example.com/",
      "32x32": "https://example.com/"
    },
    "displayName": "Higor Diego",
    "active": true,
    "locale": "pt_BR"
  }
]
Enter fullscreen mode Exit fullscreen mode

Na resposta acima temos um accountId. Com isso, podemos analisar o retorno de todas as suas issues cadastradas por data.

Para pegar todas as suas issues com filtro de data e accountId chamaremos a seguinte API:


curl -u seu_email_aqui:seu_token_aqui --location --request POST 'https://seu_dominio_aqui/rest/api/3/search' \
--header 'Content-Type: application/json' \
--data-raw '{
    "jql": "worklogDate>='2021-01-15' and worklogDate<='2021-02-15' and (worklogAuthor in ('aqui_vc_tem_seu_account_id'))",
    "fields":["worklog"]
}'

Enter fullscreen mode Exit fullscreen mode

No corpo da Http Request enviamos JQL, que representa a Linguagem de Consulta do Jira.

O retorno esperado da chamada:

{
  "expand": "schema,names",
  "startAt": 0,
  "maxResults": 50,
  "total": 1,
  "issues": [
    {
      "expand": "operations,versionedRepresentations,editmeta,changelog,renderedFields",
      "id": "32620",
      "self": "https://seu_dominio_aqui/rest/api/3/issue/326212",
      "key": "key_project_aqui",
      "fields": {
        "worklog": {
          "startAt": 0,
          "maxResults": 20,
          "total": 1,
          "worklogs": [
            {
              "self": "https://seu_dominio_aqui/rest/api/3/issue/32620/worklog/326212",
              "author": {
                "self": "https://seu_dominio_aqui/rest/api/3/user?accountId=account_aqui",
                "accountId": "seu_acount_aqui",
                "avatarUrls": {
                  "48x48": "http://",
                  "24x24": "http://",
                  "16x16": "http://",
                  "32x32": "http://"
                },
                "displayName": "Higor Diego",
                "active": true,
                "timeZone": "America/Sao_Paulo",
                "accountType": "atlassian"
              },
              "updateAuthor": {
                "self": "https://seu_dominio_aqui/rest/api/3/user?accountId=account_aqui",
                "accountId": "account_aqui",
                "avatarUrls": {
                  "48x48": "http://",
                  "24x24": "http://",
                  "16x16": "http://",
                  "32x32": "http://"
                },
                "displayName": "Higor Diego",
                "active": true,
                "timeZone": "America/Sao_Paulo",
                "accountType": "atlassian"
              },
              "created": "2021-01-05T12:21:56.896-0300",
              "updated": "2021-01-05T12:21:56.896-0300",
              "started": "2021-01-05T12:16:53.769-0300",
              "timeSpent": "5m",
              "timeSpentSeconds": 300,
              "id": "5345021321323211",
              "issueId": "326212"
            }
          ]
        }
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Com base no resultado das chamadas de API, vamos agora codificar o alerta em Go.


Criaremos um arquivo chamado helper para nos auxiliares nas seguintes etapas:

  • Pegar a data atual;
  • Formatar a data;
  • Criar o base64 para autenticação Basic;
  • Converter segundos em horas.
package helpers

import (
    "encoding/base64"
    "fmt"
    "time"
)

// BasicAuth - create basic authenticate
func BasicAuth(email, token string) string {
    auth := email + ":" + token
    return fmt.Sprintf("Basic %v", base64.StdEncoding.EncodeToString([]byte(auth)))
}

// NowDate - func date now parse to string
func NowDate() string {
    t := time.Now()
    return FormatDate(t)
}

// FormatDate - Parse data to string
func FormatDate(t time.Time) string {
    return fmt.Sprintf("%d-%02d-%02d", t.Year(), t.Month(), t.Day())
}

func ConvertHour(value float64) float64 {
    return value / 3600
}
Enter fullscreen mode Exit fullscreen mode

Agora faremos um arquivo chamado integration para nos auxiliares na request http.

package integration

import (
    "io/ioutil"
    "net/http"
    "strings"
    "time"
)


const (
    jiraReport = "https://seu_dominio_aqui/rest/api/2/search"
)


// Jira - struct
type Jira struct {
    Hours int
}

// ResponseJiraIssue - struct
type ResponseJiraIssue struct {
    Expand string `json:"expand"`
    Issues []struct {
        Expand string `json:"expand"`
        Fields struct {
            Worklog struct {
                MaxResults int64 `json:"maxResults"`
                StartAt    int64 `json:"startAt"`
                Total      int64 `json:"total"`
                Worklogs   []struct {
                    Author struct {
                        AccountID   string `json:"accountId"`
                        AccountType string `json:"accountType"`
                        Active      bool   `json:"active"`
                        AvatarUrls  struct {
                            One6x16   string `json:"16x16"`
                            Two4x24   string `json:"24x24"`
                            Three2x32 string `json:"32x32"`
                            Four8x48  string `json:"48x48"`
                        } `json:"avatarUrls"`
                        DisplayName string `json:"displayName"`
                        Self        string `json:"self"`
                        TimeZone    string `json:"timeZone"`
                    } `json:"author"`
                    Comment struct {
                        Content []struct {
                            Content []struct {
                                Text string `json:"text"`
                                Type string `json:"type"`
                            } `json:"content"`
                            Type string `json:"type"`
                        } `json:"content"`
                        Type    string `json:"type"`
                        Version int64  `json:"version"`
                    } `json:"comment"`
                    Created          string `json:"created"`
                    ID               string `json:"id"`
                    IssueID          string `json:"issueId"`
                    Self             string `json:"self"`
                    Started          string `json:"started"`
                    TimeSpent        string `json:"timeSpent"`
                    TimeSpentSeconds int64  `json:"timeSpentSeconds"`
                    UpdateAuthor     struct {
                        AccountID   string `json:"accountId"`
                        AccountType string `json:"accountType"`
                        Active      bool   `json:"active"`
                        AvatarUrls  struct {
                            One6x16   string `json:"16x16"`
                            Two4x24   string `json:"24x24"`
                            Three2x32 string `json:"32x32"`
                            Four8x48  string `json:"48x48"`
                        } `json:"avatarUrls"`
                        DisplayName string `json:"displayName"`
                        Self        string `json:"self"`
                        TimeZone    string `json:"timeZone"`
                    } `json:"updateAuthor"`
                    Updated string `json:"updated"`
                } `json:"worklogs"`
            } `json:"worklog"`
        } `json:"fields"`
        ID   string `json:"id"`
        Key  string `json:"key"`
        Self string `json:"self"`
    } `json:"issues"`
    MaxResults int64 `json:"maxResults"`
    StartAt    int64 `json:"startAt"`
    Total      int64 `json:"total"`
}


func mountedHttp (url, authorization, method string, body *strings.Reader) (*http.Response, error) {
    timeout := 5 * time.Second

    client := http.Client{
        Timeout: timeout,
    }
    request, err := http.NewRequest(method, url, body)


    if err != nil {
        return nil, err
    }

    request.Header.Set("Content-Type", "application/json")
    request.Header.Set("Authorization", authorization)

    response, e := client.Do(request)

    if e != nil {
        return nil, e
    }

    return response, nil
}

// RequestHttpJiraReport - chamada http para request de issues do jira.
func RequestHttpJiraReport (authorization string, body *strings.Reader) ([]byte, error) {

    response, err := mountedHttp(jiraReport, authorization, "POST", body)

    if err != nil {
        return nil, err
    }

    defer response.Body.Close()


    data, er := ioutil.ReadAll(response.Body)

    if er != nil {
        return nil, er
    }

    return data, nil
}
Enter fullscreen mode Exit fullscreen mode

Então criaremos um arquivo main para chamar todas as nossas funções, e revelar quantas horas foram feitas no dia.

package main

import (
    "encoding/json"
    "fmt"
    "github.com/higordiego/jira-tutorial/helpers"
    "github.com/higordiego/jira-tutorial/integration"
    "strings"
)

const (
    email = "seu_email_aqui"
    token = "token_aqui"
    accountID = "account_id"
)

func main() {

    authorization := helpers.BasicAuth(email, token)
    equalData := helpers.NowDate()

    var jiraResponse integration.ResponseJiraIssue

    jql := fmt.Sprintf(`{"jql": "worklogDate>='%v' and worklogDate<='%v' and (worklogAuthor in ('%v'))", "fields":["worklog"] }`, equalData, equalData, accountID)

    payload := strings.NewReader(jql)

    result, err := integration.RequestHttpJiraReport(authorization, payload)

    if err != nil {
        panic(err.Error())
    }

    er := json.Unmarshal(result, &jiraResponse)

    if er != nil {
        panic(er.Error())
    }

    var count = 0.0
    for _, r := range jiraResponse.Issues {
        for _, a := range r.Fields.Worklog.Worklogs {
            count += float64(a.TimeSpentSeconds)
        }
    }

    fmt.Printf("Suas horas trabalhadas: %.2f", helpers.ConvertHour(count))
}
Enter fullscreen mode Exit fullscreen mode

E por fim, teremos o seguinte resultado no console:

Suas horas trabalhadas: 0.5

Com esse resultado podemos introduzir varias formas de aviso. Por exemplo:

  • Envio de e-mail;
  • Envio de mensagem por Slack ou WhatsApp;
  • Alerta no Desktop.

Links úteis:

JQL: comece a usar a pesquisa avançada no Jira | Atlassian

The Go Programming Language (golang.org)

The Jira Cloud platform REST API (atlassian.com)

Autenticação HTTP — HTTP | MDN (mozilla.org)

Top comments (0)