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'

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"
  }
]

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"]
}'

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"
            }
          ]
        }
      }
    }
  ]
}

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
}

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
}

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))
}

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)