From a02c620894752feecd032b8b3c994791b2340b89 Mon Sep 17 00:00:00 2001 From: Valerie Burton <vburton@gitlab.com> Date: Tue, 17 Oct 2023 01:57:16 +0000 Subject: [PATCH] Add Confidentiality Checkbox to New Test Case Form Adds a checkbox to the new test case form for setting confidentiality Changelog: changed EE: true --- .../components/issuable_create_root.vue | 5 ++ .../create/components/issuable_form.vue | 32 +++++++- doc/ci/test_cases/index.md | 7 +- .../components/test_case_create_root.vue | 11 ++- .../projects/quality/test_case_create_spec.rb | 75 ++++++++++++------- .../components/test_case_create_root_spec.js | 4 +- locale/gitlab.pot | 3 + .../components/issuable_create_root_spec.js | 3 + .../create/components/issuable_form_spec.js | 32 ++++++-- 9 files changed, 132 insertions(+), 40 deletions(-) diff --git a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_create_root.vue b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_create_root.vue index 033bb8c3885c..679332163b59 100644 --- a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_create_root.vue +++ b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_create_root.vue @@ -22,6 +22,10 @@ export default { type: String, required: true, }, + issuableType: { + type: String, + required: true, + }, }, }; </script> @@ -34,6 +38,7 @@ export default { :description-help-path="descriptionHelpPath" :labels-fetch-path="labelsFetchPath" :labels-manage-path="labelsManagePath" + :issuable-type="issuableType" > <template #actions="issuableMeta"> <slot name="actions" v-bind="issuableMeta"></slot> diff --git a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue index 1cfa3f6d3d76..64f0ec3fbc7c 100644 --- a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue +++ b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue @@ -1,15 +1,17 @@ <script> -import { GlForm, GlFormInput, GlFormGroup } from '@gitlab/ui'; +import { GlForm, GlFormInput, GlFormCheckbox, GlFormGroup } from '@gitlab/ui'; import LabelsSelect from '~/sidebar/components/labels/labels_select_vue/labels_select_root.vue'; import { VARIANT_EMBEDDED } from '~/sidebar/components/labels/labels_select_widget/constants'; import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; -import { __ } from '~/locale'; +import { __, sprintf } from '~/locale'; +import { issuableTypeText } from '~/issues/constants'; export default { VARIANT_EMBEDDED, components: { GlForm, GlFormInput, + GlFormCheckbox, GlFormGroup, MarkdownEditor, LabelsSelect, @@ -31,6 +33,10 @@ export default { type: String, required: true, }, + issuableType: { + type: String, + required: true, + }, }, descriptionFormFieldProps: { ariaLabel: __('Description'), @@ -44,10 +50,20 @@ export default { return { issuableTitle: '', issuableDescription: '', + issuableConfidential: false, selectedLabels: [], }; }, - computed: {}, + computed: { + confidentialityText() { + return sprintf( + __( + 'This %{issuableType} is confidential and should only be visible to team members with at least Reporter access.', + ), + { issuableType: issuableTypeText[this.issuableType] }, + ); + }, + }, methods: { handleUpdateSelectedLabels(labels) { if (labels.length) { @@ -85,6 +101,15 @@ export default { /> </div> </div> + <div data-testid="issuable-confidential" class="form-group row"> + <div class="col-12"> + <gl-form-group :label="__('Confidentiality')" label-for="issuable-confidential"> + <gl-form-checkbox id="issuable-confidential" v-model="issuableConfidential"> + {{ confidentialityText }} + </gl-form-checkbox> + </gl-form-group> + </div> + </div> <div data-testid="issuable-labels" class="form-group row"> <label for="issuable-labels" class="col-12">{{ __('Labels') }}</label> <div class="col-12"> @@ -111,6 +136,7 @@ export default { name="actions" :issuable-title="issuableTitle" :issuable-description="issuableDescription" + :issuable-confidential="issuableConfidential" :selected-labels="selectedLabels" ></slot> </div> diff --git a/doc/ci/test_cases/index.md b/doc/ci/test_cases/index.md index 3885b450a709..071884f2bed4 100644 --- a/doc/ci/test_cases/index.md +++ b/doc/ci/test_cases/index.md @@ -71,7 +71,7 @@ To edit a test case: ## Make a test case confidential -> Ability to make a test case confidential from the right sidebar [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/422120) in GitLab 16.5. +> Introduced for [new](https://gitlab.com/gitlab-org/gitlab/-/issues/422121) and [existing](https://gitlab.com/gitlab-org/gitlab/-/issues/422120) test cases in GitLab 16.5. If you're working on a test case that contains private information, you can make it confidential. @@ -79,7 +79,10 @@ Prerequisites: - You must have at least the Reporter role. -To make a test case confidential: on the right sidebar, select **Edit** next to **Confidentiality**, and then select **Turn on**. +To make a test case confidential: + +- When you [create a test case](#create-a-test-case): under **Confidentiality**, select the **This test case is confidential...** checkbox. +- When you [edit a test case](#edit-a-test-case): on the right sidebar, next to **Confidentiality**, select **Edit**, then select **Turn on**. You can also use the `/confidential` [quick action](../../user/project/quick_actions.md) when both creating a new test case or editing an existing one. diff --git a/ee/app/assets/javascripts/test_case_create/components/test_case_create_root.vue b/ee/app/assets/javascripts/test_case_create/components/test_case_create_root.vue index d18a5d7d3ddf..0e3fe1211a85 100644 --- a/ee/app/assets/javascripts/test_case_create/components/test_case_create_root.vue +++ b/ee/app/assets/javascripts/test_case_create/components/test_case_create_root.vue @@ -3,12 +3,14 @@ import { GlButton } from '@gitlab/ui'; import { createAlert } from '~/alert'; import IssuableCreate from '~/vue_shared/issuable/create/components/issuable_create_root.vue'; import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated +import { TYPE_TEST_CASE } from '~/issues/constants'; import { s__ } from '~/locale'; import createTestCase from '../queries/create_test_case.mutation.graphql'; export default { + TYPE_TEST_CASE, components: { GlButton, IssuableCreate, @@ -27,7 +29,12 @@ export default { }; }, methods: { - handleTestCaseSubmitClick({ issuableTitle, issuableDescription, selectedLabels }) { + handleTestCaseSubmitClick({ + issuableTitle, + issuableDescription, + issuableConfidential, + selectedLabels, + }) { this.createTestCaseRequestActive = true; return this.$apollo .mutate({ @@ -37,6 +44,7 @@ export default { projectPath: this.projectFullPath, title: issuableTitle, description: issuableDescription, + confidential: issuableConfidential, labelIds: selectedLabels.map((label) => label.id), }, }, @@ -75,6 +83,7 @@ export default { :description-help-path="descriptionHelpPath" :labels-fetch-path="labelsFetchPath" :labels-manage-path="labelsManagePath" + :issuable-type="$options.TYPE_TEST_CASE" > <template #title> <h1 class="page-title gl-font-size-h-display">{{ s__('TestCases|New test case') }}</h1> diff --git a/ee/spec/features/projects/quality/test_case_create_spec.rb b/ee/spec/features/projects/quality/test_case_create_spec.rb index 2586c81a9555..e99e8cc5be5b 100644 --- a/ee/spec/features/projects/quality/test_case_create_spec.rb +++ b/ee/spec/features/projects/quality/test_case_create_spec.rb @@ -17,28 +17,25 @@ end context 'test case create form' do + let(:title) { 'Sample title' } + let(:description) { 'Sample _test case_ description.' } + before do visit new_project_quality_test_case_path(project) wait_for_requests end - it 'shows page title, title, description and label input fields' do + it 'shows page title, title, description, confidentiality and label input fields' do page.within('.issuable-create-container') do expect(page.find('.page-title')).to have_content('New test case') end page.within('.issuable-create-container form') do - form_fields = page.find_all('.row') - - expect(form_fields[0].find('label')).to have_content('Title') - expect(form_fields[0]).to have_selector('input#issuable-title') - - expect(form_fields[1].find('label')).to have_content('Description') - expect(form_fields[1]).to have_selector('.js-vue-markdown-field') - - expect(form_fields[2].find('label')).to have_content('Labels') - expect(form_fields[2]).to have_selector('.labels-select-wrapper') + expect(find_by_testid('issuable-title')).to have_selector('input#issuable-title') + expect(find_by_testid('issuable-description')).to have_selector('.js-vue-markdown-field') + expect(find_by_testid('issuable-confidential')).to have_selector('input#issuable-confidential') + expect(find_by_testid('issuable-labels')).to have_selector('.labels-select-wrapper') end end @@ -60,27 +57,53 @@ end end - it 'creates a test case on saving form' do - title = 'Sample title' - description = 'Sample _test case_ description.' - - page.within('.issuable-create-container form') do - form_fields = page.find_all('.row') + context 'when creating a confidential test case' do + before do + fill_and_submit_form(confidential: true) + end - form_fields[0].find('input#issuable-title').native.send_keys title - form_fields[1].find('textarea#issuable-description').native.send_keys description - form_fields[2].find('.js-dropdown-button').click + it 'saves test case as confidential' do + page.within('.content-wrapper .project-test-cases') do + expect(page).to have_content(title) + expect(page).to have_css('[data-testid="eye-slash-icon"]') + end + end + end - wait_for_requests + context 'when creating a non-confidential test case' do + before do + fill_and_submit_form(confidential: false) + end - form_fields[2].find_all('.js-labels-list .dropdown-content li')[0].click + it 'saves test case as non-confidential' do + page.within('.content-wrapper .project-test-cases') do + expect(page).to have_content(title) + expect(page).not_to have_css('[data-testid="eye-slash-icon"]') + end end + end + end +end - click_button 'Submit test case' +private - wait_for_requests +def fill_and_submit_form(confidential:) + page.within('.issuable-create-container form') do + fill_in _('Title'), with: title + fill_in _('Description'), with: description - expect(page).to have_selector('.content-wrapper .project-test-cases') - end + find('#issuable-confidential').set(confidential) + + click_button _('Label') + + wait_for_requests + + click_link _('bug') + click_link _('enhancement') + click_link _('documentation') end + + click_button 'Submit test case' + + wait_for_requests end diff --git a/ee/spec/frontend/test_case_create/components/test_case_create_root_spec.js b/ee/spec/frontend/test_case_create/components/test_case_create_root_spec.js index 1233951cad21..2db0454770fa 100644 --- a/ee/spec/frontend/test_case_create/components/test_case_create_root_spec.js +++ b/ee/spec/frontend/test_case_create/components/test_case_create_root_spec.js @@ -66,6 +66,7 @@ describe('TestCaseCreateRoot', () => { name="actions" issuable-title="${title}" issuable-description="Test description" + :issuable-confidential="true" :selected-labels="[]" ></slot> </div> @@ -95,6 +96,7 @@ describe('TestCaseCreateRoot', () => { expect(mutationSuccessHandler).toHaveBeenCalledWith({ createTestCaseInput: { description: 'Test description', + confidential: true, labelIds: [], projectPath: 'gitlab-org/gitlab-test', title: 'Test title', @@ -144,7 +146,7 @@ describe('TestCaseCreateRoot', () => { }); }); - it('shows a warning when mutation has recoverable error', async () => { + it('shows a warning when mutation has recoverable error', async () => { createComponent({ title: 'Test title', handler: jest.fn().mockResolvedValue(mutationResponseError), diff --git a/locale/gitlab.pot b/locale/gitlab.pot index fcd8c86969eb..0cac3949b1f2 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -48288,6 +48288,9 @@ msgstr "" msgid "This %{issuableDisplayName} is locked. Only project members can comment." msgstr "" +msgid "This %{issuableType} is confidential and should only be visible to team members with at least Reporter access." +msgstr "" + msgid "This %{issuable} is hidden because its author has been banned." msgstr "" diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js index 03f509a3fa32..35e3564c5990 100644 --- a/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js +++ b/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js @@ -5,6 +5,7 @@ import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import IssuableCreateRoot from '~/vue_shared/issuable/create/components/issuable_create_root.vue'; import IssuableForm from '~/vue_shared/issuable/create/components/issuable_form.vue'; +import { TYPE_TEST_CASE } from '~/issues/constants'; Vue.use(VueApollo); @@ -13,6 +14,7 @@ const createComponent = ({ descriptionHelpPath = '/help/user/markdown', labelsFetchPath = '/gitlab-org/gitlab-shell/-/labels.json', labelsManagePath = '/gitlab-org/gitlab-shell/-/labels', + issuableType = TYPE_TEST_CASE, } = {}) => { return mount(IssuableCreateRoot, { propsData: { @@ -20,6 +22,7 @@ const createComponent = ({ descriptionHelpPath, labelsFetchPath, labelsManagePath, + issuableType, }, apolloProvider: createMockApollo(), slots: { diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js index 623617058434..61185f913d91 100644 --- a/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js +++ b/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js @@ -1,9 +1,10 @@ -import { GlFormInput } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { GlFormInput, GlFormGroup, GlFormCheckbox } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import IssuableForm from '~/vue_shared/issuable/create/components/issuable_form.vue'; import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; import LabelsSelect from '~/sidebar/components/labels/labels_select_vue/labels_select_root.vue'; +import { TYPE_TEST_CASE } from '~/issues/constants'; import { __ } from '~/locale'; const createComponent = ({ @@ -11,13 +12,15 @@ const createComponent = ({ descriptionHelpPath = '/help/user/markdown', labelsFetchPath = '/gitlab-org/gitlab-shell/-/labels.json', labelsManagePath = '/gitlab-org/gitlab-shell/-/labels', + issuableType = TYPE_TEST_CASE, } = {}) => { - return shallowMount(IssuableForm, { + return shallowMountExtended(IssuableForm, { propsData: { descriptionPreviewPath, descriptionHelpPath, labelsFetchPath, labelsManagePath, + issuableType, }, slots: { actions: ` @@ -58,7 +61,7 @@ describe('IssuableForm', () => { describe('template', () => { it('renders issuable title input field', () => { - const titleFieldEl = wrapper.find('[data-testid="issuable-title"]'); + const titleFieldEl = wrapper.findByTestId('issuable-title'); expect(titleFieldEl.exists()).toBe(true); expect(titleFieldEl.find('label').text()).toBe('Title'); @@ -68,7 +71,7 @@ describe('IssuableForm', () => { }); it('renders issuable description input field', () => { - const descriptionFieldEl = wrapper.find('[data-testid="issuable-description"]'); + const descriptionFieldEl = wrapper.findByTestId('issuable-description'); expect(descriptionFieldEl.exists()).toBe(true); expect(descriptionFieldEl.find('label').text()).toBe('Description'); @@ -88,8 +91,23 @@ describe('IssuableForm', () => { }); }); + it('renders issuable confidential checkbox', () => { + const confidentialCheckboxEl = wrapper.findByTestId('issuable-confidential'); + expect(confidentialCheckboxEl.exists()).toBe(true); + + expect(confidentialCheckboxEl.findComponent(GlFormGroup).exists()).toBe(true); + expect(confidentialCheckboxEl.findComponent(GlFormGroup).attributes('label')).toBe( + 'Confidentiality', + ); + + expect(confidentialCheckboxEl.findComponent(GlFormCheckbox).exists()).toBe(true); + expect(confidentialCheckboxEl.findComponent(GlFormCheckbox).text()).toBe( + 'This test case is confidential and should only be visible to team members with at least Reporter access.', + ); + }); + it('renders labels select field', () => { - const labelsSelectEl = wrapper.find('[data-testid="issuable-labels"]'); + const labelsSelectEl = wrapper.findByTestId('issuable-labels'); expect(labelsSelectEl.exists()).toBe(true); expect(labelsSelectEl.find('label').text()).toBe('Labels'); @@ -111,7 +129,7 @@ describe('IssuableForm', () => { it('renders contents for slot "actions"', () => { const buttonEl = wrapper - .find('[data-testid="issuable-create-actions"]') + .findByTestId('issuable-create-actions') .find('button.js-issuable-save'); expect(buttonEl.exists()).toBe(true); -- GitLab