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.
Files changed (95) hide show
  1. sqlmirror/__init__.py +1 -0
  2. sqlmirror/apply.py +136 -0
  3. sqlmirror/backfill.py +65 -0
  4. sqlmirror/cli.py +335 -0
  5. sqlmirror/cron.py +103 -0
  6. sqlmirror/db.py +469 -0
  7. sqlmirror/db_mssql.py +530 -0
  8. sqlmirror/dev/__init__.py +1 -0
  9. sqlmirror/dev/cli.py +166 -0
  10. sqlmirror/dev/compare.py +87 -0
  11. sqlmirror/dev/e2e.py +261 -0
  12. sqlmirror/dev/golden.py +182 -0
  13. sqlmirror/dev/mock_jira.py +347 -0
  14. sqlmirror/dev/site.py +383 -0
  15. sqlmirror/dev/synth.py +769 -0
  16. sqlmirror/golden/01_issues_by_status.sql +8 -0
  17. sqlmirror/golden/02_issue_keys.sql +7 -0
  18. sqlmirror/golden/03_created_vs_resolved.sql +8 -0
  19. sqlmirror/golden/04_open_by_priority.sql +10 -0
  20. sqlmirror/golden/05_time_in_status.sql +16 -0
  21. sqlmirror/golden/06_cycle_time.sql +16 -0
  22. sqlmirror/golden/07_worklog_by_user.sql +11 -0
  23. sqlmirror/golden/08_fix_versions.sql +11 -0
  24. sqlmirror/golden/09_bugs_by_affected_version.sql +11 -0
  25. sqlmirror/golden/10_components.sql +10 -0
  26. sqlmirror/golden/11_top_labels.sql +9 -0
  27. sqlmirror/golden/12_select_field_distribution.sql +11 -0
  28. sqlmirror/golden/13_story_points_by_sprint.sql +11 -0
  29. sqlmirror/golden/14_sprint_completion.sql +14 -0
  30. sqlmirror/golden/15_epics.sql +12 -0
  31. sqlmirror/golden/16_subtasks.sql +10 -0
  32. sqlmirror/golden/17_blocked_issues.sql +11 -0
  33. sqlmirror/golden/18_most_discussed.sql +10 -0
  34. sqlmirror/golden/19_assignee_workload.sql +11 -0
  35. sqlmirror/golden/20_resolutions.sql +7 -0
  36. sqlmirror/golden/21_issue_history.sql +10 -0
  37. sqlmirror/golden/22_reopened.sql +11 -0
  38. sqlmirror/golden/23_overdue.sql +8 -0
  39. sqlmirror/golden/24_cascading_select.sql +13 -0
  40. sqlmirror/golden/25_user_picker.sql +12 -0
  41. sqlmirror/golden/26_attachments.sql +9 -0
  42. sqlmirror/golden/27_project_categories.sql +8 -0
  43. sqlmirror/golden/28_issue_types.sql +7 -0
  44. sqlmirror/golden/29_estimate_accuracy.sql +11 -0
  45. sqlmirror/golden/30_project_leads.sql +7 -0
  46. sqlmirror/golden/tsql/01_issues_by_status.sql +8 -0
  47. sqlmirror/golden/tsql/02_issue_keys.sql +6 -0
  48. sqlmirror/golden/tsql/03_created_vs_resolved.sql +8 -0
  49. sqlmirror/golden/tsql/04_open_by_priority.sql +10 -0
  50. sqlmirror/golden/tsql/05_time_in_status.sql +16 -0
  51. sqlmirror/golden/tsql/06_cycle_time.sql +16 -0
  52. sqlmirror/golden/tsql/07_worklog_by_user.sql +11 -0
  53. sqlmirror/golden/tsql/08_fix_versions.sql +11 -0
  54. sqlmirror/golden/tsql/09_bugs_by_affected_version.sql +11 -0
  55. sqlmirror/golden/tsql/10_components.sql +10 -0
  56. sqlmirror/golden/tsql/11_top_labels.sql +8 -0
  57. sqlmirror/golden/tsql/12_select_field_distribution.sql +11 -0
  58. sqlmirror/golden/tsql/13_story_points_by_sprint.sql +11 -0
  59. sqlmirror/golden/tsql/14_sprint_completion.sql +14 -0
  60. sqlmirror/golden/tsql/15_epics.sql +11 -0
  61. sqlmirror/golden/tsql/16_subtasks.sql +9 -0
  62. sqlmirror/golden/tsql/17_blocked_issues.sql +11 -0
  63. sqlmirror/golden/tsql/18_most_discussed.sql +9 -0
  64. sqlmirror/golden/tsql/19_assignee_workload.sql +11 -0
  65. sqlmirror/golden/tsql/20_resolutions.sql +7 -0
  66. sqlmirror/golden/tsql/21_issue_history.sql +10 -0
  67. sqlmirror/golden/tsql/22_reopened.sql +11 -0
  68. sqlmirror/golden/tsql/23_overdue.sql +8 -0
  69. sqlmirror/golden/tsql/24_cascading_select.sql +13 -0
  70. sqlmirror/golden/tsql/25_user_picker.sql +12 -0
  71. sqlmirror/golden/tsql/26_attachments.sql +9 -0
  72. sqlmirror/golden/tsql/27_project_categories.sql +8 -0
  73. sqlmirror/golden/tsql/28_issue_types.sql +7 -0
  74. sqlmirror/golden/tsql/29_estimate_accuracy.sql +11 -0
  75. sqlmirror/golden/tsql/30_project_leads.sql +7 -0
  76. sqlmirror/idmap.py +161 -0
  77. sqlmirror/jira.py +223 -0
  78. sqlmirror/mapper.py +774 -0
  79. sqlmirror/reconcile.py +73 -0
  80. sqlmirror/samples/dc_export_example.csv +19 -0
  81. sqlmirror/samples/dc_reports/mysql_sprint_report.sql +10 -0
  82. sqlmirror/samples/dc_reports/postgres_assigned_to_user.sql +6 -0
  83. sqlmirror/samples/dc_reports/postgres_customer_impact.sql +7 -0
  84. sqlmirror/samples/dc_reports/postgres_time_logged.sql +7 -0
  85. sqlmirror/samples/dc_reports/postgres_workflow_steps.sql +4 -0
  86. sqlmirror/samples/dc_reports/sqlserver_open_by_status.sql +9 -0
  87. sqlmirror/schema.py +518 -0
  88. sqlmirror/source.py +117 -0
  89. sqlmirror/sqlcheck.py +647 -0
  90. sqlmirror/util.py +80 -0
  91. sqlmirror-0.2.0.dist-info/METADATA +19 -0
  92. sqlmirror-0.2.0.dist-info/RECORD +95 -0
  93. sqlmirror-0.2.0.dist-info/WHEEL +5 -0
  94. sqlmirror-0.2.0.dist-info/entry_points.txt +3 -0
  95. sqlmirror-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,8 @@
