Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/boards/components/board_filtered_search.vue6
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js81
-rw-r--r--app/assets/javascripts/boards/graphql.js1
-rw-r--r--app/assets/javascripts/boards/index.js25
-rw-r--r--app/assets/javascripts/boards/mount_filtered_search_issue_boards.js10
-rw-r--r--app/assets/javascripts/issues/create_merge_request_dropdown.js12
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit.js62
-rw-r--r--app/controllers/groups/boards_controller.rb1
-rw-r--r--app/controllers/projects/boards_controller.rb1
-rw-r--r--app/presenters/blob_presenter.rb2
-rw-r--r--app/views/profiles/chat_names/_chat_name.html.haml2
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml6
-rw-r--r--app/views/shared/projects/protected_branches/_update_protected_branch.html.haml6
-rw-r--r--config/feature_flags/development/exit_registration_verification.yml (renamed from config/feature_flags/development/issue_boards_filtered_search.yml)12
-rw-r--r--config/routes.rb5
-rw-r--r--config/webpack.config.js17
-rw-r--r--lib/gitlab/ci/parsers/security/validators/schema_validator.rb35
-rw-r--r--locale/gitlab.pot24
-rw-r--r--package.json2
-rw-r--r--qa/Rakefile5
-rw-r--r--qa/qa/page/project/settings/mirroring_repositories.rb11
-rw-r--r--qa/qa/resource/api_fabricator.rb8
-rw-r--r--qa/qa/resource/deploy_key.rb42
-rw-r--r--qa/qa/resource/project.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb2
-rw-r--r--qa/qa/tools/delete_test_users.rb82
-rw-r--r--qa/qa/tools/test_resource_data_processor.rb2
-rw-r--r--spec/features/boards/board_filters_spec.rb2
-rw-r--r--spec/features/boards/boards_spec.rb89
-rw-r--r--spec/features/labels_hierarchy_spec.rb56
-rw-r--r--spec/frontend/protected_branches/protected_branch_edit_spec.js77
-rw-r--r--spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb44
-rw-r--r--spec/presenters/blob_presenter_spec.rb2
-rw-r--r--spec/spec_helper.rb2
-rw-r--r--yarn.lock8
35 files changed, 443 insertions, 307 deletions
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
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(`<div id="wrap" data-url="${TEST_URL}">
- <button class="js-force-push-toggle">Toggle</button>
- </div>`);
-
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(`<div id="wrap" data-url="${TEST_URL}">
+ <span
+ class="js-force-push-toggle"
+ data-label="Toggle allowed to force push"
+ data-is-checked="${forcePushToggleChecked}"
+ data-testid="${FORCE_PUSH_TOGGLE_TESTID}"></span>
+ <span
+ class="js-code-owner-toggle"
+ data-label="Toggle code owner approval"
+ data-is-checked="${codeOwnerToggleChecked}"
+ data-testid="${CODE_OWNER_TOGGLE_TESTID}"></span>
+ </div>`);
- 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 = /(?<report_type>[-\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"