sqlmirror 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlmirror/__init__.py +1 -0
- sqlmirror/apply.py +136 -0
- sqlmirror/backfill.py +65 -0
- sqlmirror/cli.py +335 -0
- sqlmirror/cron.py +103 -0
- sqlmirror/db.py +469 -0
- sqlmirror/db_mssql.py +530 -0
- sqlmirror/dev/__init__.py +1 -0
- sqlmirror/dev/cli.py +166 -0
- sqlmirror/dev/compare.py +87 -0
- sqlmirror/dev/e2e.py +261 -0
- sqlmirror/dev/golden.py +182 -0
- sqlmirror/dev/mock_jira.py +347 -0
- sqlmirror/dev/site.py +383 -0
- sqlmirror/dev/synth.py +769 -0
- sqlmirror/golden/01_issues_by_status.sql +8 -0
- sqlmirror/golden/02_issue_keys.sql +7 -0
- sqlmirror/golden/03_created_vs_resolved.sql +8 -0
- sqlmirror/golden/04_open_by_priority.sql +10 -0
- sqlmirror/golden/05_time_in_status.sql +16 -0
- sqlmirror/golden/06_cycle_time.sql +16 -0
- sqlmirror/golden/07_worklog_by_user.sql +11 -0
- sqlmirror/golden/08_fix_versions.sql +11 -0
- sqlmirror/golden/09_bugs_by_affected_version.sql +11 -0
- sqlmirror/golden/10_components.sql +10 -0
- sqlmirror/golden/11_top_labels.sql +9 -0
- sqlmirror/golden/12_select_field_distribution.sql +11 -0
- sqlmirror/golden/13_story_points_by_sprint.sql +11 -0
- sqlmirror/golden/14_sprint_completion.sql +14 -0
- sqlmirror/golden/15_epics.sql +12 -0
- sqlmirror/golden/16_subtasks.sql +10 -0
- sqlmirror/golden/17_blocked_issues.sql +11 -0
- sqlmirror/golden/18_most_discussed.sql +10 -0
- sqlmirror/golden/19_assignee_workload.sql +11 -0
- sqlmirror/golden/20_resolutions.sql +7 -0
- sqlmirror/golden/21_issue_history.sql +10 -0
- sqlmirror/golden/22_reopened.sql +11 -0
- sqlmirror/golden/23_overdue.sql +8 -0
- sqlmirror/golden/24_cascading_select.sql +13 -0
- sqlmirror/golden/25_user_picker.sql +12 -0
- sqlmirror/golden/26_attachments.sql +9 -0
- sqlmirror/golden/27_project_categories.sql +8 -0
- sqlmirror/golden/28_issue_types.sql +7 -0
- sqlmirror/golden/29_estimate_accuracy.sql +11 -0
- sqlmirror/golden/30_project_leads.sql +7 -0
- sqlmirror/golden/tsql/01_issues_by_status.sql +8 -0
- sqlmirror/golden/tsql/02_issue_keys.sql +6 -0
- sqlmirror/golden/tsql/03_created_vs_resolved.sql +8 -0
- sqlmirror/golden/tsql/04_open_by_priority.sql +10 -0
- sqlmirror/golden/tsql/05_time_in_status.sql +16 -0
- sqlmirror/golden/tsql/06_cycle_time.sql +16 -0
- sqlmirror/golden/tsql/07_worklog_by_user.sql +11 -0
- sqlmirror/golden/tsql/08_fix_versions.sql +11 -0
- sqlmirror/golden/tsql/09_bugs_by_affected_version.sql +11 -0
- sqlmirror/golden/tsql/10_components.sql +10 -0
- sqlmirror/golden/tsql/11_top_labels.sql +8 -0
- sqlmirror/golden/tsql/12_select_field_distribution.sql +11 -0
- sqlmirror/golden/tsql/13_story_points_by_sprint.sql +11 -0
- sqlmirror/golden/tsql/14_sprint_completion.sql +14 -0
- sqlmirror/golden/tsql/15_epics.sql +11 -0
- sqlmirror/golden/tsql/16_subtasks.sql +9 -0
- sqlmirror/golden/tsql/17_blocked_issues.sql +11 -0
- sqlmirror/golden/tsql/18_most_discussed.sql +9 -0
- sqlmirror/golden/tsql/19_assignee_workload.sql +11 -0
- sqlmirror/golden/tsql/20_resolutions.sql +7 -0
- sqlmirror/golden/tsql/21_issue_history.sql +10 -0
- sqlmirror/golden/tsql/22_reopened.sql +11 -0
- sqlmirror/golden/tsql/23_overdue.sql +8 -0
- sqlmirror/golden/tsql/24_cascading_select.sql +13 -0
- sqlmirror/golden/tsql/25_user_picker.sql +12 -0
- sqlmirror/golden/tsql/26_attachments.sql +9 -0
- sqlmirror/golden/tsql/27_project_categories.sql +8 -0
- sqlmirror/golden/tsql/28_issue_types.sql +7 -0
- sqlmirror/golden/tsql/29_estimate_accuracy.sql +11 -0
- sqlmirror/golden/tsql/30_project_leads.sql +7 -0
- sqlmirror/idmap.py +161 -0
- sqlmirror/jira.py +223 -0
- sqlmirror/mapper.py +774 -0
- sqlmirror/reconcile.py +73 -0
- sqlmirror/samples/dc_export_example.csv +19 -0
- sqlmirror/samples/dc_reports/mysql_sprint_report.sql +10 -0
- sqlmirror/samples/dc_reports/postgres_assigned_to_user.sql +6 -0
- sqlmirror/samples/dc_reports/postgres_customer_impact.sql +7 -0
- sqlmirror/samples/dc_reports/postgres_time_logged.sql +7 -0
- sqlmirror/samples/dc_reports/postgres_workflow_steps.sql +4 -0
- sqlmirror/samples/dc_reports/sqlserver_open_by_status.sql +9 -0
- sqlmirror/schema.py +518 -0
- sqlmirror/source.py +117 -0
- sqlmirror/sqlcheck.py +647 -0
- sqlmirror/util.py +80 -0
- sqlmirror-0.2.0.dist-info/METADATA +19 -0
- sqlmirror-0.2.0.dist-info/RECORD +95 -0
- sqlmirror-0.2.0.dist-info/WHEEL +5 -0
- sqlmirror-0.2.0.dist-info/entry_points.txt +3 -0
- sqlmirror-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Open issues by priority (status category 3 = Done)
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: open issues
|
|
4
|
+
SELECT pr.pname AS priority, count(*) AS open_issues
|
|
5
|
+
FROM jiraissue ji
|
|
6
|
+
JOIN priority pr ON pr.id = ji.priority
|
|
7
|
+
JOIN issuestatus s ON s.id = ji.issuestatus
|
|
8
|
+
WHERE s.statuscategory <> 3
|
|
9
|
+
GROUP BY pr.pname, pr.sequence
|
|
10
|
+
ORDER BY pr.sequence;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- Average hours spent in each status, from the change history
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: status changes
|
|
4
|
+
WITH transitions AS (
|
|
5
|
+
SELECT cg.issueid, cg.created, ci.newstring,
|
|
6
|
+
lead(cg.created) OVER (PARTITION BY cg.issueid ORDER BY cg.created, cg.id) AS next_change
|
|
7
|
+
FROM changegroup cg
|
|
8
|
+
JOIN changeitem ci ON ci.groupid = cg.id
|
|
9
|
+
WHERE ci.field = 'status'
|
|
10
|
+
)
|
|
11
|
+
SELECT newstring AS status,
|
|
12
|
+
round(avg(extract(epoch FROM (next_change - created)) / 3600)::numeric, 1) AS avg_hours
|
|
13
|
+
FROM transitions
|
|
14
|
+
WHERE next_change IS NOT NULL
|
|
15
|
+
GROUP BY newstring
|
|
16
|
+
ORDER BY newstring;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- Cycle time: first move to In Progress until resolution, per project
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: issues resolved after being In Progress
|
|
4
|
+
SELECT p.pkey,
|
|
5
|
+
round(avg(extract(epoch FROM (ji.resolutiondate - fp.started)) / 86400)::numeric, 1) AS avg_days
|
|
6
|
+
FROM jiraissue ji
|
|
7
|
+
JOIN project p ON p.id = ji.project
|
|
8
|
+
JOIN LATERAL (
|
|
9
|
+
SELECT min(cg.created) AS started
|
|
10
|
+
FROM changegroup cg
|
|
11
|
+
JOIN changeitem ci ON ci.groupid = cg.id
|
|
12
|
+
WHERE cg.issueid = ji.id AND ci.field = 'status' AND ci.newstring = 'In Progress'
|
|
13
|
+
) fp ON true
|
|
14
|
+
WHERE ji.resolutiondate IS NOT NULL AND fp.started IS NOT NULL
|
|
15
|
+
GROUP BY p.pkey
|
|
16
|
+
ORDER BY p.pkey;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Hours logged per person and project (user keys resolve through app_user / cwd_user)
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: worklogs
|
|
4
|
+
SELECT p.pkey, u.display_name, round(sum(w.timeworked) / 3600.0, 1) AS hours
|
|
5
|
+
FROM worklog w
|
|
6
|
+
JOIN jiraissue ji ON ji.id = w.issueid
|
|
7
|
+
JOIN project p ON p.id = ji.project
|
|
8
|
+
JOIN app_user au ON au.user_key = w.author
|
|
9
|
+
JOIN cwd_user u ON u.lower_user_name = au.lower_user_name
|
|
10
|
+
GROUP BY p.pkey, u.display_name
|
|
11
|
+
ORDER BY hours DESC;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Issues per fix version
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: fix versions on issues
|
|
4
|
+
SELECT p.pkey, v.vname, v.released, count(*) AS issues
|
|
5
|
+
FROM nodeassociation na
|
|
6
|
+
JOIN projectversion v ON v.id = na.sink_node_id
|
|
7
|
+
JOIN jiraissue ji ON ji.id = na.source_node_id
|
|
8
|
+
JOIN project p ON p.id = v.project
|
|
9
|
+
WHERE na.association_type = 'IssueFixVersion' AND na.source_node_entity = 'Issue'
|
|
10
|
+
GROUP BY p.pkey, v.vname, v.released
|
|
11
|
+
ORDER BY p.pkey, v.vname;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Bugs per affected version
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: bugs with Affects versions
|
|
4
|
+
SELECT v.vname, count(*) AS bugs
|
|
5
|
+
FROM nodeassociation na
|
|
6
|
+
JOIN projectversion v ON v.id = na.sink_node_id
|
|
7
|
+
JOIN jiraissue ji ON ji.id = na.source_node_id
|
|
8
|
+
JOIN issuetype it ON it.id = ji.issuetype
|
|
9
|
+
WHERE na.association_type = 'IssueVersion' AND it.pname = 'Bug'
|
|
10
|
+
GROUP BY v.vname
|
|
11
|
+
ORDER BY v.vname;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Issues per component
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: components on issues
|
|
4
|
+
SELECT p.pkey, c.cname, count(*) AS issues
|
|
5
|
+
FROM nodeassociation na
|
|
6
|
+
JOIN component c ON c.id = na.sink_node_id
|
|
7
|
+
JOIN project p ON p.id = c.project
|
|
8
|
+
WHERE na.association_type = 'IssueComponent'
|
|
9
|
+
GROUP BY p.pkey, c.cname
|
|
10
|
+
ORDER BY p.pkey, c.cname;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Values of single-select custom fields
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: single-select custom field values
|
|
4
|
+
SELECT cf.cfname, cfo.customvalue, count(*) AS issues
|
|
5
|
+
FROM customfieldvalue cfv
|
|
6
|
+
JOIN customfield cf ON cf.id = cfv.customfield
|
|
7
|
+
JOIN customfieldoption cfo ON cfo.id::text = cfv.stringvalue AND cfo.customfield = cf.id
|
|
8
|
+
WHERE cf.customfieldtypekey IN ('com.atlassian.jira.plugin.system.customfieldtypes:select',
|
|
9
|
+
'com.atlassian.jira.plugin.system.customfieldtypes:radiobuttons')
|
|
10
|
+
GROUP BY cf.cfname, cfo.customvalue
|
|
11
|
+
ORDER BY cf.cfname, cfo.customvalue;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Story points per sprint
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: issues in sprints with story points
|
|
4
|
+
SELECT s."NAME" AS sprint, sum(sp.numbervalue) AS points
|
|
5
|
+
FROM customfieldvalue sv
|
|
6
|
+
JOIN customfield scf ON scf.id = sv.customfield AND scf.customfieldtypekey = 'com.pyxis.greenhopper.jira:gh-sprint'
|
|
7
|
+
JOIN "AO_60DB71_SPRINT" s ON s."ID"::text = sv.stringvalue
|
|
8
|
+
JOIN customfieldvalue sp ON sp.issue = sv.issue
|
|
9
|
+
JOIN customfield spcf ON spcf.id = sp.customfield AND spcf.cfname IN ('Story Points', 'Story point estimate')
|
|
10
|
+
GROUP BY s."NAME", s."START_DATE"
|
|
11
|
+
ORDER BY s."START_DATE";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- Closed sprints: issues committed vs done
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: closed sprints with issues
|
|
4
|
+
SELECT s."NAME" AS sprint,
|
|
5
|
+
count(*) AS issues,
|
|
6
|
+
sum(CASE WHEN st.statuscategory = 3 THEN 1 ELSE 0 END) AS done
|
|
7
|
+
FROM "AO_60DB71_SPRINT" s
|
|
8
|
+
JOIN customfieldvalue cfv ON cfv.stringvalue = s."ID"::text
|
|
9
|
+
JOIN customfield cf ON cf.id = cfv.customfield AND cf.customfieldtypekey = 'com.pyxis.greenhopper.jira:gh-sprint'
|
|
10
|
+
JOIN jiraissue ji ON ji.id = cfv.issue
|
|
11
|
+
JOIN issuestatus st ON st.id = ji.issuestatus
|
|
12
|
+
WHERE s."CLOSED" = true
|
|
13
|
+
GROUP BY s."NAME", s."ID"
|
|
14
|
+
ORDER BY s."ID";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Epics and their child issue counts (Epic-Story Link)
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: epics with child issues
|
|
4
|
+
SELECT ep.pkey AS project, epic.issuenum AS epic_number, epic.summary, count(child.id) AS children
|
|
5
|
+
FROM issuelink il
|
|
6
|
+
JOIN issuelinktype ilt ON ilt.id = il.linktype AND ilt.linkname = 'Epic-Story Link'
|
|
7
|
+
JOIN jiraissue epic ON epic.id = il.source
|
|
8
|
+
JOIN project ep ON ep.id = epic.project
|
|
9
|
+
JOIN jiraissue child ON child.id = il.destination
|
|
10
|
+
GROUP BY ep.pkey, epic.issuenum, epic.summary
|
|
11
|
+
ORDER BY children DESC, epic.issuenum
|
|
12
|
+
LIMIT 20;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Parents and their sub-tasks (jira_subtask_link)
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: sub-tasks
|
|
4
|
+
SELECT parent.pkey AS parent_key, count(*) AS subtasks
|
|
5
|
+
FROM issuelink il
|
|
6
|
+
JOIN issuelinktype ilt ON ilt.id = il.linktype AND ilt.linkname = 'jira_subtask_link'
|
|
7
|
+
JOIN jiraissue parent ON parent.id = il.source
|
|
8
|
+
GROUP BY parent.pkey
|
|
9
|
+
ORDER BY subtasks DESC, parent.pkey
|
|
10
|
+
LIMIT 20;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Open issues blocked by another issue
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: blocking links
|
|
4
|
+
SELECT blocked.pkey AS blocked_issue, blocker.pkey AS blocked_by
|
|
5
|
+
FROM issuelink il
|
|
6
|
+
JOIN issuelinktype ilt ON ilt.id = il.linktype AND ilt.linkname = 'Blocks'
|
|
7
|
+
JOIN jiraissue blocker ON blocker.id = il.source
|
|
8
|
+
JOIN jiraissue blocked ON blocked.id = il.destination
|
|
9
|
+
JOIN issuestatus st ON st.id = blocked.issuestatus
|
|
10
|
+
WHERE st.statuscategory <> 3
|
|
11
|
+
ORDER BY blocked.pkey;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Most commented issues
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: comments
|
|
4
|
+
SELECT ji.pkey, count(*) AS comments
|
|
5
|
+
FROM jiraaction ja
|
|
6
|
+
JOIN jiraissue ji ON ji.id = ja.issueid
|
|
7
|
+
WHERE ja.actiontype = 'comment'
|
|
8
|
+
GROUP BY ji.pkey
|
|
9
|
+
ORDER BY comments DESC, ji.pkey
|
|
10
|
+
LIMIT 10;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Open issues per assignee
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: open issues
|
|
4
|
+
SELECT coalesce(u.display_name, 'Unassigned') AS assignee, count(*) AS open_issues
|
|
5
|
+
FROM jiraissue ji
|
|
6
|
+
JOIN issuestatus st ON st.id = ji.issuestatus
|
|
7
|
+
LEFT JOIN app_user au ON au.user_key = ji.assignee
|
|
8
|
+
LEFT JOIN cwd_user u ON u.lower_user_name = au.lower_user_name
|
|
9
|
+
WHERE st.statuscategory <> 3
|
|
10
|
+
GROUP BY 1
|
|
11
|
+
ORDER BY open_issues DESC;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Full change history of the most changed issue
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: change history
|
|
4
|
+
SELECT cg.created, u.display_name AS author, ci.field, ci.oldstring, ci.newstring
|
|
5
|
+
FROM changegroup cg
|
|
6
|
+
JOIN changeitem ci ON ci.groupid = cg.id
|
|
7
|
+
LEFT JOIN app_user au ON au.user_key = cg.author
|
|
8
|
+
LEFT JOIN cwd_user u ON u.lower_user_name = au.lower_user_name
|
|
9
|
+
WHERE cg.issueid = (SELECT issueid FROM changegroup GROUP BY issueid ORDER BY count(*) DESC, issueid LIMIT 1)
|
|
10
|
+
ORDER BY cg.created, cg.id;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Issues reopened: moved out of a Done-category status
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: issues reopened from a Done status
|
|
4
|
+
SELECT DISTINCT ji.pkey, ci.oldstring AS from_status, ci.newstring AS to_status
|
|
5
|
+
FROM changegroup cg
|
|
6
|
+
JOIN changeitem ci ON ci.groupid = cg.id AND ci.field = 'status'
|
|
7
|
+
JOIN issuestatus old_s ON old_s.id = ci.oldvalue
|
|
8
|
+
JOIN issuestatus new_s ON new_s.id = ci.newvalue
|
|
9
|
+
JOIN jiraissue ji ON ji.id = cg.issueid
|
|
10
|
+
WHERE old_s.statuscategory = 3 AND new_s.statuscategory <> 3
|
|
11
|
+
ORDER BY ji.pkey;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Issues resolved after their due date, or still open past it
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: due dates
|
|
4
|
+
SELECT ji.pkey, ji.duedate, ji.resolutiondate
|
|
5
|
+
FROM jiraissue ji
|
|
6
|
+
WHERE ji.duedate IS NOT NULL
|
|
7
|
+
AND (ji.resolutiondate > ji.duedate OR (ji.resolutiondate IS NULL AND ji.duedate < now()))
|
|
8
|
+
ORDER BY ji.duedate;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Cascading select values (parentkey = '1' marks the child value)
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: cascading select values
|
|
4
|
+
SELECT cf.cfname, parent_opt.customvalue AS parent, child_opt.customvalue AS child, count(*) AS issues
|
|
5
|
+
FROM customfieldvalue pv
|
|
6
|
+
JOIN customfield cf ON cf.id = pv.customfield
|
|
7
|
+
AND cf.customfieldtypekey = 'com.atlassian.jira.plugin.system.customfieldtypes:cascadingselect'
|
|
8
|
+
JOIN customfieldoption parent_opt ON parent_opt.id::text = pv.stringvalue
|
|
9
|
+
LEFT JOIN customfieldvalue cv ON cv.issue = pv.issue AND cv.customfield = pv.customfield AND cv.parentkey = '1'
|
|
10
|
+
LEFT JOIN customfieldoption child_opt ON child_opt.id::text = cv.stringvalue
|
|
11
|
+
WHERE pv.parentkey IS NULL
|
|
12
|
+
GROUP BY 1, 2, 3
|
|
13
|
+
ORDER BY 1, 2, 3;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- User picker custom fields resolved to display names
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: user picker custom field values
|
|
4
|
+
SELECT cf.cfname, u.display_name, count(*) AS issues
|
|
5
|
+
FROM customfieldvalue cfv
|
|
6
|
+
JOIN customfield cf ON cf.id = cfv.customfield
|
|
7
|
+
AND cf.customfieldtypekey IN ('com.atlassian.jira.plugin.system.customfieldtypes:userpicker',
|
|
8
|
+
'com.atlassian.jira.plugin.system.customfieldtypes:multiuserpicker')
|
|
9
|
+
JOIN app_user au ON au.user_key = cfv.stringvalue
|
|
10
|
+
JOIN cwd_user u ON u.lower_user_name = au.lower_user_name
|
|
11
|
+
GROUP BY cf.cfname, u.display_name
|
|
12
|
+
ORDER BY issues DESC;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- Attachment volume per project
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: attachments
|
|
4
|
+
SELECT p.pkey, count(*) AS files, round(sum(fa.filesize) / 1048576.0, 1) AS megabytes
|
|
5
|
+
FROM fileattachment fa
|
|
6
|
+
JOIN jiraissue ji ON ji.id = fa.issueid
|
|
7
|
+
JOIN project p ON p.id = ji.project
|
|
8
|
+
GROUP BY p.pkey
|
|
9
|
+
ORDER BY p.pkey;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Projects and their categories
|
|
2
|
+
-- expect: rows
|
|
3
|
+
SELECT p.pkey, p.pname, pc.cname AS category
|
|
4
|
+
FROM project p
|
|
5
|
+
LEFT JOIN nodeassociation na ON na.source_node_id = p.id AND na.source_node_entity = 'Project'
|
|
6
|
+
AND na.association_type = 'ProjectCategory'
|
|
7
|
+
LEFT JOIN projectcategory pc ON pc.id = na.sink_node_id
|
|
8
|
+
ORDER BY p.pkey;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Original estimate vs time spent on resolved issues
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: resolved issues with an estimate and logged time
|
|
4
|
+
SELECT p.pkey,
|
|
5
|
+
round(sum(ji.timeoriginalestimate) / 3600.0, 1) AS estimated_hours,
|
|
6
|
+
round(sum(ji.timespent) / 3600.0, 1) AS spent_hours
|
|
7
|
+
FROM jiraissue ji
|
|
8
|
+
JOIN project p ON p.id = ji.project
|
|
9
|
+
WHERE ji.resolutiondate IS NOT NULL AND ji.timeoriginalestimate IS NOT NULL AND ji.timespent IS NOT NULL
|
|
10
|
+
GROUP BY p.pkey
|
|
11
|
+
ORDER BY p.pkey;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Created vs resolved per month
|
|
2
|
+
-- expect: rows
|
|
3
|
+
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, created), 0) AS [month],
|
|
4
|
+
COUNT(*) AS created,
|
|
5
|
+
COUNT(resolutiondate) AS resolved
|
|
6
|
+
FROM jiraissue
|
|
7
|
+
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, created), 0)
|
|
8
|
+
ORDER BY [month];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Open issues by priority (status category 3 = Done)
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: open issues
|
|
4
|
+
SELECT pr.pname AS priority, COUNT(*) AS open_issues
|
|
5
|
+
FROM jiraissue ji
|
|
6
|
+
JOIN priority pr ON pr.id = ji.priority
|
|
7
|
+
JOIN issuestatus s ON s.id = ji.issuestatus
|
|
8
|
+
WHERE s.statuscategory <> 3
|
|
9
|
+
GROUP BY pr.pname, pr.sequence
|
|
10
|
+
ORDER BY pr.sequence;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- Average hours spent in each status, from the change history
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: status changes
|
|
4
|
+
WITH transitions AS (
|
|
5
|
+
SELECT cg.issueid, cg.created, ci.newstring,
|
|
6
|
+
LEAD(cg.created) OVER (PARTITION BY cg.issueid ORDER BY cg.created, cg.id) AS next_change
|
|
7
|
+
FROM changegroup cg
|
|
8
|
+
JOIN changeitem ci ON ci.groupid = cg.id
|
|
9
|
+
WHERE ci.field = 'status'
|
|
10
|
+
)
|
|
11
|
+
SELECT newstring AS status,
|
|
12
|
+
ROUND(AVG(DATEDIFF(SECOND, created, next_change) / 3600.0), 1) AS avg_hours
|
|
13
|
+
FROM transitions
|
|
14
|
+
WHERE next_change IS NOT NULL
|
|
15
|
+
GROUP BY newstring
|
|
16
|
+
ORDER BY newstring;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- Cycle time: first move to In Progress until resolution, per project
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: issues resolved after being In Progress
|
|
4
|
+
SELECT p.pkey,
|
|
5
|
+
ROUND(AVG(DATEDIFF(SECOND, fp.started, ji.resolutiondate) / 86400.0), 1) AS avg_days
|
|
6
|
+
FROM jiraissue ji
|
|
7
|
+
JOIN project p ON p.id = ji.project
|
|
8
|
+
CROSS APPLY (
|
|
9
|
+
SELECT MIN(cg.created) AS started
|
|
10
|
+
FROM changegroup cg
|
|
11
|
+
JOIN changeitem ci ON ci.groupid = cg.id
|
|
12
|
+
WHERE cg.issueid = ji.id AND ci.field = 'status' AND ci.newstring = 'In Progress'
|
|
13
|
+
) fp
|
|
14
|
+
WHERE ji.resolutiondate IS NOT NULL AND fp.started IS NOT NULL
|
|
15
|
+
GROUP BY p.pkey
|
|
16
|
+
ORDER BY p.pkey;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Hours logged per person and project (user keys resolve through app_user / cwd_user)
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: worklogs
|
|
4
|
+
SELECT p.pkey, u.display_name, ROUND(SUM(w.timeworked) / 3600.0, 1) AS hours
|
|
5
|
+
FROM worklog w
|
|
6
|
+
JOIN jiraissue ji ON ji.id = w.issueid
|
|
7
|
+
JOIN project p ON p.id = ji.project
|
|
8
|
+
JOIN app_user au ON au.user_key = w.author
|
|
9
|
+
JOIN cwd_user u ON u.lower_user_name = au.lower_user_name
|
|
10
|
+
GROUP BY p.pkey, u.display_name
|
|
11
|
+
ORDER BY hours DESC;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Issues per fix version
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: fix versions on issues
|
|
4
|
+
SELECT p.pkey, v.vname, v.released, COUNT(*) AS issues
|
|
5
|
+
FROM nodeassociation na
|
|
6
|
+
JOIN projectversion v ON v.id = na.sink_node_id
|
|
7
|
+
JOIN jiraissue ji ON ji.id = na.source_node_id
|
|
8
|
+
JOIN project p ON p.id = v.project
|
|
9
|
+
WHERE na.association_type = 'IssueFixVersion' AND na.source_node_entity = 'Issue'
|
|
10
|
+
GROUP BY p.pkey, v.vname, v.released
|
|
11
|
+
ORDER BY p.pkey, v.vname;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Bugs per affected version
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: bugs with Affects versions
|
|
4
|
+
SELECT v.vname, COUNT(*) AS bugs
|
|
5
|
+
FROM nodeassociation na
|
|
6
|
+
JOIN projectversion v ON v.id = na.sink_node_id
|
|
7
|
+
JOIN jiraissue ji ON ji.id = na.source_node_id
|
|
8
|
+
JOIN issuetype it ON it.id = ji.issuetype
|
|
9
|
+
WHERE na.association_type = 'IssueVersion' AND it.pname = 'Bug'
|
|
10
|
+
GROUP BY v.vname
|
|
11
|
+
ORDER BY v.vname;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Issues per component
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: components on issues
|
|
4
|
+
SELECT p.pkey, c.cname, COUNT(*) AS issues
|
|
5
|
+
FROM nodeassociation na
|
|
6
|
+
JOIN component c ON c.id = na.sink_node_id
|
|
7
|
+
JOIN project p ON p.id = c.project
|
|
8
|
+
WHERE na.association_type = 'IssueComponent'
|
|
9
|
+
GROUP BY p.pkey, c.cname
|
|
10
|
+
ORDER BY p.pkey, c.cname;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Values of single-select custom fields
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: single-select custom field values
|
|
4
|
+
SELECT cf.cfname, cfo.customvalue, COUNT(*) AS issues
|
|
5
|
+
FROM customfieldvalue cfv
|
|
6
|
+
JOIN customfield cf ON cf.id = cfv.customfield
|
|
7
|
+
JOIN customfieldoption cfo ON CAST(cfo.id AS NVARCHAR(255)) = cfv.stringvalue AND cfo.customfield = cf.id
|
|
8
|
+
WHERE cf.customfieldtypekey IN ('com.atlassian.jira.plugin.system.customfieldtypes:select',
|
|
9
|
+
'com.atlassian.jira.plugin.system.customfieldtypes:radiobuttons')
|
|
10
|
+
GROUP BY cf.cfname, cfo.customvalue
|
|
11
|
+
ORDER BY cf.cfname, cfo.customvalue;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Story points per sprint
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: issues in sprints with story points
|
|
4
|
+
SELECT s.NAME AS sprint, SUM(sp.numbervalue) AS points
|
|
5
|
+
FROM customfieldvalue sv
|
|
6
|
+
JOIN customfield scf ON scf.id = sv.customfield AND scf.customfieldtypekey = 'com.pyxis.greenhopper.jira:gh-sprint'
|
|
7
|
+
JOIN AO_60DB71_SPRINT s ON CAST(s.ID AS NVARCHAR(255)) = sv.stringvalue
|
|
8
|
+
JOIN customfieldvalue sp ON sp.issue = sv.issue
|
|
9
|
+
JOIN customfield spcf ON spcf.id = sp.customfield AND spcf.cfname IN ('Story Points', 'Story point estimate')
|
|
10
|
+
GROUP BY s.NAME, s.START_DATE
|
|
11
|
+
ORDER BY s.START_DATE;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- Closed sprints: issues committed vs done
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: closed sprints with issues
|
|
4
|
+
SELECT s.NAME AS sprint,
|
|
5
|
+
COUNT(*) AS issues,
|
|
6
|
+
SUM(CASE WHEN st.statuscategory = 3 THEN 1 ELSE 0 END) AS done
|
|
7
|
+
FROM AO_60DB71_SPRINT s
|
|
8
|
+
JOIN customfieldvalue cfv ON cfv.stringvalue = CAST(s.ID AS NVARCHAR(255))
|
|
9
|
+
JOIN customfield cf ON cf.id = cfv.customfield AND cf.customfieldtypekey = 'com.pyxis.greenhopper.jira:gh-sprint'
|
|
10
|
+
JOIN jiraissue ji ON ji.id = cfv.issue
|
|
11
|
+
JOIN issuestatus st ON st.id = ji.issuestatus
|
|
12
|
+
WHERE s.CLOSED = 1
|
|
13
|
+
GROUP BY s.NAME, s.ID
|
|
14
|
+
ORDER BY s.ID;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Epics and their child issue counts (Epic-Story Link)
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: epics with child issues
|
|
4
|
+
SELECT TOP 20 ep.pkey AS project, epic.issuenum AS epic_number, epic.summary, COUNT(child.id) AS children
|
|
5
|
+
FROM issuelink il
|
|
6
|
+
JOIN issuelinktype ilt ON ilt.id = il.linktype AND ilt.linkname = 'Epic-Story Link'
|
|
7
|
+
JOIN jiraissue epic ON epic.id = il.source
|
|
8
|
+
JOIN project ep ON ep.id = epic.project
|
|
9
|
+
JOIN jiraissue child ON child.id = il.destination
|
|
10
|
+
GROUP BY ep.pkey, epic.issuenum, epic.summary
|
|
11
|
+
ORDER BY children DESC, epic.issuenum;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- Parents and their sub-tasks (jira_subtask_link)
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: sub-tasks
|
|
4
|
+
SELECT TOP 20 parent.pkey AS parent_key, COUNT(*) AS subtasks
|
|
5
|
+
FROM issuelink il
|
|
6
|
+
JOIN issuelinktype ilt ON ilt.id = il.linktype AND ilt.linkname = 'jira_subtask_link'
|
|
7
|
+
JOIN jiraissue parent ON parent.id = il.source
|
|
8
|
+
GROUP BY parent.pkey
|
|
9
|
+
ORDER BY subtasks DESC, parent.pkey;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Open issues blocked by another issue
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: blocking links
|
|
4
|
+
SELECT blocked.pkey AS blocked_issue, blocker.pkey AS blocked_by
|
|
5
|
+
FROM issuelink il
|
|
6
|
+
JOIN issuelinktype ilt ON ilt.id = il.linktype AND ilt.linkname = 'Blocks'
|
|
7
|
+
JOIN jiraissue blocker ON blocker.id = il.source
|
|
8
|
+
JOIN jiraissue blocked ON blocked.id = il.destination
|
|
9
|
+
JOIN issuestatus st ON st.id = blocked.issuestatus
|
|
10
|
+
WHERE st.statuscategory <> 3
|
|
11
|
+
ORDER BY blocked.pkey;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Open issues per assignee
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: open issues
|
|
4
|
+
SELECT ISNULL(u.display_name, 'Unassigned') AS assignee, COUNT(*) AS open_issues
|
|
5
|
+
FROM jiraissue ji
|
|
6
|
+
JOIN issuestatus st ON st.id = ji.issuestatus
|
|
7
|
+
LEFT JOIN app_user au ON au.user_key = ji.assignee
|
|
8
|
+
LEFT JOIN cwd_user u ON u.lower_user_name = au.lower_user_name
|
|
9
|
+
WHERE st.statuscategory <> 3
|
|
10
|
+
GROUP BY ISNULL(u.display_name, 'Unassigned')
|
|
11
|
+
ORDER BY open_issues DESC;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Full change history of the most changed issue
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: change history
|
|
4
|
+
SELECT cg.created, u.display_name AS author, ci.field, ci.oldstring, ci.newstring
|
|
5
|
+
FROM changegroup cg
|
|
6
|
+
JOIN changeitem ci ON ci.groupid = cg.id
|
|
7
|
+
LEFT JOIN app_user au ON au.user_key = cg.author
|
|
8
|
+
LEFT JOIN cwd_user u ON u.lower_user_name = au.lower_user_name
|
|
9
|
+
WHERE cg.issueid = (SELECT TOP 1 issueid FROM changegroup GROUP BY issueid ORDER BY COUNT(*) DESC, issueid)
|
|
10
|
+
ORDER BY cg.created, cg.id;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Issues reopened: moved out of a Done-category status
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: issues reopened from a Done status
|
|
4
|
+
SELECT DISTINCT ji.pkey, ci.oldstring AS from_status, ci.newstring AS to_status
|
|
5
|
+
FROM changegroup cg
|
|
6
|
+
JOIN changeitem ci ON ci.groupid = cg.id AND ci.field = 'status'
|
|
7
|
+
JOIN issuestatus old_s ON old_s.id = ci.oldvalue
|
|
8
|
+
JOIN issuestatus new_s ON new_s.id = ci.newvalue
|
|
9
|
+
JOIN jiraissue ji ON ji.id = cg.issueid
|
|
10
|
+
WHERE old_s.statuscategory = 3 AND new_s.statuscategory <> 3
|
|
11
|
+
ORDER BY ji.pkey;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Issues resolved after their due date, or still open past it
|
|
2
|
+
-- expect: rows
|
|
3
|
+
-- needs: due dates
|
|
4
|
+
SELECT ji.pkey, ji.duedate, ji.resolutiondate
|
|
5
|
+
FROM jiraissue ji
|
|
6
|
+
WHERE ji.duedate IS NOT NULL
|
|
7
|
+
AND (ji.resolutiondate > ji.duedate OR (ji.resolutiondate IS NULL AND ji.duedate < GETUTCDATE()))
|
|
8
|
+
ORDER BY ji.duedate;
|