Commit 7691d182 authored by Pedro Pombeiro's avatar Pedro Pombeiro Committed by Natalia Tepluhina

Add /health_status, /clear_health_status quick actions

parent 8bb0cff3
...@@ -639,6 +639,9 @@ You can then see the issue's status in the issues list and the epic tree. ...@@ -639,6 +639,9 @@ You can then see the issue's status in the issues list and the epic tree.
After an issue is closed, its health status can't be edited and the **Edit** button becomes disabled After an issue is closed, its health status can't be edited and the **Edit** button becomes disabled
until the issue is reopened. until the issue is reopened.
You can also set and clear health statuses using the `/health_status` and `/clear_health_status`
[quick actions](../quick_actions.md#issues-merge-requests-and-epics).
## Publish an issue **(ULTIMATE)** ## Publish an issue **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30906) in GitLab 13.1. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30906) in GitLab 13.1.
......
...@@ -48,7 +48,7 @@ threads. Some quick actions might not be available to all subscription tiers. ...@@ -48,7 +48,7 @@ threads. Some quick actions might not be available to all subscription tiers.
<!-- Keep this table sorted alphabetically --> <!-- Keep this table sorted alphabetically -->
| Command | Issue | Merge request | Epic | Action | | Command | Issue | Merge request | Epic | Action |
|:--------------------------------------|:-----------------------|:-----------------------|:-----------------------|:-------| |:-------------------------------------------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `/add_contacts email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add one or more [CRM contacts](../crm/index.md) ([introduced in GitLab 14.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73413)). | | `/add_contacts email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add one or more [CRM contacts](../crm/index.md) ([introduced in GitLab 14.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73413)). |
| `/approve` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Approve the merge request. | | `/approve` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Approve the merge request. |
| `/assign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign one or more users. | | `/assign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign one or more users. |
...@@ -57,8 +57,9 @@ threads. Some quick actions might not be available to all subscription tiers. ...@@ -57,8 +57,9 @@ threads. Some quick actions might not be available to all subscription tiers.
| `/assign_reviewer me` or `/reviewer me` or `/request_review me` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign yourself as a reviewer. | | `/assign_reviewer me` or `/reviewer me` or `/request_review me` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign yourself as a reviewer. |
| `/award :emoji:` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Toggle emoji award. | | `/award :emoji:` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Toggle emoji award. |
| `/child_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Add child epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/-/issues/7330)). | | `/child_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Add child epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/-/issues/7330)). |
| `/clear_health_status` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear [health status](issues/managing_issues.md#health-status) ([introduced in GitLab 14.7](https://gitlab.com/gitlab-org/gitlab/-/issues/213814)). |
| `/clear_weight` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear weight. | | `/clear_weight` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear weight. |
| `/clone <path/to/project> [--with_notes]`| **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clone the issue to given project, or the current one if no arguments are given ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9421) in GitLab 13.7). Copies as much data as possible as long as the target project contains equivalent labels, milestones, and so on. Does not copy comments or system notes unless `--with_notes` is provided as an argument. | | `/clone <path/to/project> [--with_notes]` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clone the issue to given project, or the current one if no arguments are given ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9421) in GitLab 13.7). Copies as much data as possible as long as the target project contains equivalent labels, milestones, and so on. Does not copy comments or system notes unless `--with_notes` is provided as an argument. |
| `/close` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Close. | | `/close` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Close. |
| `/confidential` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Make confidential. | | `/confidential` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Make confidential. |
| `/copy_metadata <!merge_request>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Copy labels and milestone from another merge request in the project. | | `/copy_metadata <!merge_request>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Copy labels and milestone from another merge request in the project. |
...@@ -70,6 +71,7 @@ threads. Some quick actions might not be available to all subscription tiers. ...@@ -70,6 +71,7 @@ threads. Some quick actions might not be available to all subscription tiers.
| `/duplicate <#issue>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Close this issue and mark as a duplicate of another issue. **(FREE)** Also, mark both as related. | | `/duplicate <#issue>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Close this issue and mark as a duplicate of another issue. **(FREE)** Also, mark both as related. |
| `/epic <epic>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add to epic `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. | | `/epic <epic>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add to epic `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. |
| `/estimate <time>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set time estimate. For example, `/estimate 1mo 2w 3d 4h 5m`. Learn more about [time tracking](time_tracking.md). | | `/estimate <time>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set time estimate. For example, `/estimate 1mo 2w 3d 4h 5m`. Learn more about [time tracking](time_tracking.md). |
| `/health_status <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set [health status](issues/managing_issues.md#health-status). Valid options for `<value>` are `on_track`, `needs_attention`, and `at_risk` ([introduced in GitLab 14.7](https://gitlab.com/gitlab-org/gitlab/-/issues/213814)). |
| `/invite_email email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add up to six email participants. This action is behind feature flag `issue_email_participants` and is not yet supported in issue templates. | | `/invite_email email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add up to six email participants. This action is behind feature flag `issue_email_participants` and is not yet supported in issue templates. |
| `/iteration *iteration:"iteration name"` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set iteration. For example, to set the `Late in July` iteration: `/iteration *iteration:"Late in July"` ([introduced in GitLab 13.1](https://gitlab.com/gitlab-org/gitlab/-/issues/196795)). | | `/iteration *iteration:"iteration name"` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set iteration. For example, to set the `Late in July` iteration: `/iteration *iteration:"Late in July"` ([introduced in GitLab 13.1](https://gitlab.com/gitlab-org/gitlab/-/issues/196795)). |
| `/label ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add one or more labels. Label names can also start without a tilde (`~`), but mixed syntax is not supported. | | `/label ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add one or more labels. Label names can also start without a tilde (`~`), but mixed syntax is not supported. |
...@@ -106,7 +108,7 @@ threads. Some quick actions might not be available to all subscription tiers. ...@@ -106,7 +108,7 @@ threads. Some quick actions might not be available to all subscription tiers.
| `/target_branch <local branch name>` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Set target branch. | | `/target_branch <local branch name>` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Set target branch. |
| `/title <new title>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Change title. | | `/title <new title>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Change title. |
| `/todo` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add a to-do item. | | `/todo` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add a to-do item. |
| `/unapprove` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Unapprove the merge request. ([introduced in GitLab 14.3](https://gitlab.com/gitlab-org/gitlab/-/issues/8103)| | `/unapprove` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Unapprove the merge request. ([introduced in GitLab 14.3](https://gitlab.com/gitlab-org/gitlab/-/issues/8103) |
| `/unassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove specific assignees. | | `/unassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove specific assignees. |
| `/unassign` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove all assignees. | | `/unassign` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove all assignees. |
| `/unassign_reviewer @user1 @user2` or `/remove_reviewer @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove specific reviewers. | | `/unassign_reviewer @user1 @user2` or `/remove_reviewer @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove specific reviewers. |
......
---
key_path: redis_hll_counters.quickactions.i_quickactions_clear_health_status_monthly
name: "count_clear_health_status_quick_actions_monthly"
description: Count of MAU using the `/clear_health_status` quick action
product_section: dev
product_stage: plan
product_group: group::project management
product_category: issue_tracking
value_type: number
status: active
milestone: "14.7"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77215
time_frame: 28d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
options:
events:
- i_quickactions_clear_health_status
performance_indicator_type: []
distribution:
- ee
tier:
- ultimate
---
key_path: redis_hll_counters.quickactions.i_quickactions_health_status_monthly
name: "count_health_status_quick_actions_monthly"
description: Count of MAU using the `/health_status` quick action
product_section: dev
product_stage: plan
product_group: group::project management
product_category: issue_tracking
value_type: number
status: active
milestone: "14.7"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77215
time_frame: 28d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
options:
events:
- i_quickactions_health_status
performance_indicator_type: []
distribution:
- ee
tier:
- ultimate
---
key_path: redis_hll_counters.quickactions.i_quickactions_health_status_weekly
name: "count_health_status_quick_actions_weekly"
description: Count of WAU using the `/health_status` quick action
product_section: dev
product_stage: plan
product_group: group::project management
product_category: issue_tracking
value_type: number
status: active
milestone: "14.7"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77215
time_frame: 7d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
options:
events:
- i_quickactions_health_status
performance_indicator_type: []
distribution:
- ee
tier:
- ultimate
---
key_path: redis_hll_counters.quickactions.i_quickactions_clear_health_status_weekly
name: "count_clear_health_status_quick_actions_weekly"
description: Count of WAU using the `/clear_health_status` quick action
product_section: dev
product_stage: plan
product_group: group::project management
product_category: issue_tracking
value_type: number
status: active
milestone: "14.7"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77215
time_frame: 7d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
options:
events:
- i_quickactions_clear_health_status
performance_indicator_type: []
distribution:
- ee
tier:
- ultimate
...@@ -136,6 +136,52 @@ module EE ...@@ -136,6 +136,52 @@ module EE
@execution_message[:publish] = _('Failed to publish issue on status page.') @execution_message[:publish] = _('Failed to publish issue on status page.')
end end
end end
desc _('Set health status')
explanation do |health_status|
_("Sets health status to %{health_status}.") % { health_status: health_status } if health_status
end
params "<#{::Issue.health_statuses.keys.join('|')}>"
types Issue
condition do
quick_action_target.supports_health_status? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
end
parse_params do |health_status|
find_health_status(health_status)
end
command :health_status do |health_status|
if health_status
@updates[:health_status] = health_status
@execution_message[:health_status] = _("Set health status to %{health_status}.") % { health_status: health_status }
end
end
desc _('Clear health status')
explanation _('Clears health status.')
execution_message _('Cleared health status.')
types Issue
condition do
quick_action_target.persisted? &&
quick_action_target.supports_health_status? &&
quick_action_target.health_status &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
end
command :clear_health_status do
@updates[:health_status] = nil
end
end
private
def find_health_status(health_status_param)
return unless health_status_param
health_status_param = health_status_param.downcase
health_statuses = ::Issue.health_statuses.keys.map(&:downcase)
health_statuses.include?(health_status_param) && health_status_param
end end
end end
end end
......
...@@ -1043,6 +1043,127 @@ RSpec.describe QuickActions::InterpretService do ...@@ -1043,6 +1043,127 @@ RSpec.describe QuickActions::InterpretService do
end end
end end
shared_examples 'health_status command' do
it 'populates health_status specified by the /health_status command' do
_, updates = service.execute(content, issuable)
expect(updates).to eq(health_status: health_status)
end
end
shared_examples 'clear_health_status command' do
it 'populates health_status: nil if content contains /clear_health_status' do
issuable.update!(health_status: 'on_track')
_, updates = service.execute(content, issuable)
expect(updates).to eq(health_status: nil)
end
end
context 'issuable health statuses licensed' do
let(:issuable) { issue }
before do
stub_licensed_features(issuable_health_status: true)
end
context 'health_status' do
let(:content) { "/health_status #{health_status}" }
it_behaves_like 'health_status command' do
let(:health_status) { 'on_track' }
end
it_behaves_like 'health_status command' do
let(:health_status) { 'at_risk' }
end
context 'when health_status is invalid' do
it 'does not populate health_status' do
content = "/health_status unknown"
_, updates = service.execute(content, issuable)
expect(updates).to be_empty
end
end
context 'when the user does not have enough permissions' do
before do
allow(current_user).to receive(:can?).with(:use_quick_actions).and_return(true)
allow(current_user).to receive(:can?).with(:admin_issue, issuable).and_return(false)
end
it 'returns an error message' do
content = "/health_status on_track"
_, updates, message = service.execute(content, issuable)
expect(updates).to be_empty
expect(message).to eq('Could not apply health_status command.')
end
end
end
context 'clear_health_status' do
it_behaves_like 'clear_health_status command' do
let(:content) { '/clear_health_status' }
end
context 'when the user does not have enough permissions' do
before do
allow(current_user).to receive(:can?).with(:use_quick_actions).and_return(true)
allow(current_user).to receive(:can?).with(:admin_issue, issuable).and_return(false)
end
it 'returns an error message' do
content = "/clear_health_status"
_, updates, message = service.execute(content, issuable)
expect(updates).to be_empty
expect(message).to eq('Could not apply clear_health_status command.')
end
end
end
end
context 'issuable health_status unlicensed' do
before do
stub_licensed_features(issuable_health_status: false)
end
it 'does not recognise /health_status X' do
_, updates = service.execute('/health_status needs_attention', issue)
expect(updates).to be_empty
end
it 'does not recognise /clear_health_status' do
_, updates = service.execute('/clear_health_status', issue)
expect(updates).to be_empty
end
end
context 'issuable health_status not supported by type' do
let_it_be(:incident) { create(:incident, project: project) }
before do
stub_licensed_features(issuable_health_status: true)
end
it 'does not recognise /health_status X' do
_, updates = service.execute('/health_status on_track', incident)
expect(updates).to be_empty
end
it 'does not recognise /clear_health_status' do
_, updates = service.execute('/clear_health_status', incident)
expect(updates).to be_empty
end
end
shared_examples 'empty command' do shared_examples 'empty command' do
it 'populates {} if content contains an unsupported command' do it 'populates {} if content contains an unsupported command' do
_, updates = service.execute(content, issuable) _, updates = service.execute(content, issuable)
...@@ -1095,6 +1216,21 @@ RSpec.describe QuickActions::InterpretService do ...@@ -1095,6 +1216,21 @@ RSpec.describe QuickActions::InterpretService do
end end
describe '#explain' do describe '#explain' do
describe 'health_status command' do
let(:content) { '/health_status on_track' }
context 'issuable health statuses licensed' do
before do
stub_licensed_features(issuable_health_status: true)
end
it 'includes the value' do
_, explanations = service.explain(content, issue)
expect(explanations).to eq(['Sets health status to on_track.'])
end
end
end
describe 'unassign command' do describe 'unassign command' do
let(:content) { '/unassign' } let(:content) { '/unassign' }
let(:issue) { create(:issue, project: project, assignees: [user, user2]) } let(:issue) { create(:issue, project: project, assignees: [user, user2]) }
......
...@@ -39,6 +39,10 @@ ...@@ -39,6 +39,10 @@
category: quickactions category: quickactions
redis_slot: quickactions redis_slot: quickactions
aggregation: weekly aggregation: weekly
- name: i_quickactions_clear_health_status
category: quickactions
redis_slot: quickactions
aggregation: weekly
- name: i_quickactions_clone - name: i_quickactions_clone
category: quickactions category: quickactions
redis_slot: quickactions redis_slot: quickactions
...@@ -263,6 +267,10 @@ ...@@ -263,6 +267,10 @@
category: quickactions category: quickactions
redis_slot: quickactions redis_slot: quickactions
aggregation: weekly aggregation: weekly
- name: i_quickactions_health_status
category: quickactions
redis_slot: quickactions
aggregation: weekly
- name: i_quickactions_wip - name: i_quickactions_wip
category: quickactions category: quickactions
redis_slot: quickactions redis_slot: quickactions
......
...@@ -7326,6 +7326,9 @@ msgstr "" ...@@ -7326,6 +7326,9 @@ msgstr ""
msgid "Clear due date" msgid "Clear due date"
msgstr "" msgstr ""
msgid "Clear health status"
msgstr ""
msgid "Clear recent searches" msgid "Clear recent searches"
msgstr "" msgstr ""
...@@ -7344,9 +7347,15 @@ msgstr "" ...@@ -7344,9 +7347,15 @@ msgstr ""
msgid "Clear weight" msgid "Clear weight"
msgstr "" msgstr ""
msgid "Cleared health status."
msgstr ""
msgid "Cleared weight." msgid "Cleared weight."
msgstr "" msgstr ""
msgid "Clears health status."
msgstr ""
msgid "Clears weight." msgid "Clears weight."
msgstr "" msgstr ""
...@@ -32275,6 +32284,12 @@ msgstr "" ...@@ -32275,6 +32284,12 @@ msgstr ""
msgid "Set due date" msgid "Set due date"
msgstr "" msgstr ""
msgid "Set health status"
msgstr ""
msgid "Set health status to %{health_status}."
msgstr ""
msgid "Set iteration" msgid "Set iteration"
msgstr "" msgstr ""
...@@ -32431,6 +32446,9 @@ msgstr "" ...@@ -32431,6 +32446,9 @@ msgstr ""
msgid "Sets %{epic_ref} as parent epic." msgid "Sets %{epic_ref} as parent epic."
msgstr "" msgstr ""
msgid "Sets health status to %{health_status}."
msgstr ""
msgid "Sets target branch to %{branch_name}." msgid "Sets target branch to %{branch_name}."
msgstr "" msgstr ""
......
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