Commit fba1d515 authored by Jarka Košanová's avatar Jarka Košanová

Add health status data to the graphQL endpoint

- calculate epic health status based on issues
parent 0f758429
......@@ -2023,6 +2023,11 @@ type Epic implements Noteable {
"""
hasIssues: Boolean!
"""
Current health status of the epic
"""
healthStatus: EpicHealthStatus
"""
ID of the epic
"""
......@@ -2294,6 +2299,26 @@ type EpicEdge {
node: Epic
}
"""
Health status of child issues
"""
type EpicHealthStatus {
"""
Number of issues at risk
"""
issuesAtRisk: Int
"""
Number of issues that need attention
"""
issuesNeedingAttention: Int
"""
Number of issues on track
"""
issuesOnTrack: Int
}
"""
Relationship between an epic and an issue
"""
......
......@@ -5186,6 +5186,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "healthStatus",
"description": "Current health status of the epic",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "EpicHealthStatus",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "ID of the epic",
......@@ -13084,6 +13098,61 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "EpicHealthStatus",
"description": "Health status of child issues",
"fields": [
{
"name": "issuesAtRisk",
"description": "Number of issues at risk",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issuesNeedingAttention",
"description": "Number of issues that need attention",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issuesOnTrack",
"description": "Number of issues on track",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "TimelogConnection",
......
......@@ -327,6 +327,7 @@ Represents an epic.
| `group` | Group! | Group to which the epic belongs |
| `hasChildren` | Boolean! | Indicates if the epic has children |
| `hasIssues` | Boolean! | Indicates if the epic has direct issues |
| `healthStatus` | EpicHealthStatus | Current health status of the epic |
| `id` | ID! | ID of the epic |
| `iid` | ID! | Internal ID of the epic |
| `parent` | Epic | Parent epic of the epic |
......@@ -366,6 +367,16 @@ Total weight of open and closed descendant issues
| `closedIssues` | Int | Total weight of completed (closed) issues in this epic, including epic descendants |
| `openedIssues` | Int | Total weight of opened issues in this epic, including epic descendants |
## EpicHealthStatus
Health status of child issues
| Name | Type | Description |
| --- | ---- | ---------- |
| `issuesAtRisk` | Int | Number of issues at risk |
| `issuesNeedingAttention` | Int | Number of issues that need attention |
| `issuesOnTrack` | Int | Number of issues on track |
## EpicIssue
Relationship between an epic and an issue
......
# frozen_string_literal: true
module Types
# rubocop: disable Graphql/AuthorizeTypes
class EpicHealthStatusType < BaseObject
graphql_name 'EpicHealthStatus'
description 'Health status of child issues'
field :issues_on_track, GraphQL::INT_TYPE, null: true, description: 'Number of issues on track'
field :issues_needing_attention, GraphQL::INT_TYPE, null: true, description: 'Number of issues that need attention'
field :issues_at_risk, GraphQL::INT_TYPE, null: true, description: 'Number of issues at risk'
end
# rubocop: enable Graphql/AuthorizeTypes
end
......@@ -138,5 +138,11 @@ module Types
resolve: -> (epic, args, ctx) do
Gitlab::Graphql::Aggregations::Epics::LazyEpicAggregate.new(ctx, epic.id, WEIGHT_SUM)
end
field :health_status, Types::EpicHealthStatusType, null: true, complexity: 10,
description: 'Current health status of the epic',
resolve: -> (epic, args, ctx) do
Epics::DescendantCountService.new(epic, ctx[:current_user])
end
end
end
......@@ -26,6 +26,7 @@ module EE
issue_ids = EpicIssue.where(epic_id: epics).select(:issue_id)
id_in(issue_ids)
end
scope :counts_by_health_status, -> { reorder(nil).group(:health_status).count }
has_one :epic_issue
has_one :epic, through: :epic_issue
......
......@@ -31,6 +31,18 @@ module Epics
issues_count.fetch(closed_state_id, 0)
end
def issues_on_track
issue_health_statuses.fetch('on_track', 0)
end
def issues_needing_attention
issue_health_statuses.fetch('needs_attention', 0)
end
def issues_at_risk
issue_health_statuses.fetch('at_risk', 0)
end
private
def epics_count
......@@ -45,6 +57,12 @@ module Epics
end
end
def issue_health_statuses
strong_memoize(:issue_health_statuses) do
Issue.in_epics(accessible_epics).counts_by_health_status
end
end
def accessible_epics
strong_memoize(:epics) do
epic.base_and_descendants
......
---
title: Add health status data to the epic GraphQL endpoint
merge_request: 25989
author:
type: added
......@@ -11,7 +11,7 @@ describe GitlabSchema.types['Epic'] do
closed_at created_at updated_at children has_children has_issues
web_path web_url relation_path reference issues user_permissions
notes discussions relative_position subscribed participants
descendant_counts descendant_weight_sum upvotes downvotes
descendant_counts descendant_weight_sum upvotes downvotes health_status
]
end
......
......@@ -67,6 +67,16 @@ describe Issue do
end
end
describe '.counts_by_health_status' do
it 'returns counts grouped by health_status' do
create(:issue, health_status: :on_track)
create(:issue, health_status: :on_track)
create(:issue, health_status: :at_risk)
expect(Issue.counts_by_health_status).to eq({ 'on_track' => 2, 'at_risk' => 1 } )
end
end
context 'epics' do
let_it_be(:epic1) { create(:epic) }
let_it_be(:epic2) { create(:epic) }
......
......@@ -11,12 +11,13 @@ describe Epics::DescendantCountService do
let_it_be(:epic2) { create(:epic, group: subgroup, parent: parent_epic, state: :closed) }
let_it_be(:project) { create(:project, :private, group: group)}
let_it_be(:issue1) { create(:issue, project: project, state: :opened) }
let_it_be(:issue2) { create(:issue, project: project, state: :closed) }
let_it_be(:issue1) { create(:issue, project: project, state: :opened, health_status: :on_track) }
let_it_be(:issue2) { create(:issue, project: project, state: :closed, health_status: :on_track) }
let_it_be(:issue3) { create(:issue, project: project, state: :opened) }
let_it_be(:issue4) { create(:issue, project: project, state: :closed) }
let_it_be(:issue5) { create(:issue, :confidential, project: project, state: :opened) }
let_it_be(:issue6) { create(:issue, :confidential, project: project, state: :closed) }
let_it_be(:issue4) { create(:issue, project: project, state: :closed, health_status: :needs_attention) }
let_it_be(:issue5) { create(:issue, :confidential, project: project, state: :opened, health_status: :at_risk) }
let_it_be(:issue6) { create(:issue, :confidential, project: project, state: :closed, health_status: :at_risk) }
let_it_be(:epic_issue1) { create(:epic_issue, epic: parent_epic, issue: issue1) }
let_it_be(:epic_issue2) { create(:epic_issue, epic: parent_epic, issue: issue2) }
let_it_be(:epic_issue3) { create(:epic_issue, epic: epic1, issue: issue3) }
......@@ -51,4 +52,16 @@ describe Epics::DescendantCountService do
describe '#closed_issues' do
it_behaves_like 'descendants state count', :closed_issues, 3
end
describe '#issues_on_track' do
it_behaves_like 'descendants state count', :issues_on_track, 2
end
describe '#issues_needing_attention' do
it_behaves_like 'descendants state count', :issues_needing_attention, 1
end
describe '#issues_at_risk' do
it_behaves_like 'descendants state count', :issues_at_risk, 2
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment