Skip to content
Snippets Groups Projects
Verified Commit 01c6d712 authored by Phil Hughes's avatar Phil Hughes
Browse files

Added reviewers list to reviewer drawer component

parent add2b639
No related branches found
No related tags found
2 merge requests!162233Draft: Script to update Topology Service Gem,!152920Added reviewers list to reviewer drawer component
Pipeline #1291673249 passed with warnings
Pipeline: GitLab

#1291689355

    Pipeline: rspec:predictive

    #1291689349

      Showing
      with 374 additions and 2 deletions
      mutation setReviewers($projectPath: ID!, $iid: String!, $reviewerUsernames: [String!]!) {
      mergeRequestSetReviewers(
      input: { projectPath: $projectPath, iid: $iid, reviewerUsernames: $reviewerUsernames }
      ) {
      errors
      }
      }
      query addReviewerPermissions($fullPath: ID!, $iid: String!) {
      project(fullPath: $fullPath) {
      id
      mergeRequest(iid: $iid) {
      id
      userPermissions {
      adminMergeRequest
      }
      }
      }
      }
      ......@@ -2,26 +2,53 @@
      import { GlDrawer } from '@gitlab/ui';
      import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
      import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
      import getMergeRequestReviewers from '~/sidebar/queries/get_merge_request_reviewers.query.graphql';
      import ReviewersContainer from './reviewers_container.vue';
      export default {
      apollo: {
      reviewers: {
      query: getMergeRequestReviewers,
      variables() {
      return {
      fullPath: this.projectPath,
      iid: this.issuableIid,
      };
      },
      update: (data) => data.workspace?.issuable?.reviewers?.nodes || [],
      skip() {
      return !this.open;
      },
      },
      },
      components: {
      GlDrawer,
      ReviewersContainer,
      ApprovalSummary: () =>
      import('ee_component/merge_requests/components/reviewers/approval_summary.vue'),
      ApprovalRulesWrapper: () =>
      import('ee_component/merge_requests/components/reviewers/approval_rules_wrapper.vue'),
      },
      inject: ['projectPath', 'issuableIid'],
      props: {
      open: {
      type: Boolean,
      required: true,
      },
      },
      data() {
      return {
      reviewers: [],
      };
      },
      computed: {
      getDrawerHeaderHeight() {
      if (!this.open) return '0';
      return getContentWrapperHeight();
      },
      loadingReviewers() {
      return this.$apollo.queries.reviewers.loading;
      },
      },
      DRAWER_Z_INDEX,
      };
      ......@@ -40,6 +67,7 @@ export default {
      <template #header>
      <approval-summary />
      </template>
      <reviewers-container :reviewers="reviewers" :loading-reviewers="loadingReviewers" />
      <approval-rules-wrapper />
      </gl-drawer>
      </template>
      <script>
      import { GlEmptyState, GlButton } from '@gitlab/ui';
      import noReviewersAssignedSvg from '@gitlab/svgs/dist/illustrations/add-user-sm.svg?url';
      import UncollapsedReviewerList from '~/sidebar/components/reviewers/uncollapsed_reviewer_list.vue';
      import { sprintf, __, n__ } from '~/locale';
      import UpdateReviewers from './update_reviewers.vue';
      import userPermissionsQuery from './queries/user_permissions.query.graphql';
      export default {
      apollo: {
      userPermissions: {
      query: userPermissionsQuery,
      variables() {
      return {
      fullPath: this.projectPath,
      iid: this.issuableIid,
      };
      },
      update: (data) => data.project?.mergeRequest?.userPermissions || {},
      },
      },
      components: {
      GlEmptyState,
      GlButton,
      UncollapsedReviewerList,
      UpdateReviewers,
      },
      inject: ['projectPath', 'issuableIid'],
      props: {
      reviewers: {
      type: Array,
      required: true,
      },
      loadingReviewers: {
      type: Boolean,
      required: true,
      },
      },
      data() {
      return {
      userPermissions: {},
      };
      },
      computed: {
      currentUser() {
      return [gon.current_username];
      },
      relativeUrlRoot() {
      return gon.relative_url_root ?? '';
      },
      reviewersTitle() {
      if (this.reviewers.length === 0) {
      return sprintf(__('%{count} Reviewers'), { count: this.reviewers.length });
      }
      return sprintf(n__('%{count} Reviewer', '%{count} Reviewers', this.reviewers.length), {
      count: this.reviewers.length,
      });
      },
      },
      noReviewersAssignedSvg,
      };
      </script>
      <template>
      <div>
      <div class="gl-display-flex gl-mb-3">
      <template v-if="loadingReviewers">
      <div class="gl-animate-skeleton-loader gl-h-4 gl-rounded-base gl-w-20"></div>
      <div class="gl-animate-skeleton-loader gl-h-4 gl-rounded-base gl-w-2 gl-ml-auto"></div>
      </template>
      <div v-else class="gl-line-height-20 gl-text-gray-900 gl-font-weight-bold">
      {{ reviewersTitle }}
      </div>
      </div>
      <div v-if="loadingReviewers">
      <div class="gl-animate-skeleton-loader gl-h-4 gl-rounded-base gl-mb-3 gl-max-w-20!"></div>
      <div class="gl-animate-skeleton-loader gl-h-4 gl-rounded-base gl-mb-3 gl-max-w-20!"></div>
      <div class="gl-animate-skeleton-loader gl-h-4 gl-rounded-base gl-max-w-20!"></div>
      </div>
      <uncollapsed-reviewer-list
      v-else-if="reviewers.length"
      :root-path="relativeUrlRoot"
      :users="reviewers"
      issuable-type="merge_request"
      />
      <gl-empty-state v-else :svg-path="$options.noReviewersAssignedSvg" :svg-height="70">
      <template #description>
      <p class="gl-font-weight-normal gl-mb-3">{{ __('No reviewers assigned') }}</p>
      <update-reviewers
      v-if="userPermissions.adminMergeRequest"
      :selected-reviewers="currentUser"
      >
      <template #default="{ loading, updateReviewers }">
      <gl-button
      category="tertiary"
      size="small"
      :loading="loading"
      data-testid="assign-yourself-button"
      @click="updateReviewers"
      >
      {{ __('Assign yourself') }}
      </gl-button>
      </template>
      </update-reviewers>
      </template>
      </gl-empty-state>
      </div>
      </template>
      <script>
      import setReviewersMutation from './queries/set_reviewers.mutation.graphql';
      export default {
      inject: ['projectPath', 'issuableIid'],
      props: {
      selectedReviewers: {
      type: Array,
      required: true,
      },
      },
      data() {
      return {
      loading: false,
      };
      },
      methods: {
      async updateReviewers() {
      this.loading = true;
      await this.$apollo.mutate({
      mutation: setReviewersMutation,
      variables: {
      reviewerUsernames: this.selectedReviewers,
      projectPath: this.projectPath,
      iid: this.issuableIid,
      },
      });
      this.loading = false;
      },
      },
      render() {
      return this.$scopedSlots.default({
      loading: this.loading,
      updateReviewers: this.updateReviewers,
      });
      },
      };
      </script>
      ......@@ -53,7 +53,7 @@ export default {
      :data-username="user.username"
      :data-cannot-merge="cannotMerge"
      data-placement="left"
      class="gl-display-inline-block js-user-link"
      class="gl-display-inline-block js-user-link gl-reset-color! gl-hover-text-blue-800!"
      >
      <!-- use d-flex so that slot can be appropriately styled -->
      <span class="gl-display-flex">
      ......
      ......@@ -648,6 +648,14 @@ msgid_plural "%{count} Participants"
      msgstr[0] ""
      msgstr[1] ""
       
      msgid "%{count} Reviewer"
      msgid_plural "%{count} Reviewers"
      msgstr[0] ""
      msgstr[1] ""
      msgid "%{count} Reviewers"
      msgstr ""
      msgid "%{count} approval required from %{name}"
      msgid_plural "%{count} approvals required from %{name}"
      msgstr[0] ""
      ......@@ -7040,6 +7048,9 @@ msgstr ""
      msgid "Assign to me"
      msgstr ""
       
      msgid "Assign yourself"
      msgstr ""
      msgid "Assigned"
      msgstr ""
       
      ......@@ -34244,6 +34255,9 @@ msgstr ""
      msgid "No results found."
      msgstr ""
       
      msgid "No reviewers assigned"
      msgstr ""
      msgid "No runner executable"
      msgstr ""
       
      import Vue from 'vue';
      import VueApollo from 'vue-apollo';
      import { shallowMount } from '@vue/test-utils';
      import { GlDrawer } from '@gitlab/ui';
      import createMockApollo from 'helpers/mock_apollo_helper';
      import ReviewerDrawer from '~/merge_requests/components/reviewers/reviewer_drawer.vue';
      import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
      import getMergeRequestReviewers from '~/sidebar/queries/get_merge_request_reviewers.query.graphql';
      import userPermissionsQuery from '~/merge_requests/components/reviewers/queries/user_permissions.query.graphql';
      jest.mock('~/lib/utils/dom_utils', () => ({ getContentWrapperHeight: jest.fn() }));
      let wrapper;
      Vue.use(VueApollo);
      function createComponent(propsData = {}) {
      const apolloProvider = createMockApollo([
      [getMergeRequestReviewers, jest.fn().mockResolvedValue({ data: { workspace: null } })],
      [userPermissionsQuery, jest.fn().mockResolvedValue({ data: { project: null } })],
      ]);
      wrapper = shallowMount(ReviewerDrawer, {
      apolloProvider,
      propsData,
      provide: {
      projectPath: 'gitlab-org/gitlab',
      ......
      import Vue from 'vue';
      import VueApollo from 'vue-apollo';
      import { GlEmptyState, GlButton } from '@gitlab/ui';
      import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
      import createMockApollo from 'helpers/mock_apollo_helper';
      import waitForPromises from 'helpers/wait_for_promises';
      import UpdateReviewers from '~/merge_requests/components/reviewers/update_reviewers.vue';
      import ReviewersContainer from '~/merge_requests/components/reviewers/reviewers_container.vue';
      import UncollapsedReviewerList from '~/sidebar/components/reviewers/uncollapsed_reviewer_list.vue';
      import userPermissionsQuery from '~/merge_requests/components/reviewers/queries/user_permissions.query.graphql';
      import setReviewersMutation from '~/merge_requests/components/reviewers/queries/set_reviewers.mutation.graphql';
      let wrapper;
      let setReviewersMutationHandler;
      Vue.use(VueApollo);
      function createComponent(propsData = {}, adminMergeRequest = true) {
      setReviewersMutationHandler = jest.fn().mockResolvedValue({
      data: { mergeRequestSetReviewers: { errors: null } },
      });
      const apolloProvider = createMockApollo([
      [
      userPermissionsQuery,
      jest.fn().mockResolvedValue({
      data: {
      project: { id: 1, mergeRequest: { id: 1, userPermissions: { adminMergeRequest } } },
      },
      }),
      ],
      [setReviewersMutation, setReviewersMutationHandler],
      ]);
      wrapper = shallowMountExtended(ReviewersContainer, {
      apolloProvider,
      propsData: {
      reviewers: [],
      loadingReviewers: false,
      ...propsData,
      },
      provide: {
      projectPath: 'gitlab-org/gitlab',
      issuableIid: '1',
      },
      stubs: {
      GlEmptyState,
      UpdateReviewers,
      },
      });
      }
      const findEmptyState = () => wrapper.findComponent(GlEmptyState);
      const findAssignButton = () => wrapper.findComponent(GlButton);
      const findReviewersList = () => wrapper.findComponent(UncollapsedReviewerList);
      const findUpdateReviewers = () => wrapper.findComponent(UpdateReviewers);
      describe('Reviewers container component', () => {
      beforeEach(() => {
      window.gon = { current_username: 'root' };
      });
      afterEach(() => {
      window.gon = {};
      });
      describe('when no reviewers exist', () => {
      it('renders empty state', () => {
      createComponent();
      expect(findEmptyState().exists()).toBe(true);
      });
      describe('when user has permission to add reviewers', () => {
      it('renders empty state with add reviewers button', async () => {
      createComponent();
      await waitForPromises();
      expect(findAssignButton().exists()).toBe(true);
      });
      it('sets current user as update-reviewers component prop', async () => {
      createComponent();
      await waitForPromises();
      expect(findUpdateReviewers().props('selectedReviewers')).toEqual(
      expect.arrayContaining(['root']),
      );
      });
      it('adds current user as reviewer', async () => {
      createComponent();
      await waitForPromises();
      findAssignButton().vm.$emit('click');
      await waitForPromises();
      expect(setReviewersMutationHandler).toHaveBeenCalledWith(
      expect.objectContaining({
      reviewerUsernames: ['root'],
      }),
      );
      });
      });
      describe('when user does not have permission to add reviewers', () => {
      it('renders empty state with add reviewers button', async () => {
      createComponent({}, false);
      await waitForPromises();
      expect(findAssignButton().exists()).toBe(false);
      });
      });
      });
      it('renders reviewers list component', async () => {
      createComponent({ reviewers: ['test-reviewer'] });
      await waitForPromises();
      expect(findReviewersList().exists()).toBe(true);
      expect(findReviewersList().props()).toEqual(
      expect.objectContaining({
      users: ['test-reviewer'],
      issuableType: 'merge_request',
      }),
      );
      });
      });
      import Vue from 'vue';
      import VueApollo from 'vue-apollo';
      import { GlLoadingIcon } from '@gitlab/ui';
      import { mountExtended } from 'helpers/vue_test_utils_helper';
      import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
      import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
      import waitForPromises from 'helpers/wait_for_promises';
      import createMockApollo from 'helpers/mock_apollo_helper';
      import Component from '~/sidebar/components/reviewers/reviewer_title.vue';
      import getMergeRequestReviewers from '~/sidebar/queries/get_merge_request_reviewers.query.graphql';
      import userPermissionsQuery from '~/merge_requests/components/reviewers/queries/user_permissions.query.graphql';
      Vue.use(VueApollo);
      describe('ReviewerTitle component', () => {
      let wrapper;
      ......@@ -11,7 +18,13 @@ describe('ReviewerTitle component', () => {
      const findEditButton = () => wrapper.findByTestId('reviewers-edit-button');
      const createComponent = (props, { reviewerAssignDrawer = false } = {}) => {
      const apolloProvider = createMockApollo([
      [getMergeRequestReviewers, jest.fn().mockResolvedValue({ data: { workspace: null } })],
      [userPermissionsQuery, jest.fn().mockResolvedValue({ data: { project: null } })],
      ]);
      return mountExtended(Component, {
      apolloProvider,
      propsData: {
      numberOfReviewers: 0,
      editable: false,
      ......@@ -26,6 +39,7 @@ describe('ReviewerTitle component', () => {
      reviewerAssignDrawer,
      },
      },
      stubs: ['approval-summary'],
      });
      };
      ......@@ -121,7 +135,7 @@ describe('ReviewerTitle component', () => {
      resetHTMLFixture();
      });
      it('sets title for dropdown toggle as `Quick assign`', () => {
      it('sets title for dropdown toggle as `Quick assign`', async () => {
      wrapper = createComponent(
      {
      editable: true,
      ......@@ -129,6 +143,8 @@ describe('ReviewerTitle component', () => {
      { reviewerAssignDrawer: true },
      );
      await waitForPromises();
      expect(findEditButton().attributes('title')).toBe('Quick assign');
      });
      ......
      0% Loading or .
      You are about to add 0 people to the discussion. Proceed with caution.
      Finish editing this message first!
      Please register or to comment