Commit 37952ce3 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'ph/reRequestReview' into 'master'

Allow users to re-request a new review from a reviewer

See merge request gitlab-org/gitlab!50068
parents a94f7e22 389e19ba
...@@ -76,8 +76,8 @@ export default { ...@@ -76,8 +76,8 @@ export default {
class="d-inline-block" class="d-inline-block"
> >
<!-- use d-flex so that slot can be appropriately styled --> <!-- use d-flex so that slot can be appropriately styled -->
<span class="d-flex"> <span class="gl-display-flex gl-align-items-center">
<reviewer-avatar :user="user" :img-size="32" :issuable-type="issuableType" /> <reviewer-avatar :user="user" :img-size="24" :issuable-type="issuableType" />
<slot :user="user"></slot> <slot :user="user"></slot>
</span> </span>
</gl-link> </gl-link>
......
...@@ -46,6 +46,9 @@ export default { ...@@ -46,6 +46,9 @@ export default {
assignSelf() { assignSelf() {
this.$emit('assign-self'); this.$emit('assign-self');
}, },
requestReview(data) {
this.$emit('request-review', data);
},
}, },
}; };
</script> </script>
...@@ -66,6 +69,7 @@ export default { ...@@ -66,6 +69,7 @@ export default {
:users="sortedReviewers" :users="sortedReviewers"
:root-path="rootPath" :root-path="rootPath"
:issuable-type="issuableType" :issuable-type="issuableType"
@request-review="requestReview"
/> />
</div> </div>
</div> </div>
......
...@@ -83,6 +83,9 @@ export default { ...@@ -83,6 +83,9 @@ export default {
return new Flash(__('Error occurred when saving reviewers')); return new Flash(__('Error occurred when saving reviewers'));
}); });
}, },
requestReview(data) {
this.mediator.requestReview(data);
},
}, },
}; };
</script> </script>
...@@ -101,6 +104,7 @@ export default { ...@@ -101,6 +104,7 @@ export default {
:editable="store.editable" :editable="store.editable"
:issuable-type="issuableType" :issuable-type="issuableType"
class="value" class="value"
@request-review="requestReview"
/> />
</div> </div>
</template> </template>
<script> <script>
// NOTE! For the first iteration, we are simply copying the implementation of Assignees // NOTE! For the first iteration, we are simply copying the implementation of Assignees
// It will soon be overhauled in Issue https://gitlab.com/gitlab-org/gitlab/-/issues/233736 // It will soon be overhauled in Issue https://gitlab.com/gitlab-org/gitlab/-/issues/233736
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import ReviewerAvatarLink from './reviewer_avatar_link.vue'; import ReviewerAvatarLink from './reviewer_avatar_link.vue';
...@@ -8,8 +9,13 @@ const DEFAULT_RENDER_COUNT = 5; ...@@ -8,8 +9,13 @@ const DEFAULT_RENDER_COUNT = 5;
export default { export default {
components: { components: {
GlButton,
GlIcon,
ReviewerAvatarLink, ReviewerAvatarLink,
}, },
directives: {
GlTooltip: GlTooltipDirective,
},
props: { props: {
users: { users: {
type: Array, type: Array,
...@@ -28,6 +34,8 @@ export default { ...@@ -28,6 +34,8 @@ export default {
data() { data() {
return { return {
showLess: true, showLess: true,
loading: false,
requestedReviewSuccess: false,
}; };
}, },
computed: { computed: {
...@@ -61,43 +69,53 @@ export default { ...@@ -61,43 +69,53 @@ export default {
toggleShowLess() { toggleShowLess() {
this.showLess = !this.showLess; this.showLess = !this.showLess;
}, },
reRequestReview(userId) {
this.loading = true;
this.$emit('request-review', { userId, callback: this.requestReviewComplete });
},
requestReviewComplete(success) {
if (success) {
this.requestedReviewSuccess = true;
setTimeout(() => {
this.requestedReviewSuccess = false;
}, 1500);
}
this.loading = false;
},
}, },
}; };
</script> </script>
<template> <template>
<reviewer-avatar-link <div>
v-if="hasOneUser" <div
#default="{ user }" v-for="(user, index) in users"
tooltip-placement="left" :key="user.id"
:tooltip-has-name="false" :class="{ 'gl-mb-3': index !== users.length - 1 }"
:user="firstUser" data-testid="reviewer"
:root-path="rootPath" >
:issuable-type="issuableType" <reviewer-avatar-link :user="user" :root-path="rootPath" :issuable-type="issuableType">
> <div class="gl-ml-3">@{{ user.username }}</div>
<div class="gl-ml-3 gl-line-height-normal"> </reviewer-avatar-link>
<div class="author">{{ user.name }}</div> <gl-icon
<div class="username">{{ username }}</div> v-if="requestedReviewSuccess"
</div> :size="24"
</reviewer-avatar-link> name="check"
<div v-else> class="float-right gl-text-green-500"
<div class="user-list"> />
<div v-for="user in uncollapsedUsers" :key="user.id" class="user-item"> <gl-button
<reviewer-avatar-link :user="user" :root-path="rootPath" :issuable-type="issuableType" /> v-else-if="user.can_update_merge_request && user.reviewed"
</div> v-gl-tooltip.left
</div> :title="__('Re-request review')"
<div v-if="renderShowMoreSection" class="user-list-more"> :loading="loading"
<button class="float-right gl-text-gray-500!"
type="button" size="small"
class="btn-link" icon="redo"
data-qa-selector="more_reviewers_link" variant="link"
@click="toggleShowLess" @click="reRequestReview(user.id)"
> />
<template v-if="showLess">
{{ hiddenReviewersLabel }}
</template>
<template v-else>{{ __('- show less') }}</template>
</button>
</div> </div>
</div> </div>
</template> </template>
mutation mergeRequestRequestRereview($projectPath: ID!, $iid: String!, $userId: ID!) {
mergeRequestReviewerRereview(input: { projectPath: $projectPath, iid: $iid, userId: $userId }) {
errors
}
}
import sidebarDetailsQuery from 'ee_else_ce/sidebar/queries/sidebarDetails.query.graphql'; import sidebarDetailsQuery from 'ee_else_ce/sidebar/queries/sidebarDetails.query.graphql';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import createGqClient, { fetchPolicies } from '~/lib/graphql'; import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import reviewerRereviewMutation from '../queries/reviewer_rereview.mutation.graphql';
export const gqClient = createGqClient( export const gqClient = createGqClient(
{}, {},
...@@ -70,4 +72,15 @@ export default class SidebarService { ...@@ -70,4 +72,15 @@ export default class SidebarService {
move_to_project_id: moveToProjectId, move_to_project_id: moveToProjectId,
}); });
} }
requestReview(userId) {
return gqClient.mutate({
mutation: reviewerRereviewMutation,
variables: {
userId: convertToGraphQLId('User', `${userId}`), // eslint-disable-line @gitlab/require-i18n-strings
projectPath: this.fullPath,
iid: this.iid.toString(),
},
});
}
} }
import Store from 'ee_else_ce/sidebar/stores/sidebar_store'; import Store from 'ee_else_ce/sidebar/stores/sidebar_store';
import toast from '~/vue_shared/plugins/global_toast';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { visitUrl } from '../lib/utils/url_utility'; import { visitUrl } from '../lib/utils/url_utility';
import { deprecatedCreateFlash as Flash } from '../flash'; import { deprecatedCreateFlash as Flash } from '../flash';
...@@ -51,6 +52,17 @@ export default class SidebarMediator { ...@@ -51,6 +52,17 @@ export default class SidebarMediator {
return this.service.update(field, data); return this.service.update(field, data);
} }
requestReview({ userId, callback }) {
return this.service
.requestReview(userId)
.then(() => {
this.store.updateReviewer(userId);
toast(__('Requested review'));
callback(true);
})
.catch(() => callback(false));
}
setMoveToProjectId(projectId) { setMoveToProjectId(projectId) {
this.store.setMoveToProjectId(projectId); this.store.setMoveToProjectId(projectId);
} }
......
...@@ -96,6 +96,14 @@ export default class SidebarStore { ...@@ -96,6 +96,14 @@ export default class SidebarStore {
} }
} }
updateReviewer(id) {
const reviewer = this.findReviewer({ id });
if (reviewer) {
reviewer.reviewed = false;
}
}
findAssignee(findAssignee) { findAssignee(findAssignee) {
return this.assignees.find(({ id }) => id === findAssignee.id); return this.assignees.find(({ id }) => id === findAssignee.id);
} }
......
---
title: Allow users to re-request a review from a reviewer
merge_request: 50068
author:
type: added
...@@ -23918,6 +23918,9 @@ msgstr "" ...@@ -23918,6 +23918,9 @@ msgstr ""
msgid "Re-authentication required" msgid "Re-authentication required"
msgstr "" msgstr ""
msgid "Re-request review"
msgstr ""
msgid "Re-verification interval" msgid "Re-verification interval"
msgstr "" msgstr ""
...@@ -24737,6 +24740,9 @@ msgstr "" ...@@ -24737,6 +24740,9 @@ msgstr ""
msgid "Requested design version does not exist." msgid "Requested design version does not exist."
msgstr "" msgstr ""
msgid "Requested review"
msgstr ""
msgid "Requested states are invalid" msgid "Requested states are invalid"
msgstr "" msgstr ""
......
...@@ -114,8 +114,7 @@ describe('Reviewer component', () => { ...@@ -114,8 +114,7 @@ describe('Reviewer component', () => {
editable: true, editable: true,
}); });
expect(wrapper.findAll('.user-item').length).toBe(users.length); expect(wrapper.findAll('[data-testid="reviewer"]').length).toBe(users.length);
expect(wrapper.find('.user-list-more').exists()).toBe(false);
}); });
it('shows sorted reviewer where "can merge" users are sorted first', () => { it('shows sorted reviewer where "can merge" users are sorted first', () => {
...@@ -144,10 +143,10 @@ describe('Reviewer component', () => { ...@@ -144,10 +143,10 @@ describe('Reviewer component', () => {
users, users,
}); });
const userItems = wrapper.findAll('.user-list .user-item a'); const userItems = wrapper.findAll('[data-testid="reviewer"]');
expect(userItems.length).toBe(3); expect(userItems.length).toBe(3);
expect(userItems.at(0).attributes('title')).toBe(users[2].name); expect(userItems.at(0).find('a').attributes('title')).toBe(users[2].name);
}); });
it('passes the sorted reviewers to the collapsed-reviewer-list', () => { it('passes the sorted reviewers to the collapsed-reviewer-list', () => {
......
...@@ -48,7 +48,7 @@ RSpec.shared_examples 'an editable merge request' do ...@@ -48,7 +48,7 @@ RSpec.shared_examples 'an editable merge request' do
end end
page.within '.reviewer' do page.within '.reviewer' do
expect(page).to have_content user.name expect(page).to have_content user.username
end end
page.within '.milestone' do page.within '.milestone' do
......
...@@ -40,7 +40,7 @@ RSpec.shared_examples 'multiple reviewers merge request' do |action, save_button ...@@ -40,7 +40,7 @@ RSpec.shared_examples 'multiple reviewers merge request' do |action, save_button
# Closing dropdown to persist # Closing dropdown to persist
click_link 'Edit' click_link 'Edit'
expect(page).to have_content user2.name expect(page).to have_content user2.username
end end
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