Quem nunca esqueceu de apontar as horas das tarefas, né!?
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:
No setor indicado acima clique em “criar e gerenciar tokens de API”. Essa ação irá levar a tela abaixo:
Conforme orientado acima clique em criar token de API. Abrindo assim, a tela seguinte:
Na opção de login insira um nome para o acesso, e clique no botão criar. Indo então para tela abaixo:
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)