Skip to content

Commit

Permalink
Add table jira_issue_worklog Closes #89 (#104)
Browse files Browse the repository at this point in the history
Co-authored-by: Madhushree Ray <[email protected]>
  • Loading branch information
ParthaI and madhushreeray30 authored Nov 15, 2023
1 parent d30a4fe commit cf576ae
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 4 deletions.
80 changes: 80 additions & 0 deletions docs/tables/jira_issue_worklog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Table: jira_issue_worklog

The Jira issue worklog is a feature in Atlassian's Jira software that allows users to record and track the amount of time they have spent working on various tasks or issues. This is particularly useful in project management and software development contexts, where tracking time spent on tasks is crucial for understanding project progress, billing, and workload distribution.

## Examples

### Basic info

```sql
select
id,
self,
issue_id,
comment,
author
from
jira_issue_worklog;
```

### Get time logged for issues

```sql
select
issue_id,
sum(time_spent_seconds) as total_time_spent_seconds
from
jira_issue_worklog
group by
issue_id;
```

### Show the latest worklogs for issues from the past 5 days

```sql
select
id,
issue_id,
time_spent,
created
from
jira_issue_worklog
where
created >= now() - interval '5' day;
```

### Retrieve issues and their worklogs updated in the last 10 days

```sql
select distinct
w.issue_id,
w.id,
w.time_spent,
w.updated as worklog_updated_at,
i.duedate,
i.priority,
i.project_name,
i.key
from
jira_issue_worklog as w,
jira_issue as i
where
i.id like trim(w.issue_id)
and
w.updated >= now() - interval '10' day;
```

### Get author information of worklogs

