Commit 0b9fe3bc authored by Phil Hughes's avatar Phil Hughes

Refactor assignee and reviewer state to allow nil values

Refactors all the `attention_required` variable names to be
`attention_requested` to better match the feature name.

Changes the AttentionRequest service to be a ToggleAttentionRequested
service which will toggle between `attention_requested` and `nil`

This also hooks it up all on the frontend so that the assignees
and reviewers in the sidebar send the correct GraphQL mutation
when the attention request button is clicked.

Changelog: changed

Closes https://gitlab.com/gitlab-org/gitlab/-/issues/343325
parent 46f371f8
...@@ -39,8 +39,8 @@ export default { ...@@ -39,8 +39,8 @@ export default {
assignSelf() { assignSelf() {
this.$emit('assign-self'); this.$emit('assign-self');
}, },
toggleAttentionRequired(data) { toggleAttentionRequested(data) {
this.$emit('toggle-attention-required', data); this.$emit('toggle-attention-requested', data);
}, },
}, },
}; };
...@@ -65,7 +65,7 @@ export default { ...@@ -65,7 +65,7 @@ export default {
v-else v-else
:users="sortedAssigness" :users="sortedAssigness"
:issuable-type="issuableType" :issuable-type="issuableType"
@toggle-attention-required="toggleAttentionRequired" @toggle-attention-requested="toggleAttentionRequested"
/> />
</div> </div>
</div> </div>
......
...@@ -33,8 +33,8 @@ export default { ...@@ -33,8 +33,8 @@ export default {
}, },
}, },
methods: { methods: {
toggleAttentionRequired(data) { toggleAttentionRequested(data) {
this.$emit('toggle-attention-required', data); this.$emit('toggle-attention-requested', data);
}, },
}, },
}; };
...@@ -66,7 +66,7 @@ export default { ...@@ -66,7 +66,7 @@ export default {
:users="users" :users="users"
:issuable-type="issuableType" :issuable-type="issuableType"
class="gl-text-gray-800 gl-mt-2 hide-collapsed" class="gl-text-gray-800 gl-mt-2 hide-collapsed"
@toggle-attention-required="toggleAttentionRequired" @toggle-attention-requested="toggleAttentionRequested"
/> />
</div> </div>
</template> </template>
...@@ -125,8 +125,8 @@ export default { ...@@ -125,8 +125,8 @@ export default {
availability: this.assigneeAvailabilityStatus[username] || '', availability: this.assigneeAvailabilityStatus[username] || '',
})); }));
}, },
toggleAttentionRequired(data) { toggleAttentionRequested(data) {
this.mediator.toggleAttentionRequired('assignee', data); this.mediator.toggleAttentionRequested('assignee', data);
}, },
}, },
}; };
...@@ -155,7 +155,7 @@ export default { ...@@ -155,7 +155,7 @@ export default {
:editable="store.editable" :editable="store.editable"
:issuable-type="issuableType" :issuable-type="issuableType"
@assign-self="assignSelf" @assign-self="assignSelf"
@toggle-attention-required="toggleAttentionRequired" @toggle-attention-requested="toggleAttentionRequested"
/> />
</div> </div>
</template> </template>
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { IssuableType } from '~/issue_show/constants'; import { IssuableType } from '~/issue_show/constants';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import AttentionRequiredToggle from '../attention_required_toggle.vue'; import AttentionRequestedToggle from '../attention_requested_toggle.vue';
import AssigneeAvatarLink from './assignee_avatar_link.vue'; import AssigneeAvatarLink from './assignee_avatar_link.vue';
import UserNameWithStatus from './user_name_with_status.vue'; import UserNameWithStatus from './user_name_with_status.vue';
...@@ -10,7 +10,7 @@ const DEFAULT_RENDER_COUNT = 5; ...@@ -10,7 +10,7 @@ const DEFAULT_RENDER_COUNT = 5;
export default { export default {
components: { components: {
AttentionRequiredToggle, AttentionRequestedToggle,
AssigneeAvatarLink, AssigneeAvatarLink,
UserNameWithStatus, UserNameWithStatus,
}, },
...@@ -82,8 +82,8 @@ export default { ...@@ -82,8 +82,8 @@ export default {
} }
return u?.status?.availability || ''; return u?.status?.availability || '';
}, },
toggleAttentionRequired(data) { toggleAttentionRequested(data) {
this.$emit('toggle-attention-required', data); this.$emit('toggle-attention-requested', data);
}, },
}, },
}; };
...@@ -113,11 +113,11 @@ export default { ...@@ -113,11 +113,11 @@ export default {
}" }"
class="gl-display-inline-block" class="gl-display-inline-block"
> >
<attention-required-toggle <attention-requested-toggle
v-if="showVerticalList && user.can_update_merge_request" v-if="showVerticalList && user.can_update_merge_request"
:user="user" :user="user"
type="assignee" type="assignee"
@toggle-attention-required="toggleAttentionRequired" @toggle-attention-requested="toggleAttentionRequested"
/> />
<assignee-avatar-link <assignee-avatar-link
:user="user" :user="user"
......
...@@ -5,9 +5,9 @@ import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; ...@@ -5,9 +5,9 @@ import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
export default { export default {
i18n: { i18n: {
attentionRequiredReviewer: __('Request attention to review'), attentionRequestedReviewer: __('Request attention to review'),
attentionRequiredAssignee: __('Request attention'), attentionRequestedAssignee: __('Request attention'),
removeAttentionRequired: __('Remove attention request'), removeAttentionRequested: __('Remove attention request'),
}, },
components: { components: {
GlButton, GlButton,
...@@ -32,13 +32,13 @@ export default { ...@@ -32,13 +32,13 @@ export default {
}, },
computed: { computed: {
tooltipTitle() { tooltipTitle() {
if (this.user.attention_required) { if (this.user.attention_requested) {
return this.$options.i18n.removeAttentionRequired; return this.$options.i18n.removeAttentionRequested;
} }
return this.type === 'reviewer' return this.type === 'reviewer'
? this.$options.i18n.attentionRequiredReviewer ? this.$options.i18n.attentionRequestedReviewer
: this.$options.i18n.attentionRequiredAssignee; : this.$options.i18n.attentionRequestedAssignee;
}, },
}, },
methods: { methods: {
...@@ -47,7 +47,7 @@ export default { ...@@ -47,7 +47,7 @@ export default {
this.$root.$emit(BV_HIDE_TOOLTIP); this.$root.$emit(BV_HIDE_TOOLTIP);
this.loading = true; this.loading = true;
this.$emit('toggle-attention-required', { this.$emit('toggle-attention-requested', {
user: this.user, user: this.user,
callback: this.toggleAttentionRequiredComplete, callback: this.toggleAttentionRequiredComplete,
}); });
...@@ -63,8 +63,8 @@ export default { ...@@ -63,8 +63,8 @@ export default {
<span v-gl-tooltip.left.viewport="tooltipTitle"> <span v-gl-tooltip.left.viewport="tooltipTitle">
<gl-button <gl-button
:loading="loading" :loading="loading"
:variant="user.attention_required ? 'warning' : 'default'" :variant="user.attention_requested ? 'warning' : 'default'"
:icon="user.attention_required ? 'star' : 'star-o'" :icon="user.attention_requested ? 'star' : 'star-o'"
:aria-label="tooltipTitle" :aria-label="tooltipTitle"
size="small" size="small"
category="tertiary" category="tertiary"
......
...@@ -49,8 +49,8 @@ export default { ...@@ -49,8 +49,8 @@ export default {
requestReview(data) { requestReview(data) {
this.$emit('request-review', data); this.$emit('request-review', data);
}, },
toggleAttentionRequired(data) { toggleAttentionRequested(data) {
this.$emit('toggle-attention-required', data); this.$emit('toggle-attention-requested', data);
}, },
}, },
}; };
...@@ -73,7 +73,7 @@ export default { ...@@ -73,7 +73,7 @@ export default {
:root-path="rootPath" :root-path="rootPath"
:issuable-type="issuableType" :issuable-type="issuableType"
@request-review="requestReview" @request-review="requestReview"
@toggle-attention-required="toggleAttentionRequired" @toggle-attention-requested="toggleAttentionRequested"
/> />
</div> </div>
</div> </div>
......
...@@ -88,8 +88,8 @@ export default { ...@@ -88,8 +88,8 @@ export default {
requestReview(data) { requestReview(data) {
this.mediator.requestReview(data); this.mediator.requestReview(data);
}, },
toggleAttentionRequired(data) { toggleAttentionRequested(data) {
this.mediator.toggleAttentionRequired('reviewer', data); this.mediator.toggleAttentionRequested('reviewer', data);
}, },
}, },
}; };
...@@ -109,7 +109,7 @@ export default { ...@@ -109,7 +109,7 @@ export default {
:editable="store.editable" :editable="store.editable"
:issuable-type="issuableType" :issuable-type="issuableType"
@request-review="requestReview" @request-review="requestReview"
@toggle-attention-required="toggleAttentionRequired" @toggle-attention-requested="toggleAttentionRequested"
/> />
</div> </div>
</template> </template>
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui'; import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { __, sprintf, s__ } from '~/locale'; import { __, sprintf, s__ } from '~/locale';
import AttentionRequiredToggle from '../attention_required_toggle.vue'; import AttentionRequestedToggle from '../attention_requested_toggle.vue';
import ReviewerAvatarLink from './reviewer_avatar_link.vue'; import ReviewerAvatarLink from './reviewer_avatar_link.vue';
const LOADING_STATE = 'loading'; const LOADING_STATE = 'loading';
...@@ -16,7 +16,7 @@ export default { ...@@ -16,7 +16,7 @@ export default {
GlButton, GlButton,
GlIcon, GlIcon,
ReviewerAvatarLink, ReviewerAvatarLink,
AttentionRequiredToggle, AttentionRequestedToggle,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -80,8 +80,8 @@ export default { ...@@ -80,8 +80,8 @@ export default {
this.loadingStates[userId] = null; this.loadingStates[userId] = null;
} }
}, },
toggleAttentionRequired(data) { toggleAttentionRequested(data) {
this.$emit('toggle-attention-required', data); this.$emit('toggle-attention-requested', data);
}, },
}, },
LOADING_STATE, LOADING_STATE,
...@@ -97,11 +97,11 @@ export default { ...@@ -97,11 +97,11 @@ export default {
:class="{ 'gl-mb-3': index !== users.length - 1 }" :class="{ 'gl-mb-3': index !== users.length - 1 }"
data-testid="reviewer" data-testid="reviewer"
> >
<attention-required-toggle <attention-requested-toggle
v-if="glFeatures.mrAttentionRequests && user.can_update_merge_request" v-if="glFeatures.mrAttentionRequests && user.can_update_merge_request"
:user="user" :user="user"
type="reviewer" type="reviewer"
@toggle-attention-required="toggleAttentionRequired" @toggle-attention-requested="toggleAttentionRequested"
/> />
<reviewer-avatar-link :user="user" :root-path="rootPath" :issuable-type="issuableType"> <reviewer-avatar-link :user="user" :root-path="rootPath" :issuable-type="issuableType">
<div class="gl-ml-3 gl-line-height-normal gl-display-grid"> <div class="gl-ml-3 gl-line-height-normal gl-display-grid">
......
mutation mergeRequestAttentionRequired($projectPath: ID!, $iid: String!, $userId: ID!) {
mergeRequestAttentionRequired(input: { projectPath: $projectPath, iid: $iid, userId: $userId }) {
errors
}
}
mutation mergeRequestToggleAttentionRequested($projectPath: ID!, $iid: String!, $userId: ID!) {
mergeRequestToggleAttentionRequested(
input: { projectPath: $projectPath, iid: $iid, userId: $userId }
) {
errors
}
}
...@@ -5,7 +5,7 @@ import createGqClient, { fetchPolicies } from '~/lib/graphql'; ...@@ -5,7 +5,7 @@ import createGqClient, { fetchPolicies } from '~/lib/graphql';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import reviewerRereviewMutation from '../queries/reviewer_rereview.mutation.graphql'; import reviewerRereviewMutation from '../queries/reviewer_rereview.mutation.graphql';
import sidebarDetailsMRQuery from '../queries/sidebarDetailsMR.query.graphql'; import sidebarDetailsMRQuery from '../queries/sidebarDetailsMR.query.graphql';
import attentionRequiredMutation from '../queries/attention_required.mutation.graphql'; import toggleAttentionRequestedMutation from '../queries/toggle_attention_requested.mutation.graphql';
const queries = { const queries = {
merge_request: sidebarDetailsMRQuery, merge_request: sidebarDetailsMRQuery,
...@@ -92,9 +92,9 @@ export default class SidebarService { ...@@ -92,9 +92,9 @@ export default class SidebarService {
}); });
} }
attentionRequired(userId) { toggleAttentionRequested(userId) {
return gqClient.mutate({ return gqClient.mutate({
mutation: attentionRequiredMutation, mutation: toggleAttentionRequestedMutation,
variables: { variables: {
userId: convertToGraphQLId(TYPE_USER, `${userId}`), userId: convertToGraphQLId(TYPE_USER, `${userId}`),
projectPath: this.fullPath, projectPath: this.fullPath,
......
...@@ -63,30 +63,27 @@ export default class SidebarMediator { ...@@ -63,30 +63,27 @@ export default class SidebarMediator {
.catch(() => callback(userId, false)); .catch(() => callback(userId, false));
} }
async toggleAttentionRequired(type, { user, callback }) { async toggleAttentionRequested(type, { user, callback }) {
try { try {
const isReviewer = type === 'reviewer'; const isReviewer = type === 'reviewer';
const reviewerOrAssignee = isReviewer const reviewerOrAssignee = isReviewer
? this.store.findReviewer(user) ? this.store.findReviewer(user)
: this.store.findAssignee(user); : this.store.findAssignee(user);
if (reviewerOrAssignee.attention_required) { await this.service.toggleAttentionRequested(user.id);
if (reviewerOrAssignee.attention_requested) {
toast( toast(
sprintf(__('Removed attention request from @%{username}'), { sprintf(__('Removed attention request from @%{username}'), {
username: user.username, username: user.username,
}), }),
); );
} else { } else {
await this.service.attentionRequired(user.id);
toast(sprintf(__('Requested attention from @%{username}'), { username: user.username })); toast(sprintf(__('Requested attention from @%{username}'), { username: user.username }));
} }
if (isReviewer) { this.store.updateReviewer(user.id, 'attention_requested');
this.store.updateReviewer(user.id, 'attention_required'); this.store.updateAssignee(user.id, 'attention_requested');
} else {
this.store.updateAssignee(user.id, 'attention_required');
}
callback(); callback();
} catch (error) { } catch (error) {
......
...@@ -2,20 +2,20 @@ ...@@ -2,20 +2,20 @@
module Mutations module Mutations
module MergeRequests module MergeRequests
class AttentionRequired < Base class ToggleAttentionRequested < Base
graphql_name 'MergeRequestAttentionRequired' graphql_name 'MergeRequestToggleAttentionRequested'
argument :user_id, ::Types::GlobalIDType[::User], argument :user_id, ::Types::GlobalIDType[::User],
loads: Types::UserType, loads: Types::UserType,
required: true, required: true,
description: <<~DESC description: <<~DESC
User ID for the user that has their attention requested. User ID for the user to toggle attention requested.
DESC DESC
def resolve(project_path:, iid:, user:) def resolve(project_path:, iid:, user:)
merge_request = authorized_find!(project_path: project_path, iid: iid) merge_request = authorized_find!(project_path: project_path, iid: iid)
result = ::MergeRequests::AttentionRequiredService.new(project: merge_request.project, current_user: current_user, merge_request: merge_request, user: user).execute result = ::MergeRequests::ToggleAttentionRequestedService.new(project: merge_request.project, current_user: current_user, merge_request: merge_request, user: user).execute
{ {
merge_request: merge_request, merge_request: merge_request,
......
...@@ -68,7 +68,7 @@ module Types ...@@ -68,7 +68,7 @@ module Types
mount_mutation Mutations::MergeRequests::SetDraft, calls_gitaly: true mount_mutation Mutations::MergeRequests::SetDraft, calls_gitaly: true
mount_mutation Mutations::MergeRequests::SetAssignees mount_mutation Mutations::MergeRequests::SetAssignees
mount_mutation Mutations::MergeRequests::ReviewerRereview mount_mutation Mutations::MergeRequests::ReviewerRereview
mount_mutation Mutations::MergeRequests::AttentionRequired, feature_flag: :mr_attention_requests mount_mutation Mutations::MergeRequests::ToggleAttentionRequested, feature_flag: :mr_attention_requests
mount_mutation Mutations::Metrics::Dashboard::Annotations::Create mount_mutation Mutations::Metrics::Dashboard::Annotations::Create
mount_mutation Mutations::Metrics::Dashboard::Annotations::Delete mount_mutation Mutations::Metrics::Dashboard::Annotations::Delete
mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true
......
...@@ -24,7 +24,7 @@ module TodosHelper ...@@ -24,7 +24,7 @@ module TodosHelper
when Todo::UNMERGEABLE then 'Could not merge' when Todo::UNMERGEABLE then 'Could not merge'
when Todo::DIRECTLY_ADDRESSED then "directly addressed #{todo_action_subject(todo)} on" when Todo::DIRECTLY_ADDRESSED then "directly addressed #{todo_action_subject(todo)} on"
when Todo::MERGE_TRAIN_REMOVED then "Removed from Merge Train:" when Todo::MERGE_TRAIN_REMOVED then "Removed from Merge Train:"
when Todo::ATTENTION_REQUIRED then 'requested your attention on' when Todo::ATTENTION_REQUESTED then 'requested your attention on'
end end
end end
......
...@@ -7,7 +7,7 @@ module MergeRequestReviewerState ...@@ -7,7 +7,7 @@ module MergeRequestReviewerState
enum state: { enum state: {
unreviewed: 0, unreviewed: 0,
reviewed: 1, reviewed: 1,
attention_required: 2 attention_requested: 2
} }
validates :state, validates :state,
...@@ -18,7 +18,7 @@ module MergeRequestReviewerState ...@@ -18,7 +18,7 @@ module MergeRequestReviewerState
def set_state def set_state
if Feature.enabled?(:mr_attention_requests, self.merge_request&.project, default_enabled: :yaml) if Feature.enabled?(:mr_attention_requests, self.merge_request&.project, default_enabled: :yaml)
self.state = :attention_required self.state = :attention_requested
end end
end end
end end
......
...@@ -1945,7 +1945,7 @@ class MergeRequest < ApplicationRecord ...@@ -1945,7 +1945,7 @@ class MergeRequest < ApplicationRecord
end end
end end
def attention_required_enabled? def attention_requested_enabled?
Feature.enabled?(:mr_attention_requests, project, default_enabled: :yaml) Feature.enabled?(:mr_attention_requests, project, default_enabled: :yaml)
end end
......
...@@ -18,7 +18,7 @@ class Todo < ApplicationRecord ...@@ -18,7 +18,7 @@ class Todo < ApplicationRecord
DIRECTLY_ADDRESSED = 7 DIRECTLY_ADDRESSED = 7
MERGE_TRAIN_REMOVED = 8 # This is an EE-only feature MERGE_TRAIN_REMOVED = 8 # This is an EE-only feature
REVIEW_REQUESTED = 9 REVIEW_REQUESTED = 9
ATTENTION_REQUIRED = 10 ATTENTION_REQUESTED = 10
ACTION_NAMES = { ACTION_NAMES = {
ASSIGNED => :assigned, ASSIGNED => :assigned,
...@@ -30,7 +30,7 @@ class Todo < ApplicationRecord ...@@ -30,7 +30,7 @@ class Todo < ApplicationRecord
UNMERGEABLE => :unmergeable, UNMERGEABLE => :unmergeable,
DIRECTLY_ADDRESSED => :directly_addressed, DIRECTLY_ADDRESSED => :directly_addressed,
MERGE_TRAIN_REMOVED => :merge_train_removed, MERGE_TRAIN_REMOVED => :merge_train_removed,
ATTENTION_REQUIRED => :attention_required ATTENTION_REQUESTED => :attention_requested
}.freeze }.freeze
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
...@@ -191,8 +191,8 @@ class Todo < ApplicationRecord ...@@ -191,8 +191,8 @@ class Todo < ApplicationRecord
action == REVIEW_REQUESTED action == REVIEW_REQUESTED
end end
def attention_required? def attention_requested?
action == ATTENTION_REQUIRED action == ATTENTION_REQUESTED
end end
def merge_train_removed? def merge_train_removed?
......
...@@ -20,8 +20,8 @@ class MergeRequestUserEntity < ::API::Entities::UserBasic ...@@ -20,8 +20,8 @@ class MergeRequestUserEntity < ::API::Entities::UserBasic
find_reviewer_or_assignee(user, options)&.reviewed? find_reviewer_or_assignee(user, options)&.reviewed?
end end
expose :attention_required, if: satisfies(:present?, :allows_reviewers?, :attention_required_enabled?) do |user, options| expose :attention_requested, if: satisfies(:present?, :allows_reviewers?, :attention_requested_enabled?) do |user, options|
find_reviewer_or_assignee(user, options)&.attention_required? find_reviewer_or_assignee(user, options)&.attention_requested?
end end
expose :approved, if: satisfies(:present?) do |user, options| expose :approved, if: satisfies(:present?) do |user, options|
......
# frozen_string_literal: true # frozen_string_literal: true
module MergeRequests module MergeRequests
class AttentionRequiredService < MergeRequests::BaseService class ToggleAttentionRequestedService < MergeRequests::BaseService
attr_accessor :merge_request, :user attr_accessor :merge_request, :user
def initialize(project:, current_user:, merge_request:, user:) def initialize(project:, current_user:, merge_request:, user:)
...@@ -15,10 +15,12 @@ module MergeRequests ...@@ -15,10 +15,12 @@ module MergeRequests
return error("Invalid permissions") unless can?(current_user, :update_merge_request, merge_request) return error("Invalid permissions") unless can?(current_user, :update_merge_request, merge_request)
if reviewer || assignee if reviewer || assignee
reviewer&.update(state: :attention_required) update_state(reviewer)
assignee&.update(state: :attention_required) update_state(assignee)
notity_user if reviewer&.attention_requested? || assignee&.attention_requested?
notity_user
end
success success
else else
...@@ -29,7 +31,7 @@ module MergeRequests ...@@ -29,7 +31,7 @@ module MergeRequests
private private
def notity_user def notity_user
todo_service.create_attention_required_todo(merge_request, current_user, user) todo_service.create_attention_requested_todo(merge_request, current_user, user)
end end
def assignee def assignee
...@@ -39,5 +41,9 @@ module MergeRequests ...@@ -39,5 +41,9 @@ module MergeRequests
def reviewer def reviewer
merge_request.find_reviewer(user) merge_request.find_reviewer(user)
end end
def update_state(reviewer_or_assignee)
reviewer_or_assignee&.update(state: reviewer_or_assignee&.attention_requested? ? :reviewed : :attention_requested)
end
end end
end end
...@@ -217,8 +217,8 @@ class TodoService ...@@ -217,8 +217,8 @@ class TodoService
create_todos(reviewers, attributes) create_todos(reviewers, attributes)
end end
def create_attention_required_todo(target, author, users) def create_attention_requested_todo(target, author, users)
attributes = attributes_for_todo(target.project, target, author, Todo::ATTENTION_REQUIRED) attributes = attributes_for_todo(target.project, target, author, Todo::ATTENTION_REQUESTED)
create_todos(users, attributes) create_todos(users, attributes)
end end
......
...@@ -3313,29 +3313,6 @@ Input type: `MergeRequestAcceptInput` ...@@ -3313,29 +3313,6 @@ Input type: `MergeRequestAcceptInput`
| <a id="mutationmergerequestaccepterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | <a id="mutationmergerequestaccepterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationmergerequestacceptmergerequest"></a>`mergeRequest` | [`MergeRequest`](#mergerequest) | Merge request after mutation. | | <a id="mutationmergerequestacceptmergerequest"></a>`mergeRequest` | [`MergeRequest`](#mergerequest) | Merge request after mutation. |
### `Mutation.mergeRequestAttentionRequired`
Available only when feature flag `mr_attention_requests` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice.
Input type: `MergeRequestAttentionRequiredInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationmergerequestattentionrequiredclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationmergerequestattentionrequirediid"></a>`iid` | [`String!`](#string) | IID of the merge request to mutate. |
| <a id="mutationmergerequestattentionrequiredprojectpath"></a>`projectPath` | [`ID!`](#id) | Project the merge request to mutate is in. |
| <a id="mutationmergerequestattentionrequireduserid"></a>`userId` | [`UserID!`](#userid) | User ID for the user that has their attention requested. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationmergerequestattentionrequiredclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationmergerequestattentionrequirederrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationmergerequestattentionrequiredmergerequest"></a>`mergeRequest` | [`MergeRequest`](#mergerequest) | Merge request after mutation. |
### `Mutation.mergeRequestCreate` ### `Mutation.mergeRequestCreate`
Input type: `MergeRequestCreateInput` Input type: `MergeRequestCreateInput`
...@@ -3509,6 +3486,29 @@ Input type: `MergeRequestSetSubscriptionInput` ...@@ -3509,6 +3486,29 @@ Input type: `MergeRequestSetSubscriptionInput`
| <a id="mutationmergerequestsetsubscriptionerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | <a id="mutationmergerequestsetsubscriptionerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationmergerequestsetsubscriptionmergerequest"></a>`mergeRequest` | [`MergeRequest`](#mergerequest) | Merge request after mutation. | | <a id="mutationmergerequestsetsubscriptionmergerequest"></a>`mergeRequest` | [`MergeRequest`](#mergerequest) | Merge request after mutation. |
### `Mutation.mergeRequestToggleAttentionRequested`
Available only when feature flag `mr_attention_requests` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice.
Input type: `MergeRequestToggleAttentionRequestedInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationmergerequesttoggleattentionrequestedclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationmergerequesttoggleattentionrequestediid"></a>`iid` | [`String!`](#string) | IID of the merge request to mutate. |
| <a id="mutationmergerequesttoggleattentionrequestedprojectpath"></a>`projectPath` | [`ID!`](#id) | Project the merge request to mutate is in. |
| <a id="mutationmergerequesttoggleattentionrequesteduserid"></a>`userId` | [`UserID!`](#userid) | User ID for the user to toggle attention requested. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationmergerequesttoggleattentionrequestedclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationmergerequesttoggleattentionrequestederrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationmergerequesttoggleattentionrequestedmergerequest"></a>`mergeRequest` | [`MergeRequest`](#mergerequest) | Merge request after mutation. |
### `Mutation.mergeRequestUpdate` ### `Mutation.mergeRequestUpdate`
Update attributes of a merge request. Update attributes of a merge request.
...@@ -16455,7 +16455,7 @@ State of a review of a GitLab merge request. ...@@ -16455,7 +16455,7 @@ State of a review of a GitLab merge request.
| Value | Description | | Value | Description |
| ----- | ----------- | | ----- | ----------- |
| <a id="mergerequestreviewstateattention_required"></a>`ATTENTION_REQUIRED` | The merge request is attention_required. | | <a id="mergerequestreviewstateattention_requested"></a>`ATTENTION_REQUESTED` | The merge request is attention_requested. |
| <a id="mergerequestreviewstatereviewed"></a>`REVIEWED` | The merge request is reviewed. | | <a id="mergerequestreviewstatereviewed"></a>`REVIEWED` | The merge request is reviewed. |
| <a id="mergerequestreviewstateunreviewed"></a>`UNREVIEWED` | The merge request is unreviewed. | | <a id="mergerequestreviewstateunreviewed"></a>`UNREVIEWED` | The merge request is unreviewed. |
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import AttentionRequiredToggle from '~/sidebar/components/attention_required_toggle.vue'; import AttentionRequestedToggle from '~/sidebar/components/attention_requested_toggle.vue';
let wrapper; let wrapper;
function factory(propsData = {}) { function factory(propsData = {}) {
wrapper = mount(AttentionRequiredToggle, { propsData }); wrapper = mount(AttentionRequestedToggle, { propsData });
} }
const findToggle = () => wrapper.findComponent(GlButton); const findToggle = () => wrapper.findComponent(GlButton);
...@@ -16,52 +16,52 @@ describe('Attention require toggle', () => { ...@@ -16,52 +16,52 @@ describe('Attention require toggle', () => {
}); });
it('renders button', () => { it('renders button', () => {
factory({ type: 'reviewer', user: { attention_required: false } }); factory({ type: 'reviewer', user: { attention_requested: false } });
expect(findToggle().exists()).toBe(true); expect(findToggle().exists()).toBe(true);
}); });
it.each` it.each`
attentionRequired | icon attentionRequested | icon
${true} | ${'star'} ${true} | ${'star'}
${false} | ${'star-o'} ${false} | ${'star-o'}
`( `(
'renders $icon icon when attention_required is $attentionRequired', 'renders $icon icon when attention_requested is $attentionRequested',
({ attentionRequired, icon }) => { ({ attentionRequested, icon }) => {
factory({ type: 'reviewer', user: { attention_required: attentionRequired } }); factory({ type: 'reviewer', user: { attention_requested: attentionRequested } });
expect(findToggle().props('icon')).toBe(icon); expect(findToggle().props('icon')).toBe(icon);
}, },
); );
it.each` it.each`
attentionRequired | variant attentionRequested | variant
${true} | ${'warning'} ${true} | ${'warning'}
${false} | ${'default'} ${false} | ${'default'}
`( `(
'renders button with variant $variant when attention_required is $attentionRequired', 'renders button with variant $variant when attention_requested is $attentionRequested',
({ attentionRequired, variant }) => { ({ attentionRequested, variant }) => {
factory({ type: 'reviewer', user: { attention_required: attentionRequired } }); factory({ type: 'reviewer', user: { attention_requested: attentionRequested } });
expect(findToggle().props('variant')).toBe(variant); expect(findToggle().props('variant')).toBe(variant);
}, },
); );
it('emits toggle-attention-required on click', async () => { it('emits toggle-attention-requested on click', async () => {
factory({ type: 'reviewer', user: { attention_required: true } }); factory({ type: 'reviewer', user: { attention_requested: true } });
await findToggle().trigger('click'); await findToggle().trigger('click');
expect(wrapper.emitted('toggle-attention-required')[0]).toEqual([ expect(wrapper.emitted('toggle-attention-requested')[0]).toEqual([
{ {
user: { attention_required: true }, user: { attention_requested: true },
callback: expect.anything(), callback: expect.anything(),
}, },
]); ]);
}); });
it('sets loading on click', async () => { it('sets loading on click', async () => {
factory({ type: 'reviewer', user: { attention_required: true } }); factory({ type: 'reviewer', user: { attention_requested: true } });
await findToggle().trigger('click'); await findToggle().trigger('click');
...@@ -69,14 +69,14 @@ describe('Attention require toggle', () => { ...@@ -69,14 +69,14 @@ describe('Attention require toggle', () => {
}); });
it.each` it.each`
type | attentionRequired | tooltip type | attentionRequested | tooltip
${'reviewer'} | ${true} | ${AttentionRequiredToggle.i18n.removeAttentionRequired} ${'reviewer'} | ${true} | ${AttentionRequestedToggle.i18n.removeAttentionRequested}
${'reviewer'} | ${false} | ${AttentionRequiredToggle.i18n.attentionRequiredReviewer} ${'reviewer'} | ${false} | ${AttentionRequestedToggle.i18n.attentionRequestedReviewer}
${'assignee'} | ${false} | ${AttentionRequiredToggle.i18n.attentionRequiredAssignee} ${'assignee'} | ${false} | ${AttentionRequestedToggle.i18n.attentionRequestedAssignee}
`( `(
'sets tooltip as $tooltip when attention_required is $attentionRequired and type is $type', 'sets tooltip as $tooltip when attention_requested is $attentionRequested and type is $type',
({ type, attentionRequired, tooltip }) => { ({ type, attentionRequested, tooltip }) => {
factory({ type, user: { attention_required: attentionRequired } }); factory({ type, user: { attention_requested: attentionRequested } });
expect(findToggle().attributes('aria-label')).toBe(tooltip); expect(findToggle().attributes('aria-label')).toBe(tooltip);
}, },
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import AttentionRequiredToggle from '~/sidebar/components/attention_required_toggle.vue'; import AttentionRequestedToggle from '~/sidebar/components/attention_requested_toggle.vue';
import ReviewerAvatarLink from '~/sidebar/components/reviewers/reviewer_avatar_link.vue'; import ReviewerAvatarLink from '~/sidebar/components/reviewers/reviewer_avatar_link.vue';
import UncollapsedReviewerList from '~/sidebar/components/reviewers/uncollapsed_reviewer_list.vue'; import UncollapsedReviewerList from '~/sidebar/components/reviewers/uncollapsed_reviewer_list.vue';
import userDataMock from '../../user_data_mock'; import userDataMock from '../../user_data_mock';
...@@ -121,11 +121,11 @@ describe('UncollapsedReviewerList component', () => { ...@@ -121,11 +121,11 @@ describe('UncollapsedReviewerList component', () => {
expect(wrapper.findAll('[data-testid="re-request-button"]').length).toBe(0); expect(wrapper.findAll('[data-testid="re-request-button"]').length).toBe(0);
}); });
it('emits toggle-attention-required', () => { it('emits toggle-attention-requested', () => {
createComponent({ users: [userDataMock()] }, { mrAttentionRequests: true }); createComponent({ users: [userDataMock()] }, { mrAttentionRequests: true });
wrapper.find(AttentionRequiredToggle).vm.$emit('toggle-attention-required', 'data'); wrapper.find(AttentionRequestedToggle).vm.$emit('toggle-attention-requested', 'data');
expect(wrapper.emitted('toggle-attention-required')[0]).toEqual(['data']); expect(wrapper.emitted('toggle-attention-requested')[0]).toEqual(['data']);
}); });
}); });
...@@ -119,19 +119,19 @@ describe('Sidebar mediator', () => { ...@@ -119,19 +119,19 @@ describe('Sidebar mediator', () => {
}); });
}); });
describe('toggleAttentionRequired', () => { describe('toggleAttentionRequested', () => {
let attentionRequiredService; let attentionRequiredService;
beforeEach(() => { beforeEach(() => {
attentionRequiredService = jest attentionRequiredService = jest
.spyOn(mediator.service, 'attentionRequired') .spyOn(mediator.service, 'toggleAttentionRequested')
.mockResolvedValue(); .mockResolvedValue();
}); });
it('calls attentionRequired service method', async () => { it('calls attentionRequired service method', async () => {
mediator.store.reviewers = [{ id: 1, attention_required: false, username: 'root' }]; mediator.store.reviewers = [{ id: 1, attention_requested: false, username: 'root' }];
await mediator.toggleAttentionRequired('reviewer', { await mediator.toggleAttentionRequested('reviewer', {
user: { id: 1, username: 'root' }, user: { id: 1, username: 'root' },
callback: jest.fn(), callback: jest.fn(),
}); });
...@@ -145,23 +145,23 @@ describe('Sidebar mediator', () => { ...@@ -145,23 +145,23 @@ describe('Sidebar mediator', () => {
`('finds $type', ({ type, method }) => { `('finds $type', ({ type, method }) => {
const methodSpy = jest.spyOn(mediator.store, method); const methodSpy = jest.spyOn(mediator.store, method);
mediator.toggleAttentionRequired(type, { user: { id: 1 }, callback: jest.fn() }); mediator.toggleAttentionRequested(type, { user: { id: 1 }, callback: jest.fn() });
expect(methodSpy).toHaveBeenCalledWith({ id: 1 }); expect(methodSpy).toHaveBeenCalledWith({ id: 1 });
}); });
it.each` it.each`
attentionRequired | toastMessage attentionRequested | toastMessage
${true} | ${'Removed attention request from @root'} ${true} | ${'Removed attention request from @root'}
${false} | ${'Requested attention from @root'} ${false} | ${'Requested attention from @root'}
`( `(
'it creates toast $toastMessage when attention_required is $attentionRequired', 'it creates toast $toastMessage when attention_requested is $attentionRequested',
async ({ attentionRequired, toastMessage }) => { async ({ attentionRequested, toastMessage }) => {
mediator.store.reviewers = [ mediator.store.reviewers = [
{ id: 1, attention_required: attentionRequired, username: 'root' }, { id: 1, attention_requested: attentionRequested, username: 'root' },
]; ];
await mediator.toggleAttentionRequired('reviewer', { await mediator.toggleAttentionRequested('reviewer', {
user: { id: 1, username: 'root' }, user: { id: 1, username: 'root' },
callback: jest.fn(), callback: jest.fn(),
}); });
......
...@@ -13,9 +13,9 @@ RSpec.describe GitlabSchema.types['MergeRequestReviewState'] do ...@@ -13,9 +13,9 @@ RSpec.describe GitlabSchema.types['MergeRequestReviewState'] do
description: 'The merge request is unreviewed.', description: 'The merge request is unreviewed.',
value: 'unreviewed' value: 'unreviewed'
), ),
'ATTENTION_REQUIRED' => have_attributes( 'ATTENTION_REQUESTED' => have_attributes(
description: 'The merge request is attention_required.', description: 'The merge request is attention_requested.',
value: 'attention_required' value: 'attention_requested'
) )
) )
end end
......
...@@ -78,7 +78,7 @@ RSpec.describe GitlabSchema.types['UserMergeRequestInteraction'] do ...@@ -78,7 +78,7 @@ RSpec.describe GitlabSchema.types['UserMergeRequestInteraction'] do
merge_request.reviewers << user merge_request.reviewers << user
end end
it { is_expected.to eq(Types::MergeRequestReviewStateEnum.values['ATTENTION_REQUIRED'].value) } it { is_expected.to eq(Types::MergeRequestReviewStateEnum.values['ATTENTION_REQUESTED'].value) }
it 'implies not reviewed' do it 'implies not reviewed' do
expect(resolve(:reviewed)).to be false expect(resolve(:reviewed)).to be false
......
...@@ -61,7 +61,7 @@ RSpec.describe ::Users::MergeRequestInteraction do ...@@ -61,7 +61,7 @@ RSpec.describe ::Users::MergeRequestInteraction do
merge_request.reviewers << user merge_request.reviewers << user
end end
it { is_expected.to eq(Types::MergeRequestReviewStateEnum.values['ATTENTION_REQUIRED'].value) } it { is_expected.to eq(Types::MergeRequestReviewStateEnum.values['ATTENTION_REQUESTED'].value) }
it 'implies not reviewed' do it 'implies not reviewed' do
expect(interaction).not_to be_reviewed expect(interaction).not_to be_reviewed
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Setting attention required for reviewer' do RSpec.describe 'Toggle attention requested for reviewer' do
include GraphqlHelpers include GraphqlHelpers
let(:current_user) { create(:user) } let(:current_user) { create(:user) }
...@@ -16,7 +16,7 @@ RSpec.describe 'Setting attention required for reviewer' do ...@@ -16,7 +16,7 @@ RSpec.describe 'Setting attention required for reviewer' do
project_path: project.full_path, project_path: project.full_path,
iid: merge_request.iid.to_s iid: merge_request.iid.to_s
} }
graphql_mutation(:merge_request_attention_required, variables.merge(input), graphql_mutation(:merge_request_toggle_attention_requested, variables.merge(input),
<<-QL.strip_heredoc <<-QL.strip_heredoc
clientMutationId clientMutationId
errors errors
...@@ -25,7 +25,7 @@ RSpec.describe 'Setting attention required for reviewer' do ...@@ -25,7 +25,7 @@ RSpec.describe 'Setting attention required for reviewer' do
end end
def mutation_response def mutation_response
graphql_mutation_response(:merge_request_attention_required) graphql_mutation_response(:merge_request_toggle_attention_requested)
end end
def mutation_errors def mutation_errors
......
...@@ -347,7 +347,7 @@ RSpec.describe 'getting merge request information nested in a project' do ...@@ -347,7 +347,7 @@ RSpec.describe 'getting merge request information nested in a project' do
expect(interaction_data).to contain_exactly a_hash_including( expect(interaction_data).to contain_exactly a_hash_including(
'canMerge' => false, 'canMerge' => false,
'canUpdate' => can_update, 'canUpdate' => can_update,
'reviewState' => attention_required, 'reviewState' => attention_requested,
'reviewed' => false, 'reviewed' => false,
'approved' => false 'approved' => false
) )
...@@ -380,8 +380,8 @@ RSpec.describe 'getting merge request information nested in a project' do ...@@ -380,8 +380,8 @@ RSpec.describe 'getting merge request information nested in a project' do
describe 'scalability' do describe 'scalability' do
let_it_be(:other_users) { create_list(:user, 3) } let_it_be(:other_users) { create_list(:user, 3) }
let(:attention_required) do let(:attention_requested) do
{ 'reviewState' => 'ATTENTION_REQUIRED' } { 'reviewState' => 'ATTENTION_REQUESTED' }
end end
let(:reviewed) do let(:reviewed) do
...@@ -413,9 +413,9 @@ RSpec.describe 'getting merge request information nested in a project' do ...@@ -413,9 +413,9 @@ RSpec.describe 'getting merge request information nested in a project' do
expect { post_graphql(query) }.not_to exceed_query_limit(baseline) expect { post_graphql(query) }.not_to exceed_query_limit(baseline)
expect(interaction_data).to contain_exactly( expect(interaction_data).to contain_exactly(
include(attention_required), include(attention_requested),
include(attention_required), include(attention_requested),
include(attention_required), include(attention_requested),
include(reviewed) include(reviewed)
) )
end end
...@@ -444,7 +444,7 @@ RSpec.describe 'getting merge request information nested in a project' do ...@@ -444,7 +444,7 @@ RSpec.describe 'getting merge request information nested in a project' do
it_behaves_like 'when requesting information about MR interactions' do it_behaves_like 'when requesting information about MR interactions' do
let(:field) { :reviewers } let(:field) { :reviewers }
let(:attention_required) { 'ATTENTION_REQUIRED' } let(:attention_requested) { 'ATTENTION_REQUESTED' }
let(:can_update) { false } let(:can_update) { false }
def assign_user(user) def assign_user(user)
...@@ -454,7 +454,7 @@ RSpec.describe 'getting merge request information nested in a project' do ...@@ -454,7 +454,7 @@ RSpec.describe 'getting merge request information nested in a project' do
it_behaves_like 'when requesting information about MR interactions' do it_behaves_like 'when requesting information about MR interactions' do
let(:field) { :assignees } let(:field) { :assignees }
let(:attention_required) { nil } let(:attention_requested) { nil }
let(:can_update) { true } # assignees can update MRs let(:can_update) { true } # assignees can update MRs
def assign_user(user) def assign_user(user)
......
...@@ -19,7 +19,7 @@ RSpec.describe MergeRequestUserEntity do ...@@ -19,7 +19,7 @@ RSpec.describe MergeRequestUserEntity do
is_expected.to include( is_expected.to include(
:id, :name, :username, :state, :avatar_url, :web_url, :id, :name, :username, :state, :avatar_url, :web_url,
:can_merge, :can_update_merge_request, :reviewed, :approved, :can_merge, :can_update_merge_request, :reviewed, :approved,
:attention_required :attention_requested
) )
end end
...@@ -57,8 +57,8 @@ RSpec.describe MergeRequestUserEntity do ...@@ -57,8 +57,8 @@ RSpec.describe MergeRequestUserEntity do
end end
end end
context 'attention_required' do context 'attention_requested' do
it { is_expected.to include(attention_required: true ) } it { is_expected.to include(attention_requested: true ) }
end end
describe 'performance' do describe 'performance' do
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe MergeRequests::AttentionRequiredService do RSpec.describe MergeRequests::ToggleAttentionRequestedService do
let(:current_user) { create(:user) } let(:current_user) { create(:user) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:assignee_user) { create(:user) } let(:assignee_user) { create(:user) }
...@@ -39,6 +39,10 @@ RSpec.describe MergeRequests::AttentionRequiredService do ...@@ -39,6 +39,10 @@ RSpec.describe MergeRequests::AttentionRequiredService do
end end
context 'reviewer exists' do context 'reviewer exists' do
before do
reviewer.update!(state: :reviewed)
end
it 'returns success' do it 'returns success' do
expect(result[:status]).to eq :success expect(result[:status]).to eq :success
end end
...@@ -47,11 +51,11 @@ RSpec.describe MergeRequests::AttentionRequiredService do ...@@ -47,11 +51,11 @@ RSpec.describe MergeRequests::AttentionRequiredService do
service.execute service.execute
reviewer.reload reviewer.reload
expect(reviewer.state).to eq 'attention_required' expect(reviewer.state).to eq 'attention_requested'
end end
it 'creates a new todo for the reviewer' do it 'creates a new todo for the reviewer' do
expect(todo_service).to receive(:create_attention_required_todo).with(merge_request, current_user, user) expect(todo_service).to receive(:create_attention_requested_todo).with(merge_request, current_user, user)
service.execute service.execute
end end
...@@ -60,6 +64,10 @@ RSpec.describe MergeRequests::AttentionRequiredService do ...@@ -60,6 +64,10 @@ RSpec.describe MergeRequests::AttentionRequiredService do
context 'assignee exists' do context 'assignee exists' do
let(:service) { described_class.new(project: project, current_user: current_user, merge_request: merge_request, user: assignee_user) } let(:service) { described_class.new(project: project, current_user: current_user, merge_request: merge_request, user: assignee_user) }
before do
assignee.update!(state: :reviewed)
end
it 'returns success' do it 'returns success' do
expect(result[:status]).to eq :success expect(result[:status]).to eq :success
end end
...@@ -68,11 +76,11 @@ RSpec.describe MergeRequests::AttentionRequiredService do ...@@ -68,11 +76,11 @@ RSpec.describe MergeRequests::AttentionRequiredService do
service.execute service.execute
assignee.reload assignee.reload
expect(assignee.state).to eq 'attention_required' expect(assignee.state).to eq 'attention_requested'
end end
it 'creates a new todo for the reviewer' do it 'creates a new todo for the reviewer' do
expect(todo_service).to receive(:create_attention_required_todo).with(merge_request, current_user, assignee_user) expect(todo_service).to receive(:create_attention_requested_todo).with(merge_request, current_user, assignee_user)
service.execute service.execute
end end
...@@ -83,13 +91,37 @@ RSpec.describe MergeRequests::AttentionRequiredService do ...@@ -83,13 +91,37 @@ RSpec.describe MergeRequests::AttentionRequiredService do
let(:service) { described_class.new(project: project, current_user: current_user, merge_request: merge_request, user: user) } let(:service) { described_class.new(project: project, current_user: current_user, merge_request: merge_request, user: user) }
let(:assignee) { merge_request.find_assignee(user) } let(:assignee) { merge_request.find_assignee(user) }
before do
reviewer.update!(state: :reviewed)
assignee.update!(state: :reviewed)
end
it 'updates reviewers and assignees state' do it 'updates reviewers and assignees state' do
service.execute service.execute
reviewer.reload reviewer.reload
assignee.reload assignee.reload
expect(reviewer.state).to eq 'attention_required' expect(reviewer.state).to eq 'attention_requested'
expect(assignee.state).to eq 'attention_required' expect(assignee.state).to eq 'attention_requested'
end
end
context 'state is attention_requested' do
before do
reviewer.update!(state: :attention_requested)
end
it 'toggles state to reviewed' do
service.execute
reviewer.reload
expect(reviewer.state).to eq "reviewed"
end
it 'does not create a new todo for the reviewer' do
expect(todo_service).not_to receive(:create_attention_requested_todo).with(merge_request, current_user, assignee_user)
service.execute
end end
end end
end end
......
...@@ -1218,14 +1218,14 @@ RSpec.describe TodoService do ...@@ -1218,14 +1218,14 @@ RSpec.describe TodoService do
end end
end end
describe '#create_attention_required_todo' do describe '#create_attention_requested_todo' do
let(:target) { create(:merge_request, author: author, source_project: project) } let(:target) { create(:merge_request, author: author, source_project: project) }
let(:user) { create(:user) } let(:user) { create(:user) }
it 'creates a todo for user' do it 'creates a todo for user' do
service.create_attention_required_todo(target, author, user) service.create_attention_requested_todo(target, author, user)
should_create_todo(user: user, target: target, action: Todo::ATTENTION_REQUIRED) should_create_todo(user: user, target: target, action: Todo::ATTENTION_REQUESTED)
end end
end end
......
...@@ -10,6 +10,6 @@ RSpec.shared_examples 'having reviewer state' do ...@@ -10,6 +10,6 @@ RSpec.shared_examples 'having reviewer state' do
end end
describe 'mr_attention_requests feature flag is enabled' do describe 'mr_attention_requests feature flag is enabled' do
it { is_expected.to have_attributes(state: 'attention_required') } it { is_expected.to have_attributes(state: 'attention_requested') }
end end
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