Skip to content

Commit 3a13c7b

Browse files
authored
Initial commit
1 parent cca4598 commit 3a13c7b

16 files changed

+425
-0
lines changed

DESCRIPTION

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Package: tidyJira
2+
Type: Package
3+
Title: Tidy exports of jira data
4+
Version: 0.1.0
5+
Author: Richard Gustavsson [aut, cre]
6+
Maintainer: Richard Gustavsson <rgustavs@gmail.com>
7+
Description: Tidy data export of jira issues. Data set includes issues and issues worklog. Integration is based on REST api
8+
and authentication based on login/password.
9+
Imports: dplyr, httr, jsonlite, lubridate
10+
License: GPL-3
11+
Encoding: UTF-8
12+
LazyData: true
13+
Suggests: testthat
14+
RoxygenNote: 6.0.1

NAMESPACE

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Generated by roxygen2: do not edit by hand
2+
3+
export(jira_connect)
4+
export(jira_get)
5+
export(jira_issue)
6+
export(jira_issue_worklog)
7+
#export(map_chr_hack)

R/jira_connect.R

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
#' Validate the connection to jira by logging in an performing a search
3+
#'
4+
#' @param host
5+
#' @param user
6+
#' @param password
7+
#'
8+
#' @return
9+
#' @export
10+
#'
11+
#' @examples
12+
jira_connect <- function(host, user, password) {
13+
if(is.null(host))
14+
stop('host is NULL.')
15+
16+
if(is.null(user))
17+
stop("user is NULL")
18+
19+
if(is.null(password))
20+
stop("password is NULL")
21+
22+
url <- file.path(host, "rest/api/latest/search?&maxResults=1")
23+
res <- jira_get(url = url, user = user, password = password)
24+
res <- res$issues
25+
26+
#Check if we got an response
27+
if(length(res) != 1) {
28+
stop(paste0("Cannot connect to host:", host))
29+
}
30+
31+
#Return
32+
con <- list(user=user, password=password, host=host)
33+
con
34+
35+
}

R/jira_issue.R

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#' Returns a list of jira issues
2+
#'
3+
#' input can either be a a jira connection or a file with raw data
4+
#'
5+
#' @param con - a jira connection object
6+
#' @param raw_issue_file - instead of alternative to online query.
7+
#'
8+
#' @return a data frame containing issues
9+
#' @export
10+
#'
11+
#' @examples
12+
#'
13+
#' issue <- jira_issue(con)
14+
#' issue <- jira_issue(issue_raw_file = "./issue_raw.RData")
15+
jira_issue <- function(con = NULL, issue_raw_file = NULL){
16+
17+
if(is.null(issue_raw_file)){
18+
#
19+
# online query
20+
#
21+
#Check input - expecting 3 arguments
22+
if(length(con) != 3)
23+
stop('Malformed connection')
24+
25+
#Build query and execute
26+
#url <- file.path(con$host, "rest/api/latest/search?project=TO&maxResults=1000")
27+
#url <- file.path(con$host, "rest/api/latest/search?jql=assignee=marcgide&maxResults=1000")
28+
#url <- file.path(con$host, "rest/api/2/TO/search?")
29+
position <- 0
30+
increment <- 100
31+
issue <- NULL
32+
33+
repeat{
34+
url <- file.path(con$host, paste0('rest/api/latest/search?jql=project%20%3D%20"TO"%20&startAt=', position, '&maxResults=', increment))
35+
#url <- file.path(con$host, paste0('rest/api/latest/search?&startAt=', position, '&maxResults=', increment))
36+
url
37+
res <- jira_get(url = url, user = con$user, password = con$password, verbose = TRUE)
38+
issue_element <- jira_issue_raw_2_issue(res$issues)
39+
if(is.null(issue)){ issue <- issue_element } else {issue <- bind_rows(issue, issue_element)}
40+
if( position + length(res$issues) > res$total){ break }
41+
print(paste0("position: ", position, "/", res$total))
42+
position <- position + increment
43+
}
44+
} else {
45+
#
46+
# file based input
47+
#
48+
load(issue_raw_file)
49+
if( !(exists("issue_raw")) )
50+
stop('Malformed raw file')
51+
issue <- jira_issue_raw_2_issue(issue_raw)
52+
}
53+
54+
#return
55+
issue
56+
57+
}
58+
59+
jira_issue_raw_2_issue <- function(issue_raw){
60+
# Transform output
61+
# using pattern from https://jennybc.github.io/purrr-tutorial/ls01_map-name-position-shortcuts.html
62+
issue <- issue_raw %>% {
63+
tibble(
64+
#Identifiers
65+
id = map_chr(., "id"),
66+
key = map_chr(., "key"),
67+
summary = map_chr_hack(., ~ .x[["fields"]][["summary"]]),
68+
69+
#Category
70+
issuetype_id = map_chr_hack(., ~ .x[["fields"]][["issuetype"]][["id"]]),
71+
issuetype_name = map_chr_hack(., ~ .x[["fields"]][["issuetype"]][["name"]]),
72+
73+
#Effort - How much
74+
timespent = map_chr_hack(., ~ .x[["fields"]][["timespent"]]) %>% as.numeric(),
75+
76+
#Who - creators,
77+
#resolution = map_chr_hack(., ~ .x[["fields"]][["resolution"]]),
78+
creator_key = map_chr_hack(., ~ .x[["fields"]][["creator"]][["key"]]),
79+
creator_name = map_chr_hack(., ~ .x[["fields"]][["creator"]][["name"]]),
80+
assignee_key = map_chr_hack(., ~ .x[["fields"]][["assignee"]][["key"]]),
81+
assignee_name = map_chr_hack(., ~ .x[["fields"]][["assignee"]][["name"]]),
82+
83+
#Dates - when
84+
created_date = map_chr_hack(., ~ .x[["fields"]][["created"]]) %>% ymd_hms(),
85+
resolution_date = map_chr_hack(., ~ .x[["fields"]][["resolutiondate"]]) %>% ymd_hms(),
86+
last_viewed_date = map_chr_hack(., ~ .x[["fields"]][["lastViewed"]]) %>% ymd_hms()
87+
)
88+
}
89+
#return
90+
issue
91+
92+
}

