diff --git a/app/assets/javascripts/boards/components/board_filtered_search.vue b/app/assets/javascripts/boards/components/board_filtered_search.vue index 45192b5304a..95d4fd5bc0a 100644 --- a/app/assets/javascripts/boards/components/board_filtered_search.vue +++ b/app/assets/javascripts/boards/components/board_filtered_search.vue @@ -151,10 +151,10 @@ export default { }); } - if (this.filterParams['not[iteration_id]']) { + if (this.filterParams['not[iterationId]']) { filteredSearchValue.push({ - type: 'iteration_id', - value: { data: this.filterParams['not[iteration_id]'], operator: '!=' }, + type: 'iteration', + value: { data: this.filterParams['not[iterationId]'], operator: '!=' }, }); } diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js deleted file mode 100644 index 72586970008..00000000000 --- a/app/assets/javascripts/boards/filtered_search_boards.js +++ /dev/null @@ -1,81 +0,0 @@ -import { transformBoardConfig } from 'ee_else_ce/boards/boards_util'; -import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager'; -import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; -import { updateHistory } from '~/lib/utils/url_utility'; -import FilteredSearchContainer from '../filtered_search/container'; -import vuexstore from './stores'; - -export default class FilteredSearchBoards extends FilteredSearchManager { - constructor(store, updateUrl = false, cantEdit = []) { - super({ - page: 'boards', - isGroupDecendent: true, - stateFiltersSelector: '.issues-state-filters', - isGroup: IS_EE, - useDefaultState: false, - filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, - }); - - this.store = store; - this.updateUrl = updateUrl; - - // Issue boards is slightly different, we handle all the requests async - // instead or reloading the page, we just re-fire the list ajax requests - this.isHandledAsync = true; - this.cantEdit = cantEdit.filter((i) => typeof i === 'string'); - this.cantEditWithValue = cantEdit.filter((i) => typeof i === 'object'); - - if (vuexstore.state.boardConfig) { - const boardConfigPath = transformBoardConfig(vuexstore.state.boardConfig); - // TODO Refactor: https://gitlab.com/gitlab-org/gitlab/-/issues/329274 - // here we are using "window.location.search" as a temporary store - // only to unpack the params and do another validation inside - // 'performSearch' and 'setFilter' vuex actions. - if (boardConfigPath !== '') { - const filterPath = window.location.search ? `${window.location.search}&` : '?'; - updateHistory({ - url: `${filterPath}${transformBoardConfig(vuexstore.state.boardConfig)}`, - }); - } - } - } - - updateObject(path) { - const groupByParam = new URLSearchParams(window.location.search).get('group_by'); - this.store.path = `${path.substr(1)}${groupByParam ? `&group_by=${groupByParam}` : ''}`; - - updateHistory({ - url: `?${path.substr(1)}${groupByParam ? `&group_by=${groupByParam}` : ''}`, - }); - vuexstore.dispatch('performSearch'); - } - - removeTokens() { - const tokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token'); - - // Remove all the tokens as they will be replaced by the search manager - [].forEach.call(tokens, (el) => { - el.parentNode.removeChild(el); - }); - - this.filteredSearchInput.value = ''; - } - - updateTokens() { - this.removeTokens(); - - this.loadSearchParamsFromURL(); - - // Get the placeholder back if search is empty - this.filteredSearchInput.dispatchEvent(new Event('input')); - } - - canEdit(tokenName, tokenValue) { - if (this.cantEdit.includes(tokenName)) return false; - return ( - this.cantEditWithValue.findIndex( - (token) => token.name === tokenName && token.value === tokenValue, - ) === -1 - ); - } -} diff --git a/app/assets/javascripts/boards/graphql.js b/app/assets/javascripts/boards/graphql.js index 95863d4d5ac..d066a5d002e 100644 --- a/app/assets/javascripts/boards/graphql.js +++ b/app/assets/javascripts/boards/graphql.js @@ -10,5 +10,6 @@ export const gqlClient = createDefaultClient( return object.__typename === 'BoardList' ? object.iid : defaultDataIdFromObject(object); }, }, + batchMax: 2, }, ); diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index f6073f9d981..d2a64246aa7 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -8,8 +8,6 @@ import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_t import BoardApp from '~/boards/components/board_app.vue'; import '~/boards/filters/due_date_filters'; import { issuableTypes } from '~/boards/constants'; -import eventHub from '~/boards/eventhub'; -import FilteredSearchBoards from '~/boards/filtered_search_boards'; import initBoardsFilteredSearch from '~/boards/mount_filtered_search_issue_boards'; import store from '~/boards/stores'; import toggleFocusMode from '~/boards/toggle_focus'; @@ -50,17 +48,6 @@ function mountBoardApp(el) { }, }); - if (!gon?.features?.issueBoardsFilteredSearch) { - // Warning: FilteredSearchBoards has an implicit dependency on the Vuex state 'boardConfig' - // Improve this situation in the future. - const filterManager = new FilteredSearchBoards({ path: '' }, true, []); - filterManager.setup(); - - eventHub.$on('updateTokens', () => { - filterManager.updateTokens(); - }); - } - // eslint-disable-next-line no-new new Vue({ el, @@ -110,10 +97,14 @@ export default () => { } }); - if (gon?.features?.issueBoardsFilteredSearch) { - const { releasesFetchPath } = $boardApp.dataset; - initBoardsFilteredSearch(apolloProvider, isLoggedIn(), releasesFetchPath); - } + const { releasesFetchPath, epicFeatureAvailable, iterationFeatureAvailable } = $boardApp.dataset; + initBoardsFilteredSearch( + apolloProvider, + isLoggedIn(), + releasesFetchPath, + parseBoolean(epicFeatureAvailable), + parseBoolean(iterationFeatureAvailable), + ); mountBoardApp($boardApp); diff --git a/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js b/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js index 327fb9ba8d7..bb659eb075a 100644 --- a/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js +++ b/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js @@ -4,7 +4,13 @@ import store from '~/boards/stores'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { queryToObject } from '~/lib/utils/url_utility'; -export default (apolloProvider, isSignedIn, releasesFetchPath) => { +export default ( + apolloProvider, + isSignedIn, + releasesFetchPath, + epicFeatureAvailable, + iterationFeatureAvailable, +) => { const el = document.getElementById('js-issue-board-filtered-search'); const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true }); @@ -23,6 +29,8 @@ export default (apolloProvider, isSignedIn, releasesFetchPath) => { initialFilterParams, isSignedIn, releasesFetchPath, + epicFeatureAvailable, + iterationFeatureAvailable, }, store, // TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/324094 apolloProvider, diff --git a/app/assets/javascripts/issues/create_merge_request_dropdown.js b/app/assets/javascripts/issues/create_merge_request_dropdown.js index a3752c7043c..247f8dd0bd6 100644 --- a/app/assets/javascripts/issues/create_merge_request_dropdown.js +++ b/app/assets/javascripts/issues/create_merge_request_dropdown.js @@ -10,6 +10,7 @@ import ISetter from '~/filtered_search/droplab/plugins/input_setter'; import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { __, sprintf } from '~/locale'; +import { mergeUrlParams } from '~/lib/utils/url_utility'; // Todo: Remove this when fixing issue in input_setter plugin const InputSetter = { ...ISetter }; @@ -171,12 +172,21 @@ export default class CreateMergeRequestDropdown { this.isCreatingMergeRequest = true; return this.createBranch().then(() => { - window.location.href = canCreateConfidentialMergeRequest() + let path = canCreateConfidentialMergeRequest() ? this.createMrPath.replace( this.projectPath, confidentialMergeRequestState.selectedProject.pathWithNamespace, ) : this.createMrPath; + path = mergeUrlParams( + { + 'merge_request[target_branch]': this.refInput.value, + 'merge_request[source_branch]': this.branchInput.value, + }, + path, + ); + + window.location.href = path; }); }); } diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js b/app/assets/javascripts/protected_branches/protected_branch_edit.js index 86273cfdda6..93eaebe2af3 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_edit.js +++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js @@ -3,6 +3,7 @@ import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; import AccessDropdown from '~/projects/settings/access_dropdown'; +import { initToggle } from '~/toggles'; import { ACCESS_LEVELS, LEVEL_TYPES } from './constants'; export default class ProtectedBranchEdit { @@ -14,8 +15,6 @@ export default class ProtectedBranchEdit { this.$wrap = options.$wrap; this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge'); this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push'); - this.$forcePushToggle = this.$wrap.find('.js-force-push-toggle'); - this.$codeOwnerToggle = this.$wrap.find('.js-code-owner-toggle'); this.$wraps[ACCESS_LEVELS.MERGE] = this.$allowedToMergeDropdown.closest( `.${ACCESS_LEVELS.MERGE}-container`, @@ -25,38 +24,45 @@ export default class ProtectedBranchEdit { ); this.buildDropdowns(); - this.bindEvents(); + this.initToggles(); } - bindEvents() { - this.$forcePushToggle.on('click', this.onForcePushToggleClick.bind(this)); + initToggles() { + const wrap = this.$wrap.get(0); + + const forcePushToggle = initToggle(wrap.querySelector('.js-force-push-toggle')); + forcePushToggle.$on('change', (value) => { + forcePushToggle.isLoading = true; + forcePushToggle.disabled = true; + this.updateProtectedBranch( + { + allow_force_push: value, + }, + () => { + forcePushToggle.isLoading = false; + forcePushToggle.disabled = false; + }, + ); + }); + if (this.hasLicense) { - this.$codeOwnerToggle.on('click', this.onCodeOwnerToggleClick.bind(this)); + const codeOwnerToggle = initToggle(wrap.querySelector('.js-code-owner-toggle')); + codeOwnerToggle.$on('change', (value) => { + codeOwnerToggle.isLoading = true; + codeOwnerToggle.disabled = true; + this.updateProtectedBranch( + { + code_owner_approval_required: value, + }, + () => { + codeOwnerToggle.isLoading = false; + codeOwnerToggle.disabled = false; + }, + ); + }); } } - onForcePushToggleClick() { - this.$forcePushToggle.toggleClass('is-checked'); - this.$forcePushToggle.prop('disabled', true); - - const formData = { - allow_force_push: this.$forcePushToggle.hasClass('is-checked'), - }; - - this.updateProtectedBranch(formData, () => this.$forcePushToggle.prop('disabled', false)); - } - - onCodeOwnerToggleClick() { - this.$codeOwnerToggle.toggleClass('is-checked'); - this.$codeOwnerToggle.prop('disabled', true); - - const formData = { - code_owner_approval_required: this.$codeOwnerToggle.hasClass('is-checked'), - }; - - this.updateProtectedBranch(formData, () => this.$codeOwnerToggle.prop('disabled', false)); - } - updateProtectedBranch(formData, callback) { axios .patch(this.$wrap.data('url'), { diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb index 6fac6fcf426..641b3adb12b 100644 --- a/app/controllers/groups/boards_controller.rb +++ b/app/controllers/groups/boards_controller.rb @@ -7,7 +7,6 @@ class Groups::BoardsController < Groups::ApplicationController before_action :assign_endpoint_vars before_action do - push_frontend_feature_flag(:issue_boards_filtered_search, group, default_enabled: :yaml) push_frontend_feature_flag(:board_multi_select, group, default_enabled: :yaml) push_frontend_feature_flag(:iteration_cadences, group, default_enabled: :yaml) experiment(:prominent_create_board_btn, subject: current_user) do |e| diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 0170cff6160..c44a0830e2e 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -7,7 +7,6 @@ class Projects::BoardsController < Projects::ApplicationController before_action :check_issues_available! before_action :assign_endpoint_vars before_action do - push_frontend_feature_flag(:issue_boards_filtered_search, project&.group, default_enabled: :yaml) push_frontend_feature_flag(:board_multi_select, project, default_enabled: :yaml) push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml) experiment(:prominent_create_board_btn, subject: current_user) do |e| diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb index 47b72df32a2..8695fe96652 100644 --- a/app/presenters/blob_presenter.rb +++ b/app/presenters/blob_presenter.rb @@ -56,7 +56,7 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated end def replace_path - url_helpers.project_create_blob_path(project, ref_qualified_path) + url_helpers.project_update_blob_path(project, ref_qualified_path) end def pipeline_editor_path diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml index 3206fca6bcd..8f80c9fdc6c 100644 --- a/app/views/profiles/chat_names/_chat_name.html.haml +++ b/app/views/profiles/chat_names/_chat_name.html.haml @@ -24,4 +24,4 @@ = _('Never') %td - = link_to _('Remove'), profile_chat_name_path(chat_name), method: :delete, class: 'gl-button btn btn-danger float-right', data: { confirm: _('Are you sure you want to revoke this nickname?') } + = link_to _('Remove'), profile_chat_name_path(chat_name), method: :delete, class: 'gl-button btn btn-danger float-right', aria: { label: _('Remove') }, data: { confirm: _('Are you sure you want to remove this nickname?'), confirm_btn_variant: 'danger' } diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index b02c6b65359..37a79a50fb1 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -5,10 +5,6 @@ - placeholder = local_assigns[:placeholder] || _('Search or filter results...') - block_css_class = type != :productivity_analytics ? 'row-content-block second-block' : '' - is_epic_board = board&.to_type == "EpicBoard" -- if @group.present? - - ff_resource = @group -- else - - ff_resource = board&.resource_parent&.group - if is_epic_board - user_can_admin_list = can?(current_user, :admin_epic_board_list, board.resource_parent) @@ -31,7 +27,7 @@ = check_box_tag checkbox_id, nil, false, class: "check-all-issues left" - if is_epic_board #js-board-filtered-search{ data: { full_path: @group&.full_path } } - - elsif Feature.enabled?(:issue_boards_filtered_search, ff_resource, default_enabled: :yaml) && board + - elsif board #js-issue-board-filtered-search - else .issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row diff --git a/app/views/shared/projects/protected_branches/_update_protected_branch.html.haml b/app/views/shared/projects/protected_branches/_update_protected_branch.html.haml index 3cbe35e5c15..f3544b55b63 100644 --- a/app/views/shared/projects/protected_branches/_update_protected_branch.html.haml +++ b/app/views/shared/projects/protected_branches/_update_protected_branch.html.haml @@ -34,4 +34,8 @@ = _('Members of %{group} can also push to this branch: %{branch}') % { group: (group_push_access_levels.size > 1 ? 'these groups' : 'this group'), branch: group_push_access_levels.map(&:humanize).to_sentence } %td - = render "shared/buttons/project_feature_toggle", is_checked: protected_branch.allow_force_push, label: s_("ProtectedBranch|Toggle allowed to force push"), class_list: "js-force-push-toggle project-feature-toggle", data: { qa_selector: 'force_push_toggle_button', qa_branch_name: protected_branch.name } + = render "shared/gl_toggle", + classes: 'js-force-push-toggle', + label: s_("ProtectedBranch|Toggle allowed to force push"), + is_checked: protected_branch.allow_force_push, + label_position: 'hidden' diff --git a/config/feature_flags/development/issue_boards_filtered_search.yml b/config/feature_flags/development/exit_registration_verification.yml similarity index 53% rename from config/feature_flags/development/issue_boards_filtered_search.yml rename to config/feature_flags/development/exit_registration_verification.yml index dadbbd1b2fc..c544ebc2943 100644 --- a/config/feature_flags/development/issue_boards_filtered_search.yml +++ b/config/feature_flags/development/exit_registration_verification.yml @@ -1,8 +1,8 @@ --- -name: issue_boards_filtered_search -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61752 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/331649 -milestone: '14.1' +name: exit_registration_verification +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80286 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352397 +milestone: '14.8' type: development -group: group::product planning -default_enabled: true +group: group::activation +default_enabled: false diff --git a/config/routes.rb b/config/routes.rb index a57795bea0c..910ddb2e571 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,7 +69,10 @@ Rails.application.routes.draw do resources :groups, only: [:new, :create] resources :projects, only: [:new, :create] resources :groups_projects, only: [:new, :create] do - post :import, on: :collection + collection do + post :import + put :exit + end end draw :verification end diff --git a/config/webpack.config.js b/config/webpack.config.js index 7b559c8881b..c7e5bf04a20 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -40,11 +40,16 @@ const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/c const IS_PRODUCTION = process.env.NODE_ENV === 'production'; const IS_DEV_SERVER = process.env.WEBPACK_SERVE === 'true'; -const { DEV_SERVER_HOST, DEV_SERVER_PUBLIC_ADDR } = process.env; +const { + DEV_SERVER_HOST, + DEV_SERVER_PUBLIC_ADDR, + DEV_SERVER_TYPE, + DEV_SERVER_SSL_KEY, + DEV_SERVER_SSL_CERT, +} = process.env; const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10); const DEV_SERVER_ALLOWED_HOSTS = process.env.DEV_SERVER_ALLOWED_HOSTS && process.env.DEV_SERVER_ALLOWED_HOSTS.split(','); -const DEV_SERVER_HTTPS = process.env.DEV_SERVER_HTTPS && process.env.DEV_SERVER_HTTPS !== 'false'; const DEV_SERVER_LIVERELOAD = IS_DEV_SERVER && process.env.DEV_SERVER_LIVERELOAD !== 'false'; const INCREMENTAL_COMPILER_ENABLED = IS_DEV_SERVER && @@ -709,7 +714,6 @@ module.exports = { }, host: DEV_SERVER_HOST || 'localhost', port: DEV_SERVER_PORT || 3808, - https: DEV_SERVER_HTTPS, hot: DEV_SERVER_LIVERELOAD, // The following settings are mainly needed for HMR support in gitpod. // Per default only local hosts are allowed, but here we could @@ -720,6 +724,13 @@ module.exports = { client: { ...(DEV_SERVER_PUBLIC_ADDR ? { webSocketURL: DEV_SERVER_PUBLIC_ADDR } : {}), }, + server: { + type: DEV_SERVER_TYPE || 'http', + options: { + key: DEV_SERVER_SSL_KEY, + cert: DEV_SERVER_SSL_CERT, + }, + }, }, devtool: NO_SOURCEMAPS ? false : devtool, diff --git a/lib/gitlab/ci/parsers/security/validators/schema_validator.rb b/lib/gitlab/ci/parsers/security/validators/schema_validator.rb index 651ed23eb25..1dce0cb18a8 100644 --- a/lib/gitlab/ci/parsers/security/validators/schema_validator.rb +++ b/lib/gitlab/ci/parsers/security/validators/schema_validator.rb @@ -6,6 +6,41 @@ module Gitlab module Security module Validators class SchemaValidator + # https://docs.gitlab.com/ee/update/deprecations.html#147 + SUPPORTED_VERSIONS = { + cluster_image_scanning: %w[14.0.4 14.0.5 14.0.6 14.1.0], + container_scanning: %w[14.0.0 14.0.1 14.0.2 14.0.3 14.0.4 14.0.5 14.0.6 14.1.0], + coverage_fuzzing: %w[14.0.0 14.0.1 14.0.2 14.0.3 14.0.4 14.0.5 14.0.6 14.1.0], + dast: %w[14.0.0 14.0.1 14.0.2 14.0.3 14.0.4 14.0.5 14.0.6 14.1.0], + api_fuzzing: %w[14.0.0 14.0.1 14.0.2 14.0.3 14.0.4 14.0.5 14.0.6 14.1.0], + dependency_scanning: %w[14.0.0 14.0.1 14.0.2 14.0.3 14.0.4 14.0.5 14.0.6 14.1.0], + sast: %w[14.0.0 14.0.1 14.0.2 14.0.3 14.0.4 14.0.5 14.0.6 14.1.0], + secret_detection: %w[14.0.0 14.0.1 14.0.2 14.0.3 14.0.4 14.0.5 14.0.6 14.1.0] + }.freeze + + # https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/tags + PREVIOUS_RELEASES = %w[10.0.0 12.0.0 12.1.0 13.0.0 + 13.1.0 2.3.0-rc1 2.3.0-rc1 2.3.1-rc1 2.3.2-rc1 2.3.3-rc1 + 2.4.0-rc1 3.0.0 3.0.0-rc1 3.1.0-rc1 4.0.0-rc1 5.0.0-rc1 + 5.0.1-rc1 6.0.0-rc1 6.0.1-rc1 6.1.0-rc1 7.0.0-rc1 7.0.1-rc1 + 8.0.0-rc1 8.0.1-rc1 8.1.0-rc1 9.0.0-rc1].freeze + + # These come from https://app.periscopedata.com/app/gitlab/895813/Secure-Scan-metrics?widget=12248944&udv=1385516 + KNOWN_VERSIONS_TO_DEPRECATE = %w[0.1 1.0 1.0.0 1.2 1.3 10.0.0 12.1.0 13.1.0 2.0 2.1 2.1.0 2.3 2.3.0 2.4 3.0 3.0.0 3.0.6 3.13.2 V2.7.0].freeze + + VERSIONS_TO_DEPRECATE_IN_15_0 = (PREVIOUS_RELEASES + KNOWN_VERSIONS_TO_DEPRECATE).freeze + + DEPRECATED_VERSIONS = { + cluster_image_scanning: VERSIONS_TO_DEPRECATE_IN_15_0, + container_scanning: VERSIONS_TO_DEPRECATE_IN_15_0, + coverage_fuzzing: VERSIONS_TO_DEPRECATE_IN_15_0, + dast: VERSIONS_TO_DEPRECATE_IN_15_0, + api_fuzzing: VERSIONS_TO_DEPRECATE_IN_15_0, + dependency_scanning: VERSIONS_TO_DEPRECATE_IN_15_0, + sast: VERSIONS_TO_DEPRECATE_IN_15_0, + secret_detection: VERSIONS_TO_DEPRECATE_IN_15_0 + }.freeze + class Schema def root_path File.join(__dir__, 'schemas') diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a6b52e80b19..c3eda9d65a5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4788,6 +4788,9 @@ msgstr "" msgid "Are you sure you want to remove this list?" msgstr "" +msgid "Are you sure you want to remove this nickname?" +msgstr "" + msgid "Are you sure you want to reset the health check token?" msgstr "" @@ -4800,9 +4803,6 @@ msgstr "" msgid "Are you sure you want to revoke this %{type}? This action cannot be undone." msgstr "" -msgid "Are you sure you want to revoke this nickname?" -msgstr "" - msgid "Are you sure you want to revoke this personal access token? This action cannot be undone." msgstr "" @@ -14670,6 +14670,9 @@ msgstr "" msgid "Existing sign in methods may be removed" msgstr "" +msgid "Exit." +msgstr "" + msgid "Expand" msgstr "" @@ -18275,6 +18278,9 @@ msgstr "" msgid "IdentityVerification|Verify your identity" msgstr "" +msgid "IdentityVerification|You can always verify your account at a later time to create a group." +msgstr "" + msgid "If any indexed field exceeds this limit, it is truncated to this number of characters. The rest of the content is neither indexed nor searchable. This does not apply to repository and wiki indexing. For unlimited characters, set this to 0." msgstr "" @@ -32418,9 +32424,6 @@ msgstr "" msgid "SecurityOrchestration|Invalid policy type" msgstr "" -msgid "SecurityOrchestration|Latest scan" -msgstr "" - msgid "SecurityOrchestration|Latest scan run against %{agent}" msgstr "" @@ -32430,6 +32433,9 @@ msgstr "" msgid "SecurityOrchestration|New policy" msgstr "" +msgid "SecurityOrchestration|No description" +msgstr "" + msgid "SecurityOrchestration|No rules defined - policy will not run." msgstr "" @@ -32442,6 +32448,9 @@ msgstr "" msgid "SecurityOrchestration|Policies" msgstr "" +msgid "SecurityOrchestration|Policy Type" +msgstr "" + msgid "SecurityOrchestration|Policy cannot be enabled for non-existing branches (%{branches})" msgstr "" @@ -32586,9 +32595,6 @@ msgstr "" msgid "SecurityOrchestration|users with ids" msgstr "" -msgid "SecurityOrchestration|view results" -msgstr "" - msgid "SecurityOrchestration|vulnerabilities" msgstr "" diff --git a/package.json b/package.json index 582d86454b8..d05fca48850 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "dateformat": "^5.0.1", "deckar01-task_list": "^2.3.1", "diff": "^3.4.0", - "dompurify": "^2.3.5", + "dompurify": "^2.3.6", "dropzone": "^4.2.0", "editorconfig": "^0.15.3", "emoji-regex": "^10.0.0", diff --git a/qa/Rakefile b/qa/Rakefile index 5d8c49a399b..b6fae0f685d 100644 --- a/qa/Rakefile +++ b/qa/Rakefile @@ -64,4 +64,9 @@ desc "Deletes resources created during E2E test runs" task :delete_test_resources, :file_pattern do |t, args| QA::Tools::DeleteTestResources.new(args[:file_pattern]).run end + +desc "Deletes test users" +task :delete_test_users, [:delete_before, :dry_run, :exclude_users] do |t, args| + QA::Tools::DeleteTestUsers.new(args).run +end # rubocop:enable Rails/RakeEnvironment diff --git a/qa/qa/page/project/settings/mirroring_repositories.rb b/qa/qa/page/project/settings/mirroring_repositories.rb index 582079157f2..501b31f8a95 100644 --- a/qa/qa/page/project/settings/mirroring_repositories.rb +++ b/qa/qa/page/project/settings/mirroring_repositories.rb @@ -87,20 +87,21 @@ module QA end def update(url) - row_index = find_repository_row_index url + row_index = find_repository_row_index(url) within_element_by_index(:mirrored_repository_row, row_index) do # When a repository is first mirrored, the update process might # already be started, so the button is already "clicked" click_element :update_now_button unless has_element? :updating_button end + end - # Wait a few seconds for the sync to occur and then refresh the page - # so that 'last update' shows 'just now' or a period in seconds - sleep 5 + def verify_update(url) refresh - wait_until(max_duration: 180, sleep_interval: 1) do + row_index = find_repository_row_index(url) + + wait_until(sleep_interval: 1) do within_element_by_index(:mirrored_repository_row, row_index) do last_update = find_element(:mirror_last_update_at_cell, wait: 0) last_update.has_text?('just now') || last_update.has_text?('seconds') diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb index 1958884916c..e1071616afe 100644 --- a/qa/qa/resource/api_fabricator.rb +++ b/qa/qa/resource/api_fabricator.rb @@ -165,6 +165,14 @@ module QA def transform_api_resource(api_resource) api_resource end + + # Get api request url + # + # @param [String] path + # @return [String] + def request_url(path, **opts) + Runtime::API::Request.new(api_client, path, **opts).url + end end end end diff --git a/qa/qa/resource/deploy_key.rb b/qa/qa/resource/deploy_key.rb index 26355729dab..c06671be77d 100644 --- a/qa/qa/resource/deploy_key.rb +++ b/qa/qa/resource/deploy_key.rb @@ -5,6 +5,8 @@ module QA class DeployKey < Base attr_accessor :title, :key + attribute :id + attribute :md5_fingerprint do Page::Project::Settings::Repository.perform do |setting| setting.expand_deploy_keys do |key| @@ -34,6 +36,46 @@ module QA end end end + + def fabricate_via_api! + resource_web_url(api_get) + rescue ResourceNotFoundError + super + end + + def resource_web_url(resource) + super + rescue ResourceURLMissingError + # this particular resource does not expose a web_url property + end + + def api_get_path + "/projects/#{project.id}/deploy_keys/#{find_id}" + end + + def api_post_path + "/projects/#{project.id}/deploy_keys" + end + + def api_post_body + { + key: key, + title: title + } + end + + private + + def find_id + id + rescue NoValueError + found_key = auto_paginated_response(request_url("/projects/#{project.id}/deploy_keys", per_page: '100')) + .find { |keys| keys[:key].strip == @key.strip } + + return found_key.fetch(:id) if found_key + + raise ResourceNotFoundError + end end end end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index c5b72eebe03..4550e00f87e 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -407,14 +407,6 @@ module QA Git::Location.new(api_resource[:http_url_to_repo]) api_resource end - - # Get api request url - # - # @param [String] path - # @return [String] - def request_url(path, **opts) - Runtime::API::Request.new(api_client, path, **opts).url - end end end end diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb index 260c812420c..1055bd98d3c 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb @@ -10,7 +10,7 @@ module QA deploy_key_title = 'deploy key title' deploy_key_value = key.public_key - deploy_key = Resource::DeployKey.fabricate! do |resource| + deploy_key = Resource::DeployKey.fabricate_via_browser_ui! do |resource| resource.title = deploy_key_title resource.key = deploy_key_value end diff --git a/qa/qa/tools/delete_test_users.rb b/qa/qa/tools/delete_test_users.rb new file mode 100644 index 00000000000..1f69f1bc548 --- /dev/null +++ b/qa/qa/tools/delete_test_users.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +# This script deletes users with a username starting with "qa-user-" +# - Specify `delete_before` to delete only keys that were created before the given date (default: yesterday) +# - If `dry_run` is true the script will list the users to be deleted by username, but it won't delete them +# - Specify `exclude_users` as a comma-separated list of usernames to not delete. +# +# Required environment variables: GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS +# - GITLAB_QA_ACCESS_TOKEN must have admin API access + +module QA + module Tools + class DeleteTestUsers + include Support::API + + ITEMS_PER_PAGE = '100' + EXCLUDE_USERS = %w[qa-user-abc123].freeze + FALSY_VALUES = %w[false no 0].freeze + + def initialize(delete_before: (Date.today - 1).to_s, dry_run: 'false', exclude_users: nil) + raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS'] + raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN'] + + @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN']) + @dry_run = !FALSY_VALUES.include?(dry_run.to_s.downcase) + @delete_before = Date.parse(delete_before) + @page_no = '1' + @exclude_users = Array(exclude_users.to_s.split(',')) + EXCLUDE_USERS + end + + def run + puts "Deleting users with a username starting with 'qa-user-' created before #{@delete_before}..." + + while page_no.present? + users = fetch_test_users + + delete_test_users(users) if users.present? + end + + puts "\nDone" + end + + private + + attr_reader :dry_run, :page_no + alias_method :dry_run?, :dry_run + + def fetch_test_users + puts "Fetching QA test users from page #{page_no}..." + + response = get Runtime::API::Request.new(@api_client, "/users", page: page_no, per_page: ITEMS_PER_PAGE).url + + # When we reach the last page, the x-next-page header is a blank string + @page_no = response.headers[:x_next_page].to_s + + JSON.parse(response.body).select do |user| + user['username'].start_with?('qa-user-', 'test-user-') \ + && (user['name'] == 'QA Tests' || user['name'].start_with?('QA User')) \ + && !@exclude_users.include?(user['username']) \ + && Date.parse(user.fetch('created_at', Date.today.to_s)) < @delete_before + end + end + + def delete_test_users(users) + usernames = users.map { |user| user['username'] }.join(', ') + if dry_run? + puts "Dry run: found users with usernames #{usernames}" + + return + end + + puts "Deleting #{users.length} users with usernames #{usernames}..." + users.each do |user| + delete_response = delete Runtime::API::Request.new(@api_client, "/users/#{user['id']}", hard_delete: 'true').url + dot_or_f = delete_response.code == 204 ? "\e[32m.\e[0m" : "\e[31mF\e[0m" + print dot_or_f + end + print "\n" + end + end + end +end diff --git a/qa/qa/tools/test_resource_data_processor.rb b/qa/qa/tools/test_resource_data_processor.rb index 965919dc516..b9c2540c681 100644 --- a/qa/qa/tools/test_resource_data_processor.rb +++ b/qa/qa/tools/test_resource_data_processor.rb @@ -79,7 +79,7 @@ module QA else default end - rescue QA::Resource::Base::NoValueError + rescue QA::Resource::Base::NoValueError, QA::Resource::Errors::ResourceNotFoundError default end end diff --git a/spec/features/boards/board_filters_spec.rb b/spec/features/boards/board_filters_spec.rb index e37bf515088..537b677cbd0 100644 --- a/spec/features/boards/board_filters_spec.rb +++ b/spec/features/boards/board_filters_spec.rb @@ -22,8 +22,6 @@ RSpec.describe 'Issue board filters', :js do let(:filter_submit) { find('.gl-search-box-by-click-search-button') } before do - stub_feature_flags(issue_boards_filtered_search: true) - project.add_maintainer(user) sign_in(user) diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 2ca4ff94911..fa01304ffe0 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -13,6 +13,10 @@ RSpec.describe 'Project issue boards', :js do let_it_be(:user) { create(:user) } let_it_be(:user2) { create(:user) } + let(:filtered_search) { find('[data-testid="issue-board-filtered-search"]') } + let(:filter_input) { find('.gl-filtered-search-term-input') } + let(:filter_submit) { find('.gl-search-box-by-click-search-button') } + context 'signed in user' do before do project.add_maintainer(user) @@ -90,8 +94,7 @@ RSpec.describe 'Project issue boards', :js do end it 'search closed list' do - find('.filtered-search').set(issue8.title) - find('.filtered-search').native.send_keys(:enter) + set_filter_and_search_by_token_value(issue8.title) wait_for_requests @@ -101,8 +104,7 @@ RSpec.describe 'Project issue boards', :js do end it 'search list' do - find('.filtered-search').set(issue5.title) - find('.filtered-search').native.send_keys(:enter) + set_filter_and_search_by_token_value(issue5.title) wait_for_requests @@ -111,26 +113,6 @@ RSpec.describe 'Project issue boards', :js do expect(find('.board:nth-child(4)')).to have_selector('.board-card', count: 0) end - context 'search list negation queries' do - before do - visit_project_board_path_without_query_limit(project, board) - end - - it 'does not have the != option' do - find('.filtered-search').set('label:') - - wait_for_requests - within('#js-dropdown-operator') do - tokens = all(:css, 'li.filter-dropdown-item') - expect(tokens.count).to eq(2) - button = tokens[0].find('button') - expect(button).to have_content('=') - button = tokens[1].find('button') - expect(button).to have_content('!=') - end - end - end - it 'allows user to delete board' do remove_list @@ -309,8 +291,8 @@ RSpec.describe 'Project issue boards', :js do context 'filtering' do it 'filters by author' do set_filter("author", user2.username) - click_filter_link(user2.username) - submit_filter + click_on user2.username + filter_submit.click wait_for_requests wait_for_board_cards(2, 1) @@ -319,8 +301,8 @@ RSpec.describe 'Project issue boards', :js do it 'filters by assignee' do set_filter("assignee", user.username) - click_filter_link(user.username) - submit_filter + click_on user.username + filter_submit.click wait_for_requests @@ -330,8 +312,8 @@ RSpec.describe 'Project issue boards', :js do it 'filters by milestone' do set_filter("milestone", "\"#{milestone.title}") - click_filter_link(milestone.title) - submit_filter + click_on milestone.title + filter_submit.click wait_for_requests wait_for_board_cards(2, 1) @@ -341,8 +323,8 @@ RSpec.describe 'Project issue boards', :js do it 'filters by label' do set_filter("label", testing.title) - click_filter_link(testing.title) - submit_filter + click_on testing.title + filter_submit.click wait_for_requests wait_for_board_cards(2, 1) @@ -351,8 +333,10 @@ RSpec.describe 'Project issue boards', :js do it 'filters by label with encoded character' do set_filter("label", a_plus.title) - click_filter_link(a_plus.title) - submit_filter + # This one is a char encoding issue like the & issue + click_on a_plus.title + filter_submit.click + wait_for_requests wait_for_board_cards(1, 1) wait_for_empty_boards((2..4)) @@ -360,8 +344,8 @@ RSpec.describe 'Project issue boards', :js do it 'filters by label with space after reload', :quarantine do set_filter("label", "\"#{accepting.title}") - click_filter_link(accepting.title) - submit_filter + click_on accepting.title + filter_submit.click # Test after reload page.evaluate_script 'window.location.reload()' @@ -384,13 +368,13 @@ RSpec.describe 'Project issue boards', :js do it 'removes filtered labels' do inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do set_filter("label", testing.title) - click_filter_link(testing.title) - submit_filter + click_on testing.title + filter_submit.click wait_for_board_cards(2, 1) - find('.clear-search').click - submit_filter + find('[data-testid="filtered-search-clear-button"]').click + filter_submit.click end wait_for_board_cards(2, 8) @@ -400,9 +384,9 @@ RSpec.describe 'Project issue boards', :js do create_list(:labeled_issue, 30, project: project, labels: [planning, testing]) set_filter("label", testing.title) - click_filter_link(testing.title) + click_on testing.title inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do - submit_filter + filter_submit.click end wait_for_requests @@ -442,10 +426,10 @@ RSpec.describe 'Project issue boards', :js do it 'filters by multiple labels', :quarantine do set_filter("label", testing.title) - click_filter_link(testing.title) + click_on testing.title set_filter("label", bug.title) - click_filter_link(bug.title) + click_on bug.title submit_filter @@ -463,7 +447,7 @@ RSpec.describe 'Project issue boards', :js do wait_for_requests end - page.within('.tokens-container') do + page.within('.gl-filtered-search-token') do expect(page).to have_content(bug.title) end @@ -561,19 +545,26 @@ RSpec.describe 'Project issue boards', :js do end end + def set_filter_and_search_by_token_value(value) + filter_input.click + filter_input.set(value) + filter_submit.click + end + def set_filter(type, text) - find('.filtered-search').native.send_keys("#{type}:=#{text}") + filter_input.click + filter_input.native.send_keys("#{type}:=#{text}") end def submit_filter - find('.filtered-search').native.send_keys(:enter) + filter_input.native.send_keys(:enter) end def click_filter_link(link_text) - page.within('.filtered-search-box') do + page.within(filtered_search) do expect(page).to have_button(link_text) - click_button(link_text) + click_on link_text end end diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index 6c8d41fd96f..a064eef5cc8 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -179,38 +179,6 @@ RSpec.describe 'Labels Hierarchy', :js do it_behaves_like 'assigning labels from sidebar' end - - context 'on project board issue sidebar' do - let(:board) { create(:board, project: project_1) } - - before do - project_1.add_developer(user) - - visit project_board_path(project_1, board) - - wait_for_requests - - find('.board-card').click - end - - it_behaves_like 'assigning labels from sidebar' - end - - context 'on group board issue sidebar' do - let(:board) { create(:board, group: parent) } - - before do - parent.add_developer(user) - - visit group_board_path(parent, board) - - wait_for_requests - - find('.board-card').click - end - - it_behaves_like 'assigning labels from sidebar' - end end context 'issuable filtering' do @@ -242,29 +210,5 @@ RSpec.describe 'Labels Hierarchy', :js do it_behaves_like 'filtering by ancestor labels for groups' end - - context 'on project boards filter' do - let(:board) { create(:board, project: project_1) } - - before do - project_1.add_developer(user) - - visit project_board_path(project_1, board) - end - - it_behaves_like 'filtering by ancestor labels for projects', true - end - - context 'on group boards filter' do - let(:board) { create(:board, group: parent) } - - before do - parent.add_developer(user) - - visit group_board_path(parent, board) - end - - it_behaves_like 'filtering by ancestor labels for groups', true - end end end diff --git a/spec/frontend/protected_branches/protected_branch_edit_spec.js b/spec/frontend/protected_branches/protected_branch_edit_spec.js index b41b5028736..13e0388979d 100644 --- a/spec/frontend/protected_branches/protected_branch_edit_spec.js +++ b/spec/frontend/protected_branches/protected_branch_edit_spec.js @@ -8,59 +8,101 @@ import ProtectedBranchEdit from '~/protected_branches/protected_branch_edit'; jest.mock('~/flash'); const TEST_URL = `${TEST_HOST}/url`; +const FORCE_PUSH_TOGGLE_TESTID = 'force-push-toggle'; +const CODE_OWNER_TOGGLE_TESTID = 'code-owner-toggle'; const IS_CHECKED_CLASS = 'is-checked'; +const IS_DISABLED_CLASS = 'is-disabled'; +const IS_LOADING_SELECTOR = '.toggle-loading'; describe('ProtectedBranchEdit', () => { let mock; beforeEach(() => { - setFixtures(`
- -
`); - jest.spyOn(ProtectedBranchEdit.prototype, 'buildDropdowns').mockImplementation(); mock = new MockAdapter(axios); }); - const findForcePushesToggle = () => document.querySelector('.js-force-push-toggle'); + const findForcePushToggle = () => + document.querySelector(`div[data-testid="${FORCE_PUSH_TOGGLE_TESTID}"] button`); + const findCodeOwnerToggle = () => + document.querySelector(`div[data-testid="${CODE_OWNER_TOGGLE_TESTID}"] button`); - const create = ({ isChecked = false }) => { - if (isChecked) { - findForcePushesToggle().classList.add(IS_CHECKED_CLASS); - } + const create = ({ + forcePushToggleChecked = false, + codeOwnerToggleChecked = false, + hasLicense = true, + } = {}) => { + setFixtures(`
+ + +
`); - return new ProtectedBranchEdit({ $wrap: $('#wrap'), hasLicense: false }); + return new ProtectedBranchEdit({ $wrap: $('#wrap'), hasLicense }); }; afterEach(() => { mock.restore(); }); - describe('when unchecked toggle button', () => { + describe('when license supports code owner approvals', () => { + beforeEach(() => { + create(); + }); + + it('instantiates the code owner toggle', () => { + expect(findCodeOwnerToggle()).not.toBe(null); + }); + }); + + describe('when license does not support code owner approvals', () => { + beforeEach(() => { + create({ hasLicense: false }); + }); + + it('does not instantiate the code owner toggle', () => { + expect(findCodeOwnerToggle()).toBe(null); + }); + }); + + describe.each` + description | checkedOption | patchParam | finder + ${'force push'} | ${'forcePushToggleChecked'} | ${'allow_force_push'} | ${findForcePushToggle} + ${'code owner'} | ${'codeOwnerToggleChecked'} | ${'code_owner_approval_required'} | ${findCodeOwnerToggle} + `('when unchecked $description toggle button', ({ checkedOption, patchParam, finder }) => { let toggle; beforeEach(() => { - create({ isChecked: false }); + create({ [checkedOption]: false }); - toggle = findForcePushesToggle(); + toggle = finder(); }); it('is not changed', () => { expect(toggle).not.toHaveClass(IS_CHECKED_CLASS); - expect(toggle).not.toBeDisabled(); + expect(toggle.querySelector(IS_LOADING_SELECTOR)).toBe(null); + expect(toggle).not.toHaveClass(IS_DISABLED_CLASS); }); describe('when clicked', () => { beforeEach(() => { - mock.onPatch(TEST_URL, { protected_branch: { allow_force_push: true } }).replyOnce(200, {}); + mock.onPatch(TEST_URL, { protected_branch: { [patchParam]: true } }).replyOnce(200, {}); toggle.click(); }); it('checks and disables button', () => { expect(toggle).toHaveClass(IS_CHECKED_CLASS); - expect(toggle).toBeDisabled(); + expect(toggle.querySelector(IS_LOADING_SELECTOR)).not.toBe(null); + expect(toggle).toHaveClass(IS_DISABLED_CLASS); }); it('sends update to BE', () => @@ -68,7 +110,8 @@ describe('ProtectedBranchEdit', () => { // Args are asserted in the `.onPatch` call expect(mock.history.patch).toHaveLength(1); - expect(toggle).not.toBeDisabled(); + expect(toggle).not.toHaveClass(IS_DISABLED_CLASS); + expect(toggle.querySelector(IS_LOADING_SELECTOR)).toBe(null); expect(createFlash).not.toHaveBeenCalled(); })); }); diff --git a/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb index 951e0576a58..070b65c7808 100644 --- a/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb +++ b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb @@ -3,6 +3,50 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do + describe 'SUPPORTED_VERSIONS' do + schema_path = Rails.root.join("lib", "gitlab", "ci", "parsers", "security", "validators", "schemas") + + it 'matches DEPRECATED_VERSIONS keys' do + expect(described_class::SUPPORTED_VERSIONS.keys).to eq(described_class::DEPRECATED_VERSIONS.keys) + end + + context 'files under schema path are explicitly listed' do + # We only care about the part that comes before report-format.json + # https://rubular.com/r/N8Juz7r8hYDYgD + filename_regex = /(?[-\w]*)\-report-format.json/ + + versions = Dir.glob(File.join(schema_path, "*", File::SEPARATOR)).map { |path| path.split("/").last } + + versions.each do |version| + files = Dir[schema_path.join(version, "*.json")] + + files.each do |file| + matches = filename_regex.match(file) + report_type = matches[:report_type].tr("-", "_").to_sym + + it "#{report_type} #{version}" do + expect(described_class::SUPPORTED_VERSIONS[report_type]).to include(version) + end + end + end + end + + context 'every SUPPORTED_VERSION has a corresponding JSON file' do + described_class::SUPPORTED_VERSIONS.each_key do |report_type| + # api_fuzzing is covered by DAST schema + next if report_type == :api_fuzzing + + described_class::SUPPORTED_VERSIONS[report_type].each do |version| + it "#{report_type} #{version} schema file is present" do + filename = "#{report_type.to_s.tr("_", "-")}-report-format.json" + full_path = schema_path.join(version, filename) + expect(File.file?(full_path)).to be true + end + end + end + end + end + using RSpec::Parameterized::TableSyntax where(:report_type, :expected_errors, :valid_data) do diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb index 225386d9596..847668ffc52 100644 --- a/spec/presenters/blob_presenter_spec.rb +++ b/spec/presenters/blob_presenter_spec.rb @@ -28,7 +28,7 @@ RSpec.describe BlobPresenter do end describe '#replace_path' do - it { expect(presenter.replace_path).to eq("/#{project.full_path}/-/create/#{blob.commit_id}/#{blob.path}") } + it { expect(presenter.replace_path).to eq("/#{project.full_path}/-/update/#{blob.commit_id}/#{blob.path}") } end describe '#can_current_user_push_to_branch' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 37e9ef1d994..42163584870 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -304,8 +304,6 @@ RSpec.configure do |config| # As we're ready to change `master` usages to `main`, let's enable it stub_feature_flags(main_branch_over_master: false) - stub_feature_flags(issue_boards_filtered_search: false) - # Disable issue respositioning to avoid heavy load on database when importing big projects. # This is only turned on when app is handling heavy project imports. # Can be removed when we find a better way to deal with the problem. diff --git a/yarn.lock b/yarn.lock index dc2080f9bfb..f36a1712029 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4953,10 +4953,10 @@ dompurify@2.3.4: resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.4.tgz#1cf5cf0105ccb4debdf6db162525bd41e6ddacc6" integrity sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ== -dompurify@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.5.tgz#c83ed5a3ae5ce23e52efe654ea052ffb358dd7e3" - integrity sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ== +dompurify@^2.3.5, dompurify@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.6.tgz#2e019d7d7617aacac07cbbe3d88ae3ad354cf875" + integrity sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg== domutils@^2.5.2, domutils@^2.6.0: version "2.6.0"