1
+ -- Issues per project and status
2
+ -- expect: rows
3
+ SELECT p.pkey, s.pname AS status, count(*) AS issues
4
+ FROM jiraissue ji
5
+ JOIN project p ON p.id = ji.project
6
+ JOIN issuestatus s ON s.id = ji.issuestatus
7
+ GROUP BY p.pkey, s.pname
8
+ ORDER BY p.pkey, s.pname;
@@ -0,0 +1,7 @@
1
+ -- Issue keys the Data Center way (project key + issue number)
2
+ -- expect: rows
3
+ SELECT p.pkey || '-' || ji.issuenum AS issue_key, ji.summary, ji.created
4
+ FROM jiraissue ji
5
+ JOIN project p ON p.id = ji.project
6
+ ORDER BY ji.created DESC
7
+ LIMIT 50;
@@ -0,0 +1,8 @@
1
+ -- Created vs resolved per month
2
+ -- expect: rows
3
+ SELECT date_trunc('month', created) AS month,
4
+ count(*) AS created,
5
+ count(resolutiondate) AS resolved
6
+ FROM jiraissue
7
+ GROUP BY 1
8
+ ORDER BY 1;
@@ -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,9 @@
1
+ -- Most used labels (system Labels field only)
2
+ -- expect: rows
3
+ -- needs: labels
4
+ SELECT label, count(*) AS uses
5
+ FROM label
6
+ WHERE fieldid IS NULL
7
+ GROUP BY label
8
+ ORDER BY uses DESC, label
9
+ LIMIT 10;
@@ -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,7 @@
1
+ -- Resolution breakdown
2
+ -- expect: rows
3
+ SELECT coalesce(r.pname, 'Unresolved') AS resolution, count(*) AS issues
4
+ FROM jiraissue ji
5
+ LEFT JOIN resolution r ON r.id = ji.resolution
6
+ GROUP BY 1
7
+ ORDER BY 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,7 @@
1
+ -- Issues per issue type, sub-tasks flagged by pstyle
2
+ -- expect: rows
3
+ SELECT it.pname, it.pstyle, count(*) AS issues
4
+ FROM jiraissue ji
5
+ JOIN issuetype it ON it.id = ji.issuetype
6
+ GROUP BY it.pname, it.pstyle
7
+ ORDER BY issues DESC;
@@ -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,7 @@
1
+ -- Project leads
2
+ -- expect: rows
3
+ SELECT p.pkey, p.pname, u.display_name AS lead
4
+ FROM project p
5
+ LEFT JOIN app_user au ON au.user_key = p.lead
6
+ LEFT JOIN cwd_user u ON u.lower_user_name = au.lower_user_name
7
+ ORDER BY p.pkey;
@@ -0,0 +1,8 @@
1
+ -- Issues per project and status
2
+ -- expect: rows
3
+ SELECT p.pkey, s.pname AS status, COUNT(*) AS issues
4
+ FROM jiraissue ji
5
+ JOIN project p ON p.id = ji.project
6
+ JOIN issuestatus s ON s.id = ji.issuestatus
7
+ GROUP BY p.pkey, s.pname
8
+ ORDER BY p.pkey, s.pname;
@@ -0,0 +1,6 @@
1
+ -- Issue keys the Data Center way (project key + issue number)
2
+ -- expect: rows
3
+ SELECT TOP 50 p.pkey + '-' + CAST(ji.issuenum AS VARCHAR(20)) AS issue_key, ji.summary, ji.created
4
+ FROM jiraissue ji
5
+ JOIN project p ON p.id = ji.project
6
+ ORDER BY ji.created DESC;
@@ -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,8 @@
1
+ -- Most used labels (system Labels field only)
2
+ -- expect: rows
3
+ -- needs: labels
4
+ SELECT TOP 10 label, COUNT(*) AS uses
5
+ FROM label
6
+ WHERE fieldid IS NULL
7
+ GROUP BY label
8
+ ORDER BY uses DESC, label;
@@ -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,9 @@
1
+ -- Most commented issues
2
+ -- expect: rows
3
+ -- needs: comments
4
+ SELECT TOP 10 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;
@@ -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,7 @@
1
+ -- Resolution breakdown
2
+ -- expect: rows
3
+ SELECT ISNULL(r.pname, 'Unresolved') AS resolution, COUNT(*) AS issues
4
+ FROM jiraissue ji
5
+ LEFT JOIN resolution r ON r.id = ji.resolution
6
+ GROUP BY ISNULL(r.pname, 'Unresolved')
7
+ ORDER BY 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;