R/jira_issue_worklog.R

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#' iterates through issues and returns issue worklog items
2+
#'
3+
#' @param con - a validated jira connection
4+
#' @param issue - uses issue$key to identify issues to iterate through
5+
#'
6+
#' @return a data frame containing issue_worklog items
7+
#' @export
8+
#'
9+
#' @examples
10+
#' issue_worklog <- jira_issue_worklog(con, issue)
11+
jira_issue_worklog <- function(con, issue){
12+
13+
r <- map(issue$key, ~ jira_issue_worklog_element(con,.x))
14+
a <- bind_rows(r)
15+
16+
a <- a %>%
17+
mutate(time_spent_hours = as.numeric(time_spent_seconds)/3600) %>%
18+
mutate(started_day = floor_date(started), unit="day")
19+
#return
20+
a
21+
}
22+
23+
jira_issue_worklog_element <- function(con, key){
24+
25+
#Check input - expecting 3 arguments
26+
if(length(con) != 3)
27+
stop('Malformed connection')
28+
29+
#
30+
print(paste0("processing id ", key))
31+
32+
#Build query and execute
33+
url <- file.path(host, paste0("rest/api/2/issue/", key , "/worklog"))
34+
res <- jira_get(url = url, user = con$user, password = con$password, verbose = verbose)
35+
36+
# Transform output
37+
issue_worklog_raw <- res$worklogs
38+
39+
# using pattern from https://jennybc.github.io/purrr-tutorial/ls01_map-name-position-shortcuts.html
40+
issue_worklog <- issue_worklog_raw %>% {
41+
tibble(
42+
#Identifiers
43+
self = map_chr(., "self"),
44+
id = map_chr(., "id"),
45+
issue_id = map_chr(., "issueId"),
46+
comment = map_chr(., "comment"),
47+
created = map_chr(., "created") %>% ymd_hms(),
48+
updated = map_chr(., "updated") %>% ymd_hms(),
49+
started = map_chr(., "started") %>% ymd_hms(),
50+
assignee_email = map_chr_hack(., ~ .x[["author"]][["emailAddress"]]),
51+
updater_email = map_chr_hack(., ~ .x[["updateAuthor"]][["emailAddress"]]),
52+
time_spent = map_chr(., "timeSpent"),
53+
time_spent_seconds = map_chr(., "timeSpentSeconds")
54+
)
55+
}
56+
57+
#return
58+
issue_worklog
59+
60+
}

R/jira_utils.R

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#---------------------------
2+
#file container helper functions
3+
#
4+
5+
#
6+
# GET data from jira
7+
#
8+
#' Runs a rest GET call towards jira
9+
#'
10+
#' @param url
11+
#' @param user
12+
#' @param password
13+
#' @param verbose
14+
#'
15+
#' @return a parsed rest document (in the shape of a list)
16+
#' @export
17+
#'
18+
#' @examples
19+
#'
20+
#' res <- jira_get(url = url, user = user, password = password)
21+
22+
jira_get <- function(url = url, user = user, password = password, verbose = verbose){
23+
24+
verbose <- TRUE
25+
res <- GET(url = url,
26+
authenticate(user = user, password = password, "basic"),
27+
add_headers("Content-Type" = "application/json")
28+
#verbose(data_out = verbose, data_in = verbose, info = verbose)
29+
)
30+
res <- content(res, as = "parsed")
31+
32+
res$startAt
33+
return(res)
34+
}
35+
36+
37+
#
38+
#' to avoid issues with map_chr and NULL
39+
#' @param .x
40+
#' @param .f
41+
#' @param ...
42+
#'
43+
#' @return a character vector
44+
#' @export
45+
#'
46+
#' @examples
47+
#' A helper function from https://github.com/jennybc/analyze-github-stuff-with-r#readme
48+
#' same arguments as map_chr
49+
#'
50+
#' issue <- res$issues %>% {
51+
#' tibble(issuetype_id = map_chr_hack(., ~ .x[["fields"]][["issuetype"]][["id"]]))}
52+
map_chr_hack <- function(.x, .f, ...) {
53+
map(.x, .f, ...) %>%
54+
map_if(is.null, ~ NA_character_) %>%
55+
flatten_chr()
56+
}
57+

R/tidyJira.R

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#' tidyJira: A package to generate tidy jira exports for issues
2+
#'
3+
#' The package is used in three steps
4+
#' 1. Connect to jira
5+
#' 2. Get issues
6+
#' 3. Iterate through issues to get extended information
7+
#'
8+
#' @docType package
9+
#' @name tidyJira

man/jira_connect.Rd

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/jira_get.Rd

+27
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/jira_issue.Rd

+24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/jira_issue_worklog.Rd

+22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)