Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-02-18 06:14:34 +00:00
parent 20b517258a
commit c8512eb431
35 changed files with 443 additions and 307 deletions

View File

@ -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: '!=' },
});
}

View File

@ -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
);
}
}

View File

@ -10,5 +10,6 @@ export const gqlClient = createDefaultClient(
return object.__typename === 'BoardList' ? object.iid : defaultDataIdFromObject(object);
},
},
batchMax: 2,
},
);

View File

@ -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);

View File

@ -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,

View File

@ -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;
});
});
}

View File

@ -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'), {

View File

@ -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|

View File

@ -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|

View File

@ -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

View File

@ -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' }

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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')

View File

@ -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 ""

View File

@ -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",

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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();
}));
});

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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"