```sql
select
id,
issue_id,
author ->> 'accountId' as author_account_id,
author ->> 'accountType' as author_account_type,
author ->> 'displayName' as author_name,
author ->> 'emailAddress' as author_email_address,
author ->> 'timeZone' as author_time_zone
from
jira_issue_worklog;
```
7 changes: 4 additions & 3 deletions jira/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ const pluginName = "steampipe-plugin-jira"
// Plugin creates this (jira) plugin
func Plugin(ctx context.Context) *plugin.Plugin {
p := &plugin.Plugin{
Name: pluginName,
DefaultTransform: transform.FromCamel(),
DefaultRetryConfig: &plugin.RetryConfig{ShouldRetryErrorFunc: shouldRetryError([]string{"429"})},
Name: pluginName,
DefaultTransform: transform.FromCamel(),
DefaultRetryConfig: &plugin.RetryConfig{ShouldRetryErrorFunc: shouldRetryError([]string{"429"})},
ConnectionConfigSchema: &plugin.ConnectionConfigSchema{
NewInstance: ConfigInstance,
Schema: ConfigSchema,
Expand All @@ -30,6 +30,7 @@ func Plugin(ctx context.Context) *plugin.Plugin {
"jira_group": tableGroup(ctx),
"jira_issue": tableIssue(ctx),
"jira_issue_type": tableIssueType(ctx),
"jira_issue_worklog": tableIssueWorklog(ctx),
"jira_priority": tablePriority(ctx),
"jira_project": tableProject(ctx),
"jira_project_role": tableProjectRole(ctx),
Expand Down
214 changes: 214 additions & 0 deletions jira/table_jira_issue_worklog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package jira

import (
"context"
"fmt"

"github.com/andygrunwald/go-jira"
"github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto"
"github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform"

"github.com/turbot/steampipe-plugin-sdk/v5/plugin"
)

//// TABLE DEFINITION

func tableIssueWorklog(_ context.Context) *plugin.Table {
return &plugin.Table{
Name: "jira_issue_worklog",
Description: "Jira worklog is a feature within the Jira software that allows users to record the amount of time they have spent working on various tasks or issues.",
Get: &plugin.GetConfig{
KeyColumns: plugin.AnyColumn([]string{"issue_id", "id"}),
Hydrate: getIssueWorklog,
},
List: &plugin.ListConfig{
ParentHydrate: listIssues,
Hydrate: listIssueWorklogs,
KeyColumns: plugin.KeyColumnSlice{
{Name: "issue_id", Require: plugin.Optional},
},
},
Columns: []*plugin.Column{
// top fields
{
Name: "id",
Description: "A unique identifier for the worklog entry.",
Type: proto.ColumnType_STRING,
Transform: transform.FromGo(),
},
{
Name: "issue_id",
Description: "The ID of the issue.",
Type: proto.ColumnType_STRING,
},
{
Name: "self",
Description: "The URL of the worklogs.",
Type: proto.ColumnType_STRING,
},
{
Name: "comment",
Description: "Any comments or descriptions added to the worklog entry.",
Type: proto.ColumnType_STRING,
},
{
Name: "started",
Description: "The date and time when the worklog activity started.",
Type: proto.ColumnType_TIMESTAMP,
Transform: transform.FromField("Started").Transform(convertJiraTime),
},
{
Name: "created",
Description: "The date and time when the worklog entry was created.",
Type: proto.ColumnType_TIMESTAMP,
Transform: transform.FromField("Created").Transform(convertJiraTime),
},
{
Name: "updated",
Description: "The date and time when the worklog entry was last updated.",
Type: proto.ColumnType_TIMESTAMP,
Transform: transform.FromField("Updated").Transform(convertJiraTime),
},
{
Name: "time_spent",
Description: "The duration of time logged for the task, often in hours or minutes.",
Type: proto.ColumnType_STRING,
},
{
Name: "time_spent_seconds",
Description: "The duration of time logged in seconds.",
Type: proto.ColumnType_INT,
},
{
Name: "properties",
Description: "The properties of each worklog.",
Type: proto.ColumnType_JSON,
},
{
Name: "author",
Description: "Information about the user who created the worklog entry, often including their username, display name, and user account details.",
Type: proto.ColumnType_JSON,
},
{
Name: "update_author",
Description: "Details of the user who last updated the worklog entry, similar to the author information.",
Type: proto.ColumnType_JSON,
},

// Standard columns
{
Name: "title",
Description: ColumnDescriptionTitle,
Type: proto.ColumnType_STRING,
Transform: transform.FromField("ID"),
},
},
}
}

type WorklogDetails struct {
jira.WorklogRecord
IssueId string
}

//// LIST FUNCTION

func listIssueWorklogs(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
if h.Item == nil {
return nil, nil
}
issueinfo := h.Item.(IssueInfo)
issueId := d.EqualsQualString("issue_id")

// Minize the API call for given issue ID.
if issueId != "" && issueId != issueinfo.ID {
return nil, nil
}

client, err := connect(ctx, d)
if err != nil {
plugin.Logger(ctx).Error("jira_issue_worklog.listIssueWorklogs", "connection_error", err)
return nil, err
}

last := 0

// If the requested number of items is less than the paging max limit
// set the limit to that instead
queryLimit := d.QueryContext.Limit
var limit int = 5000
if d.QueryContext.Limit != nil {
if *queryLimit < 5000 {
limit = int(*queryLimit)
}
}

for {
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog?startAt=%d&maxResults=%d&expand=properties", issueinfo.ID, last, limit)

req, err := client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
plugin.Logger(ctx).Error("jira_issue_worklog.listIssueWorklogs", "get_request_error", err)
return nil, err
}

w := new(jira.Worklog)
_, err = client.Do(req, w)
if err != nil {
plugin.Logger(ctx).Error("jira_issue_worklog.listIssueWorklogs", "api_error", err)
return nil, err
}

for _, c := range w.Worklogs {
d.StreamListItem(ctx, WorklogDetails{c, issueinfo.ID})

// Context may get cancelled due to manual cancellation or if the limit has been reached
if d.RowsRemaining(ctx) == 0 {
return nil, nil
}
}

last = w.StartAt + len(w.Worklogs)
if last >= w.Total {
return nil, nil
}
}
}

//// HYDRATE FUNCTION

func getIssueWorklog(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) {

issueId := d.EqualsQualString("issue_id")
id := d.EqualsQualString("id")

if issueId == "" || id == "" {
return nil, nil
}

client, err := connect(ctx, d)
if err != nil {
plugin.Logger(ctx).Error("jira_issue_worklog.getIssueWorklog", "connection_error", err)
return nil, err
}

apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s?expand=properties", issueId, id)

req, err := client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
plugin.Logger(ctx).Error("jira_issue_worklog.getIssueWorklog", "get_request_error", err)
return nil, err
}

res := new(jira.WorklogRecord)
_, err = client.Do(req, res)
if err != nil {
if isNotFoundError(err) {
return nil, nil
}
plugin.Logger(ctx).Error("jira_issue_worklog.getIssueWorklog", "api_error", err)
return nil, err
}

return WorklogDetails{*res, issueId}, nil
}
7 changes: 6 additions & 1 deletion jira/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ func convertJiraTime(_ context.Context, d *transform.TransformData) (interface{}
if d.Value == nil {
return nil, nil
}
return time.Time(d.Value.(jira.Time)), nil
if v, ok := d.Value.(jira.Time); ok {
return time.Time(v), nil
} else if v, ok := d.Value.(*jira.Time); ok {
return time.Time(*v), nil
}
return nil, nil
}

// convertJiraDate:: converts jira.Date to time.Time
Expand Down

0 comments on commit cf576ae

Please sign in to comment.