From 9cc33a92d0d4e79d7ca4a1e7b4400fbbdda33933 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 23 Jan 2023 06:08:33 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../commit/components/branches_dropdown.vue | 67 +++++++--- .../projects/commit/components/form_modal.vue | 13 +- .../commit/components/projects_dropdown.vue | 57 +++++---- .../geo/secondary_proxy/index.md | 6 +- doc/administration/index.md | 2 +- doc/api/index.md | 2 +- doc/integration/index.md | 2 +- .../application_security/dast/proxy-based.md | 3 +- .../projects/commit/cherry_pick_spec.rb | 4 +- .../components/branches_dropdown_spec.js | 115 +++++++++++++++++- .../components/projects_dropdown_spec.js | 64 +++++++++- 11 files changed, 275 insertions(+), 60 deletions(-) diff --git a/app/assets/javascripts/projects/commit/components/branches_dropdown.vue b/app/assets/javascripts/projects/commit/components/branches_dropdown.vue index a1fc3f1a731..a037e721677 100644 --- a/app/assets/javascripts/projects/commit/components/branches_dropdown.vue +++ b/app/assets/javascripts/projects/commit/components/branches_dropdown.vue @@ -1,5 +1,11 @@ diff --git a/app/assets/javascripts/projects/commit/components/form_modal.vue b/app/assets/javascripts/projects/commit/components/form_modal.vue index b31ba4a100c..1febe8ceaab 100644 --- a/app/assets/javascripts/projects/commit/components/form_modal.vue +++ b/app/assets/javascripts/projects/commit/components/form_modal.vue @@ -141,7 +141,11 @@ export default { :value="targetProjectId" /> - + - + -import { GlCollapsibleListbox } from '@gitlab/ui'; +import { GlDropdown, GlSearchBoxByType, GlDropdownItem, GlDropdownText } from '@gitlab/ui'; import { mapGetters, mapState } from 'vuex'; import { I18N_NO_RESULTS_MESSAGE, @@ -10,7 +10,10 @@ import { export default { name: 'ProjectsDropdown', components: { - GlCollapsibleListbox, + GlDropdown, + GlSearchBoxByType, + GlDropdownItem, + GlDropdownText, }, props: { value: { @@ -38,20 +41,17 @@ export default { project.name.toLowerCase().includes(lowerCasedFilterTerm), ); }, - listboxItems() { - return this.filteredResults.map(({ id, name }) => ({ value: id, text: name })); - }, selectedProject() { return this.sortedProjects.find((project) => project.id === this.targetProjectId) || {}; }, }, methods: { - selectProject(value) { - this.$emit('selectProject', value); - - // when we select a project, we want the dropdown to filter to the selected project - const project = this.listboxItems.find((x) => x.value === value); - this.filterTerm = project?.text || ''; + selectProject(project) { + this.$emit('selectProject', project.id); + this.filterTerm = project.name; // when we select a project, we want the dropdown to filter to the selected project + }, + isSelected(selectedProject) { + return selectedProject === this.selectedProject; }, filterTermChanged(value) { this.filterTerm = value; @@ -60,15 +60,28 @@ export default { }; diff --git a/doc/administration/geo/secondary_proxy/index.md b/doc/administration/geo/secondary_proxy/index.md index ac8b88a91d5..ef3ce9237e4 100644 --- a/doc/administration/geo/secondary_proxy/index.md +++ b/doc/administration/geo/secondary_proxy/index.md @@ -122,9 +122,9 @@ for details. - [Viewing projects and designs data from a primary site is not possible when using a unified URL](../index.md#view-replication-data-on-the-primary-site). - When secondary proxying is used together with separate URLs, registering [GitLab runners](https://docs.gitlab.com/runner/) to clone from -secondary sites is not supported. The runner registration will succeed, but the clone URL will default to the primary site. The runner +secondary sites is not supported. The runner registration succeeds, but the clone URL defaults to the primary site. The runner [clone URL](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) is configured per GitLab deployment -and cannot be configured per Geo site. Therefore, all runners will clone from the primary site (or configured clone URL) irrespective of +and cannot be configured per Geo site. Therefore, all runners clone from the primary site (or configured clone URL) irrespective of which Geo site they register on. For information about GitLab CI using a specific Geo secondary to clone from, see issue [3294](https://gitlab.com/gitlab-org/gitlab/-/issues/3294#note_1009488466). @@ -147,7 +147,7 @@ secondary Geo sites are able to support write requests. Certain **read** request sites for improved latency and bandwidth nearby. All write requests are proxied to the primary site. The following table details the components currently tested through the Geo secondary site Workhorse proxy. -It does not cover all data types, more will be added in the future as they are tested. +It does not cover all data types. | Feature / component | Accelerated reads? | |:----------------------------------------------------|:-----------------------| diff --git a/doc/administration/index.md b/doc/administration/index.md index 1059424da27..9f4477bcbf7 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated description: 'Learn how to install, configure, update, and maintain your GitLab instance.' --- -# Administrator documentation **(FREE SELF)** +# Administer GitLab **(FREE SELF)** If you use GitLab.com, only GitLab team members have access to administration tools and settings. If you use a self-managed GitLab instance, learn how to administer it. diff --git a/doc/api/index.md b/doc/api/index.md index d3f652dccc3..f3a6d0e30ce 100644 --- a/doc/api/index.md +++ b/doc/api/index.md @@ -4,7 +4,7 @@ group: Integrations info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# API Docs **(FREE)** +# GitLab APIs **(FREE)** Use the GitLab APIs to automate GitLab. diff --git a/doc/integration/index.md b/doc/integration/index.md index bdf6475b6d2..d778d7c0856 100644 --- a/doc/integration/index.md +++ b/doc/integration/index.md @@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w comments: false --- -# GitLab integrations **(FREE)** +# Integrate with GitLab **(FREE)** You can integrate GitLab with external services for enhanced functionality. diff --git a/doc/user/application_security/dast/proxy-based.md b/doc/user/application_security/dast/proxy-based.md index fc78018bdad..605d243f72e 100644 --- a/doc/user/application_security/dast/proxy-based.md +++ b/doc/user/application_security/dast/proxy-based.md @@ -360,13 +360,14 @@ including a large number of false positives. |:------------------------------------------------|:--------------|:------------------------------| | `DAST_ADVERTISE_SCAN` | boolean | Set to `true` to add a `Via` header to every request sent, advertising that the request was sent as part of a GitLab DAST scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334947) in GitLab 14.1. | | `DAST_AGGREGATE_VULNERABILITIES` | boolean | Vulnerability aggregation is set to `true` by default. To disable this feature and see each vulnerability individually set to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254043) in GitLab 14.0. | +| `DAST_ALLOWED_HOSTS` | Comma-separated list of strings | Hostnames included in this variable are considered in scope when crawled. By default the `DAST_WEBSITE` hostname is included in the allowed hosts list. Headers set using `DAST_REQUEST_HEADERS` are added to every request made to these hostnames. Example, `site.com,another.com`. | | `DAST_API_HOST_OVERRIDE` 1 | string | **{warning}** **[Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/383467)** in GitLab 15.7. Replaced by [DAST API scan](../dast_api/index.md#available-cicd-variables). Used to override domains defined in API specification files. Only supported when importing the API specification from a URL. Example: `example.com:8080`. | | `DAST_API_SPECIFICATION` 1 | URL or string | **{warning}** **[Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/383467)** in GitLab 15.7. Replaced by [DAST API scan](../dast_api/index.md#available-cicd-variables). The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. The variable `DAST_WEBSITE` must be specified if this is omitted. | | `DAST_AUTH_EXCLUDE_URLS` | URLs | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/289959)** in GitLab 14.0. Replaced by `DAST_EXCLUDE_URLS`. The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. | | `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false`. | | `DAST_DEBUG` 1 | boolean | Enable debug message output. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. | | `DAST_EXCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to exclude them from running during the scan. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). For example, `HTTP Parameter Override` has a rule ID of `10026`. Cannot be used when `DAST_ONLY_INCLUDE_RULES` is set. **Note:** In earlier versions of GitLab the excluded rules were executed but vulnerabilities they generated were suppressed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118641) in GitLab 12.10. | -| `DAST_EXCLUDE_URLS` 1 | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Example, `http://example.com/sign-out`. | +| `DAST_EXCLUDE_URLS` 1 | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Example, `http://example.com/sign-out`. | | `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | boolean | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/293595)** in GitLab 14.0. Set to `true` to require domain validation when running DAST full scans. Default: `false` | | `DAST_FULL_SCAN_ENABLED` 1 | boolean | Set to `true` to run a [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of a [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Default: `false` | | `DAST_HTML_REPORT` | string | **{warning}** **[Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/384340)** in GitLab 15.7. The filename of the HTML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. | diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb index 4b9b692b652..dc8b84283a1 100644 --- a/spec/features/projects/commit/cherry_pick_spec.rb +++ b/spec/features/projects/commit/cherry_pick_spec.rb @@ -78,9 +78,9 @@ RSpec.describe 'Cherry-pick Commits', :js, feature_category: :source_code_manage end page.within("#{modal_selector} .dropdown-menu") do - fill_in 'Search branches', with: 'feature' + find('[data-testid="dropdown-search-box"]').set('feature') wait_for_requests - find('.gl-dropdown-item-text-wrapper', exact_text: 'feature').click + click_button 'feature' end submit_cherry_pick diff --git a/spec/frontend/projects/commit/components/branches_dropdown_spec.js b/spec/frontend/projects/commit/components/branches_dropdown_spec.js index 7334e007e18..a84dd246f5d 100644 --- a/spec/frontend/projects/commit/components/branches_dropdown_spec.js +++ b/spec/frontend/projects/commit/components/branches_dropdown_spec.js @@ -1,8 +1,9 @@ -import { GlCollapsibleListbox } from '@gitlab/ui'; +import { GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import BranchesDropdown from '~/projects/commit/components/branches_dropdown.vue'; Vue.use(Vuex); @@ -33,7 +34,12 @@ describe('BranchesDropdown', () => { }), ); }; - const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox); + + const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); + const findSearchBoxByType = () => wrapper.findComponent(GlSearchBoxByType); + const findDropdownItemByIndex = (index) => wrapper.findAllComponents(GlDropdownItem).at(index); + const findNoResults = () => wrapper.findByTestId('empty-result-message'); + const findLoading = () => wrapper.findByTestId('dropdown-text-loading-icon'); afterEach(() => { wrapper.destroy(); @@ -49,6 +55,72 @@ describe('BranchesDropdown', () => { it('invokes fetchBranches', () => { expect(spyFetchBranches).toHaveBeenCalled(); }); + + describe('with a value but visually blanked', () => { + beforeEach(() => { + createComponent({ value: '_main_', blanked: true }, { branch: '_main_' }); + }); + + it('renders all branches', () => { + expect(findAllDropdownItems()).toHaveLength(3); + expect(findDropdownItemByIndex(0).text()).toBe('_main_'); + expect(findDropdownItemByIndex(1).text()).toBe('_branch_1_'); + expect(findDropdownItemByIndex(2).text()).toBe('_branch_2_'); + }); + + it('selects the active branch', () => { + expect(wrapper.vm.isSelected('_main_')).toBe(true); + }); + }); + }); + + describe('Loading states', () => { + it('shows loading icon while fetching', () => { + createComponent({ value: '' }, { isFetching: true }); + + expect(findLoading().isVisible()).toBe(true); + }); + + it('does not show loading icon', () => { + createComponent({ value: '' }); + + expect(findLoading().isVisible()).toBe(false); + }); + }); + + describe('No branches found', () => { + beforeEach(() => { + createComponent({ value: '_non_existent_branch_' }); + }); + + it('renders empty results message', () => { + expect(findNoResults().text()).toBe('No matching results'); + }); + + it('shows GlSearchBoxByType with default attributes', () => { + expect(findSearchBoxByType().exists()).toBe(true); + expect(findSearchBoxByType().vm.$attrs).toMatchObject({ + placeholder: 'Search branches', + debounce: DEFAULT_DEBOUNCE_AND_THROTTLE_MS, + }); + }); + }); + + describe('Search term is empty', () => { + beforeEach(() => { + createComponent({ value: '' }); + }); + + it('renders all branches when search term is empty', () => { + expect(findAllDropdownItems()).toHaveLength(3); + expect(findDropdownItemByIndex(0).text()).toBe('_main_'); + expect(findDropdownItemByIndex(1).text()).toBe('_branch_1_'); + expect(findDropdownItemByIndex(2).text()).toBe('_branch_2_'); + }); + + it('should not be selected on the inactive branch', () => { + expect(wrapper.vm.isSelected('_main_')).toBe(false); + }); }); describe('When searching', () => { @@ -59,7 +131,7 @@ describe('BranchesDropdown', () => { it('invokes fetchBranches', async () => { const spy = jest.spyOn(wrapper.vm, 'fetchBranches'); - findDropdown().vm.$emit('search', '_anything_'); + findSearchBoxByType().vm.$emit('input', '_anything_'); await nextTick(); @@ -68,13 +140,46 @@ describe('BranchesDropdown', () => { }); }); + describe('Branches found', () => { + beforeEach(() => { + createComponent({ value: '_branch_1_' }, { branch: '_branch_1_' }); + }); + + it('renders only the branch searched for', () => { + expect(findAllDropdownItems()).toHaveLength(1); + expect(findDropdownItemByIndex(0).text()).toBe('_branch_1_'); + }); + + it('should not display empty results message', () => { + expect(findNoResults().exists()).toBe(false); + }); + + it('should signify this branch is selected', () => { + expect(wrapper.vm.isSelected('_branch_1_')).toBe(true); + }); + + it('should signify the branch is not selected', () => { + expect(wrapper.vm.isSelected('_not_selected_branch_')).toBe(false); + }); + + describe('Custom events', () => { + it('should emit selectBranch if an branch is clicked', () => { + findDropdownItemByIndex(0).vm.$emit('click'); + + expect(wrapper.emitted('selectBranch')).toEqual([['_branch_1_']]); + expect(wrapper.vm.searchTerm).toBe('_branch_1_'); + }); + }); + }); + describe('Case insensitive for search term', () => { beforeEach(() => { createComponent({ value: '_BrAnCh_1_' }); }); - it('returns only the branch searched for', () => { - expect(findDropdown().props('items')).toEqual([{ text: '_branch_1_', value: '_branch_1_' }]); + it('renders only the branch searched for', () => { + expect(findAllDropdownItems()).toHaveLength(1); + expect(findDropdownItemByIndex(0).text()).toBe('_branch_1_'); }); }); }); diff --git a/spec/frontend/projects/commit/components/projects_dropdown_spec.js b/spec/frontend/projects/commit/components/projects_dropdown_spec.js index 0e213ff388a..bb20918e0cd 100644 --- a/spec/frontend/projects/commit/components/projects_dropdown_spec.js +++ b/spec/frontend/projects/commit/components/projects_dropdown_spec.js @@ -1,4 +1,4 @@ -import { GlCollapsibleListbox } from '@gitlab/ui'; +import { GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import Vuex from 'vuex'; @@ -35,23 +35,78 @@ describe('ProjectsDropdown', () => { ); }; - const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox); + const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); + const findSearchBoxByType = () => wrapper.findComponent(GlSearchBoxByType); + const findDropdownItemByIndex = (index) => wrapper.findAllComponents(GlDropdownItem).at(index); + const findNoResults = () => wrapper.findByTestId('empty-result-message'); afterEach(() => { wrapper.destroy(); spyFetchProjects.mockReset(); }); + describe('No projects found', () => { + beforeEach(() => { + createComponent('_non_existent_project_'); + }); + + it('renders empty results message', () => { + expect(findNoResults().text()).toBe('No matching results'); + }); + + it('shows GlSearchBoxByType with default attributes', () => { + expect(findSearchBoxByType().exists()).toBe(true); + expect(findSearchBoxByType().vm.$attrs).toMatchObject({ + placeholder: 'Search projects', + }); + }); + }); + + describe('Search term is empty', () => { + beforeEach(() => { + createComponent(''); + }); + + it('renders all projects when search term is empty', () => { + expect(findAllDropdownItems()).toHaveLength(3); + expect(findDropdownItemByIndex(0).text()).toBe('_project_1_'); + expect(findDropdownItemByIndex(1).text()).toBe('_project_2_'); + expect(findDropdownItemByIndex(2).text()).toBe('_project_3_'); + }); + + it('should not be selected on the inactive project', () => { + expect(wrapper.vm.isSelected('_project_1_')).toBe(false); + }); + }); + describe('Projects found', () => { beforeEach(() => { createComponent('_project_1_', { targetProjectId: '1' }); }); + it('renders only the project searched for', () => { + expect(findAllDropdownItems()).toHaveLength(1); + expect(findDropdownItemByIndex(0).text()).toBe('_project_1_'); + }); + + it('should not display empty results message', () => { + expect(findNoResults().exists()).toBe(false); + }); + + it('should signify this project is selected', () => { + expect(findDropdownItemByIndex(0).props('isChecked')).toBe(true); + }); + + it('should signify the project is not selected', () => { + expect(wrapper.vm.isSelected('_not_selected_project_')).toBe(false); + }); + describe('Custom events', () => { it('should emit selectProject if a project is clicked', () => { - findDropdown().vm.$emit('select', '1'); + findDropdownItemByIndex(0).vm.$emit('click'); expect(wrapper.emitted('selectProject')).toEqual([['1']]); + expect(wrapper.vm.filterTerm).toBe('_project_1_'); }); }); }); @@ -62,7 +117,8 @@ describe('ProjectsDropdown', () => { }); it('renders only the project searched for', () => { - expect(findDropdown().props('items')).toEqual([{ text: '_project_1_', value: '1' }]); + expect(findAllDropdownItems()).toHaveLength(1); + expect(findDropdownItemByIndex(0).text()).toBe('_project_1_'); }); }); });