summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/package-and-test/main.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/review-apps/main.gitlab-ci.yml1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/admin/users/components/actions/ban.vue4
-rw-r--r--app/assets/javascripts/diffs/store/actions.js4
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/actions.js6
-rw-r--r--app/assets/javascripts/ide/stores/modules/pipelines/actions.js4
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/actions/checks.js4
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js4
-rw-r--r--app/assets/javascripts/issuable/components/issuable_header_warnings.vue11
-rw-r--r--app/assets/javascripts/lib/utils/http_status.js2
-rw-r--r--app/assets/javascripts/profile/preferences/profile_preferences_bundle.js3
-rw-r--r--app/assets/javascripts/ref/stores/mutations.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/listbox_input/init_listbox_inputs.js42
-rw-r--r--app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue2
-rw-r--r--app/assets/stylesheets/utilities.scss4
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb4
-rw-r--r--app/finders/issuable_finder.rb5
-rw-r--r--app/finders/issuable_finder/params.rb5
-rw-r--r--app/finders/issues_finder.rb2
-rw-r--r--app/finders/issues_finder/params.rb8
-rw-r--r--app/graphql/resolvers/concerns/resolves_merge_requests.rb2
-rw-r--r--app/helpers/issuables_helper.rb8
-rw-r--r--app/helpers/issues_helper.rb4
-rw-r--r--app/helpers/merge_requests_helper.rb6
-rw-r--r--app/helpers/preferences_helper.rb17
-rw-r--r--app/models/merge_request.rb12
-rw-r--r--app/policies/issuable_policy.rb3
-rw-r--r--app/policies/issue_policy.rb3
-rw-r--r--app/policies/merge_request_policy.rb4
-rw-r--r--app/views/admin/dashboard/_stats_users_table.html.haml49
-rw-r--r--app/views/admin/dashboard/stats.html.haml66
-rw-r--r--app/views/profiles/preferences/show.html.haml24
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml1
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml2
-rw-r--r--config/feature_flags/development/hide_merge_requests_from_banned_users.yml8
-rw-r--r--config/feature_flags/development/phabricator_import.yml8
-rw-r--r--doc/architecture/blueprints/ci_pipeline_components/index.md58
-rw-r--r--doc/integration/gitlab.md4
-rw-r--r--doc/integration/jira/development_panel.md2
-rw-r--r--doc/user/admin_area/moderate_users.md2
-rw-r--r--doc/user/permissions.md4
-rw-r--r--doc/user/project/merge_requests/status_checks.md6
-rw-r--r--lib/gitlab/phabricator_import.rb3
-rw-r--r--locale/gitlab.pot29
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock4
-rw-r--r--qa/qa/page/project/settings/protected_branches.rb9
-rw-r--r--qa/qa/page/project/web_ide/vscode.rb66
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide_new/add_new_directory_in_web_ide_spec.rb73
-rwxr-xr-xscripts/review_apps/review-apps.sh19
-rw-r--r--spec/controllers/import/phabricator_controller_spec.rb13
-rw-r--r--spec/features/admin/dashboard_spec.rb3
-rw-r--r--spec/features/merge_request/admin_views_hidden_merge_request_spec.rb27
-rw-r--r--spec/features/merge_requests/admin_views_hidden_merge_requests_spec.rb27
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb10
-rw-r--r--spec/finders/merge_requests_finder_spec.rb25
-rw-r--r--spec/frontend/api_spec.js5
-rw-r--r--spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js3
-rw-r--r--spec/frontend/ide/stores/modules/commit/actions_spec.js39
-rw-r--r--spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js5
-rw-r--r--spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js4
-rw-r--r--spec/frontend/ide/stores/modules/terminal/messages_spec.js5
-rw-r--r--spec/frontend/issuable/components/issuable_by_email_spec.js4
-rw-r--r--spec/frontend/issuable/components/issuable_header_warnings_spec.js5
-rw-r--r--spec/frontend/lib/utils/poll_until_complete_spec.js7
-rw-r--r--spec/frontend/notifications/components/custom_notifications_modal_spec.js6
-rw-r--r--spec/frontend/notifications/components/notifications_dropdown_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js8
-rw-r--r--spec/helpers/issuables_helper_spec.rb24
-rw-r--r--spec/helpers/preferences_helper_spec.rb24
-rw-r--r--spec/lib/gitlab/database/indexing_exclusive_lease_guard_spec.rb (renamed from spec/lib/gitlab/database/indexing_exclusive_lease_guard.rb_spec.rb)0
-rw-r--r--spec/models/merge_request_spec.rb42
-rw-r--r--spec/policies/merge_request_policy_spec.rb30
-rw-r--r--spec/requests/projects/merge_requests_controller_spec.rb28
-rw-r--r--spec/support/helpers/listbox_input_helper.rb6
-rw-r--r--spec/views/profiles/preferences/show.html.haml_spec.rb5
-rw-r--r--workhorse/go.mod4
-rw-r--r--workhorse/go.sum13
79 files changed, 759 insertions, 237 deletions
diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
index 58cf81dd7c4..6dd0a92d9c4 100644
--- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml
+++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
@@ -603,6 +603,7 @@ ee:importers:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::Import
+ QA_MOCK_GITHUB: "false"
GITLAB_QA_OPTS: --set-feature-flags bulk_import_projects=enabled
rules:
- !reference [.rules:test:qa, rules]
diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml
index 7600188103f..13e8ea330da 100644
--- a/.gitlab/ci/review-apps/main.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml
@@ -131,6 +131,7 @@ review-deploy:
- run_timed_command "deploy" || (display_deployment_debug && exit 1)
- run_timed_command "verify_deploy" || (display_deployment_debug && exit 1)
- run_timed_command "disable_sign_ups" || (display_deployment_debug && exit 1)
+ - run_timed_command "verify_commit_sha" || (display_deployment_debug && exit 1)
after_script:
# Run seed-dast-test-data.sh only when DAST_RUN is set to true. This is to pupulate review app with data for DAST scan.
# Set DAST_RUN to true when jobs are manually scheduled.
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index b846b693ba2..37575989f69 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-faa85e577ef9dd8063e7a1096d04e68dd22da3fc
+3bf6396fafab29ebbb984a77be0405d7ff5510fd
diff --git a/app/assets/javascripts/admin/users/components/actions/ban.vue b/app/assets/javascripts/admin/users/components/actions/ban.vue
index 55938832dce..898a688c203 100644
--- a/app/assets/javascripts/admin/users/components/actions/ban.vue
+++ b/app/assets/javascripts/admin/users/components/actions/ban.vue
@@ -11,7 +11,9 @@ const messageHtml = `
<ul>
<li>${s__("AdminUsers|The user can't log in.")}</li>
<li>${s__("AdminUsers|The user can't access git repositories.")}</li>
- <li>${s__('AdminUsers|Issues authored by this user are hidden from other users.')}</li>
+ <li>${s__(
+ 'AdminUsers|Issues and merge requests authored by this user are hidden from other users.',
+ )}</li>
</ul>
<p>${s__('AdminUsers|You can unban their account in the future. Their data remains intact.')}</p>
<p>${sprintf(
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 96a73917820..b87fe65841b 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -9,7 +9,7 @@ import { createAlert, VARIANT_WARNING } from '~/flash';
import { diffViewerModes } from '~/ide/constants';
import axios from '~/lib/utils/axios_utils';
-import httpStatusCodes from '~/lib/utils/http_status';
+import httpStatusCodes, { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status';
import Poll from '~/lib/utils/poll';
import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
@@ -232,7 +232,7 @@ export const fetchDiffFilesMeta = ({ commit, state }) => {
.catch((error) => {
worker.terminate();
- if (error.response.status === httpStatusCodes.NOT_FOUND) {
+ if (error.response.status === HTTP_STATUS_NOT_FOUND) {
createAlert({
message: __('Building your merge request. Wait a few moments, then refresh this page.'),
variant: VARIANT_WARNING,
diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js
index cbc6e0fe519..d490b8c5dad 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js
@@ -183,7 +183,11 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
dispatch(
'redirectToUrl',
- createNewMergeRequestUrl(currentProject.web_url, branchName, targetBranch),
+ createNewMergeRequestUrl(
+ currentProject.web_url,
+ encodeURIComponent(branchName),
+ encodeURIComponent(targetBranch),
+ ),
{ root: true },
);
}
diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js
index 62476b7fc63..6eb56a68429 100644
--- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js
@@ -1,6 +1,6 @@
import axios from 'axios';
import Visibility from 'visibilityjs';
-import httpStatus from '~/lib/utils/http_status';
+import { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status';
import Poll from '~/lib/utils/poll';
import { __ } from '~/locale';
import { rightSidebarViews } from '../../../constants';
@@ -24,7 +24,7 @@ export const forcePipelineRequest = () => {
export const requestLatestPipeline = ({ commit }) => commit(types.REQUEST_LATEST_PIPELINE);
export const receiveLatestPipelineError = ({ commit, dispatch }, err) => {
- if (err.status !== httpStatus.NOT_FOUND) {
+ if (err.status !== HTTP_STATUS_NOT_FOUND) {
dispatch(
'setErrorMessage',
{
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/checks.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/checks.js
index ac6e32ee25b..c4198a7427f 100644
--- a/app/assets/javascripts/ide/stores/modules/terminal/actions/checks.js
+++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/checks.js
@@ -1,5 +1,5 @@
import Api from '~/api';
-import httpStatus, { HTTP_STATUS_FORBIDDEN } from '~/lib/utils/http_status';
+import { HTTP_STATUS_FORBIDDEN, HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status';
import * as terminalService from '../../../../services/terminals';
import { CHECK_CONFIG, CHECK_RUNNERS, RETRY_RUNNERS_INTERVAL } from '../constants';
import * as messages from '../messages';
@@ -18,7 +18,7 @@ export const receiveConfigCheckError = ({ commit, state }, e) => {
const { status } = e.response;
const { paths } = state;
- const isVisible = status !== HTTP_STATUS_FORBIDDEN && status !== httpStatus.NOT_FOUND;
+ const isVisible = status !== HTTP_STATUS_FORBIDDEN && status !== HTTP_STATUS_NOT_FOUND;
commit(types.SET_VISIBLE, isVisible);
const message = messages.configCheckError(status, paths.webTerminalConfigHelpPath);
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js
index a510ec0847b..874cc5094d3 100644
--- a/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js
+++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js
@@ -1,6 +1,6 @@
import { createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import httpStatus, { HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status';
+import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status';
import * as terminalService from '../../../../services/terminals';
import { STARTING, STOPPING, STOPPED } from '../constants';
import * as messages from '../messages';
@@ -107,7 +107,7 @@ export const restartSession = ({ state, dispatch, rootState }) => {
const responseStatus = error.response && error.response.status;
// We may have removed the build, in this case we'll just create a new session
if (
- responseStatus === httpStatus.NOT_FOUND ||
+ responseStatus === HTTP_STATUS_NOT_FOUND ||
responseStatus === HTTP_STATUS_UNPROCESSABLE_ENTITY
) {
dispatch('startSession');
diff --git a/app/assets/javascripts/issuable/components/issuable_header_warnings.vue b/app/assets/javascripts/issuable/components/issuable_header_warnings.vue
index 543dca0afe1..14325d6b64e 100644
--- a/app/assets/javascripts/issuable/components/issuable_header_warnings.vue
+++ b/app/assets/javascripts/issuable/components/issuable_header_warnings.vue
@@ -1,11 +1,16 @@
<script>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { mapGetters } from 'vuex';
-import { __ } from '~/locale';
+import { sprintf, __ } from '~/locale';
import { IssuableType, WorkspaceType } from '~/issues/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
+const NoteableTypeText = {
+ issue: __('issue'),
+ merge_request: __('merge request'),
+};
+
export default {
WorkspaceType,
IssuableType,
@@ -40,7 +45,9 @@ export default {
iconName: 'spam',
visible: this.hidden,
dataTestId: 'hidden',
- tooltip: __('This issue is hidden because its author has been banned'),
+ tooltip: sprintf(__('This %{issuable} is hidden because its author has been banned'), {
+ issuable: NoteableTypeText[this.getNoteableData.targetType],
+ }),
},
];
},
diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js
index 4dd9fd10acb..27fd73affaa 100644
--- a/app/assets/javascripts/lib/utils/http_status.js
+++ b/app/assets/javascripts/lib/utils/http_status.js
@@ -17,12 +17,12 @@ export const HTTP_STATUS_TOO_MANY_REQUESTS = 429;
export const HTTP_STATUS_BAD_REQUEST = 400;
export const HTTP_STATUS_UNAUTHORIZED = 401;
export const HTTP_STATUS_FORBIDDEN = 403;
+export const HTTP_STATUS_NOT_FOUND = 404;
// TODO move the rest of the status codes to primitive constants
// https://docs.gitlab.com/ee/development/fe_guide/style/javascript.html#export-constants-as-primitives
const httpStatusCodes = {
OK: 200,
- NOT_FOUND: 404,
INTERNAL_SERVER_ERROR: 500,
SERVICE_UNAVAILABLE: 503,
};
diff --git a/app/assets/javascripts/profile/preferences/profile_preferences_bundle.js b/app/assets/javascripts/profile/preferences/profile_preferences_bundle.js
index 6520e68d41c..8e4d42a42c6 100644
--- a/app/assets/javascripts/profile/preferences/profile_preferences_bundle.js
+++ b/app/assets/javascripts/profile/preferences/profile_preferences_bundle.js
@@ -1,7 +1,10 @@
import Vue from 'vue';
+import { initListboxInputs } from '~/vue_shared/components/listbox_input/init_listbox_inputs';
import ProfilePreferences from './components/profile_preferences.vue';
export default () => {
+ initListboxInputs();
+
const el = document.querySelector('#js-profile-preferences-app');
const formEl = document.querySelector('#profile-preferences-form');
const shouldParse = ['integrationViews', 'themes', 'userFields'];
diff --git a/app/assets/javascripts/ref/stores/mutations.js b/app/assets/javascripts/ref/stores/mutations.js
index e078d3333d4..9846ac0adb7 100644
--- a/app/assets/javascripts/ref/stores/mutations.js
+++ b/app/assets/javascripts/ref/stores/mutations.js
@@ -1,5 +1,5 @@
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import httpStatusCodes from '~/lib/utils/http_status';
+import { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status';
import { X_TOTAL_HEADER } from '../constants';
import * as types from './mutation_types';
@@ -86,7 +86,7 @@ export default {
// 404's are expected when the search query doesn't match any commits
// and shouldn't be treated as an actual error
- error: error.response?.status !== httpStatusCodes.NOT_FOUND ? error : null,
+ error: error.response?.status !== HTTP_STATUS_NOT_FOUND ? error : null,
};
},
[types.RESET_COMMIT_MATCHES](state) {
diff --git a/app/assets/javascripts/vue_shared/components/listbox_input/init_listbox_inputs.js b/app/assets/javascripts/vue_shared/components/listbox_input/init_listbox_inputs.js
new file mode 100644
index 00000000000..ad89b78b521
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/listbox_input/init_listbox_inputs.js
@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import ListboxInput from '~/vue_shared/components/listbox_input/listbox_input.vue';
+
+export const initListboxInputs = () => {
+ const els = [...document.querySelectorAll('.js-listbox-input')];
+
+ els.forEach((el, index) => {
+ const { label, description, name, defaultToggleText, value = null } = el.dataset;
+ const { id } = el;
+ const items = JSON.parse(el.dataset.items);
+
+ return new Vue({
+ el,
+ name: `ListboxInputRoot${index + 1}`,
+ data() {
+ return {
+ selected: value,
+ };
+ },
+ render(createElement) {
+ return createElement(ListboxInput, {
+ on: {
+ select: (newValue) => {
+ this.selected = newValue;
+ },
+ },
+ props: {
+ label,
+ description,
+ name,
+ defaultToggleText,
+ selected: this.selected,
+ items,
+ },
+ attrs: {
+ id,
+ },
+ });
+ },
+ });
+ });
+};
diff --git a/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue
index 8340ee19084..bc6b5d3176f 100644
--- a/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue
+++ b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue
@@ -115,7 +115,7 @@ export default {
</script>
<template>
- <component :is="wrapperComponent" :label="label" :description="description">
+ <component :is="wrapperComponent" :label="label" :description="description" v-bind="$attrs">
<gl-listbox
:selected="selected"
:toggle-text="toggleText"
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index 5eb76be4bbf..e5f0fe14d1c 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -236,6 +236,10 @@ to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1709
}
}
+.gl-mt-n5 {
+ margin-top: -$gl-spacing-scale-5;
+}
+
// Utils below are very specific so cannot be part of GitLab UI
.gl-md-mt-5 {
@include gl-media-breakpoint-up(md) {
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index d8da448a323..76b06b2ce9d 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -13,6 +13,10 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
@issuable =
@merge_request ||=
merge_request_includes(@project.merge_requests).find_by_iid!(params[:id])
+
+ return render_404 unless can?(current_user, :read_merge_request, @issuable)
+
+ @issuable
end
def merge_request_includes(association)
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 5fcb81949ee..c5a3293ad2f 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -248,7 +248,10 @@ class IssuableFinder
end
def init_collection
- klass.all
+ return klass.all if params.user_can_see_all_issuables?
+
+ # Only admins and auditors can see hidden issuables, for other users we filter out hidden issuables
+ klass.without_hidden
end
def default_or_simple_sort?
diff --git a/app/finders/issuable_finder/params.rb b/app/finders/issuable_finder/params.rb
index 32d50802537..e59c2224594 100644
--- a/app/finders/issuable_finder/params.rb
+++ b/app/finders/issuable_finder/params.rb
@@ -195,6 +195,11 @@ class IssuableFinder
project || group
end
+ def user_can_see_all_issuables?
+ Ability.allowed?(current_user, :read_all_resources)
+ end
+ strong_memoize_attr :user_can_see_all_issuables?
+
private
def projects_public_or_visible_to_user
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index e12dce744b5..bd81f06f93b 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -49,7 +49,7 @@ class IssuesFinder < IssuableFinder
# rubocop: disable CodeReuse/ActiveRecord
def with_confidentiality_access_check
- return model_class.all if params.user_can_see_all_issues?
+ return model_class.all if params.user_can_see_all_issuables?
# Only admins can see hidden issues, so for non-admins, we filter out any hidden issues
issues = model_class.without_hidden
diff --git a/app/finders/issues_finder/params.rb b/app/finders/issues_finder/params.rb
index 7f8acb79ed6..786bfbd4113 100644
--- a/app/finders/issues_finder/params.rb
+++ b/app/finders/issues_finder/params.rb
@@ -44,7 +44,7 @@ class IssuesFinder
if parent
Ability.allowed?(current_user, :read_confidential_issues, parent)
else
- user_can_see_all_issues?
+ user_can_see_all_issuables?
end
end
end
@@ -54,12 +54,6 @@ class IssuesFinder
current_user.blank?
end
-
- def user_can_see_all_issues?
- strong_memoize(:user_can_see_all_issues) do
- Ability.allowed?(current_user, :read_all_resources)
- end
- end
end
end
diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
index d56951bc821..c68e120ee24 100644
--- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb
+++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
@@ -34,7 +34,7 @@ module ResolvesMergeRequests
end
def unconditional_includes
- [:target_project]
+ [:target_project, :author]
end
def preloads
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 09e18129605..fb407aa7eed 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -368,6 +368,14 @@ module IssuablesHelper
end
end
+ def hidden_issuable_icon(issuable)
+ title = format(_('This %{issuable} is hidden because its author has been banned'),
+ issuable: issuable.is_a?(Issue) ? _('issue') : _('merge request'))
+ content_tag(:span, class: 'has-tooltip', title: title) do
+ sprite_icon('spam', css_class: 'gl-vertical-align-text-bottom')
+ end
+ end
+
private
def sidebar_gutter_collapsed?
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 9095129e05c..527106de790 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -75,9 +75,7 @@ module IssuesHelper
def hidden_issue_icon(issue)
return unless issue_hidden?(issue)
- content_tag(:span, class: 'has-tooltip', title: _('This issue is hidden because its author has been banned')) do
- sprite_icon('spam', css_class: 'gl-vertical-align-text-bottom')
- end
+ hidden_issuable_icon(issue)
end
def award_user_list(awards, current_user, limit: 10)
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 1d7d812dc5d..ec395baef9e 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -276,6 +276,12 @@ module MergeRequestsHelper
data
end
+
+ def hidden_merge_request_icon(merge_request)
+ return unless merge_request.hidden?
+
+ hidden_issuable_icon(merge_request)
+ end
end
MergeRequestsHelper.prepend_mod_with('MergeRequestsHelper')
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index f2b7c0064e4..88e68a52199 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -17,8 +17,11 @@ module PreferencesHelper
dashboards -= excluded_dashboard_choices
dashboards.map do |key|
- # Use `fetch` so `KeyError` gets raised when a key is missing
- [localized_dashboard_choices.fetch(key), key]
+ {
+ # Use `fetch` so `KeyError` gets raised when a key is missing
+ text: localized_dashboard_choices.fetch(key),
+ value: key
+ }
end
end
@@ -99,10 +102,12 @@ module PreferencesHelper
end
def language_choices
- options_for_select(
- selectable_locales_with_translation_level(Gitlab::I18n::MINIMUM_TRANSLATION_LEVEL).sort,
- current_user.preferred_language
- )
+ selectable_locales_with_translation_level(Gitlab::I18n::MINIMUM_TRANSLATION_LEVEL).sort.map do |lang, key|
+ {
+ text: lang,
+ value: key
+ }
+ end
end
def default_preferred_language_choices
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index caedc91ee8f..0012f098ab2 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -442,6 +442,14 @@ class MergeRequest < ApplicationRecord
)
end
+ scope :without_hidden, -> {
+ if Feature.enabled?(:hide_merge_requests_from_banned_users)
+ where_not_exists(Users::BannedUser.where('merge_requests.author_id = banned_users.user_id'))
+ else
+ all
+ end
+ }
+
def self.total_time_to_merge
join_metrics
.merge(MergeRequest::Metrics.with_valid_time_to_merge)
@@ -2007,6 +2015,10 @@ class MergeRequest < ApplicationRecord
false # overridden in EE
end
+ def hidden?
+ Feature.enabled?(:hide_merge_requests_from_banned_users) && author&.banned?
+ end
+
private
attr_accessor :skip_fetch_ref
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index fae66498038..52796ed1a1d 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -16,6 +16,9 @@ class IssuablePolicy < BasePolicy
condition(:is_incident) { @subject.incident? }
+ desc "Issuable is hidden"
+ condition(:hidden, scope: :subject) { @subject.hidden? }
+
rule { can?(:guest_access) & assignee_or_author & ~is_incident }.policy do
enable :read_issue
enable :update_issue
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index 174d68d14fc..d7f400732cd 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -21,9 +21,6 @@ class IssuePolicy < IssuablePolicy
desc "Issue is confidential"
condition(:confidential, scope: :subject) { @subject.confidential? }
- desc "Issue is hidden"
- condition(:hidden, scope: :subject) { @subject.hidden? }
-
desc "Issue is persisted"
condition(:persisted, scope: :subject) { @subject.persisted? }
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
index 1759cf057e4..49f9225a1d3 100644
--- a/app/policies/merge_request_policy.rb
+++ b/app/policies/merge_request_policy.rb
@@ -29,6 +29,10 @@ class MergeRequestPolicy < IssuablePolicy
enable :update_subscription
end
+ rule { hidden & ~admin }.policy do
+ prevent :read_merge_request
+ end
+
condition(:can_merge) { @subject.can_be_merged_by?(@user) }
rule { can_merge }.policy do
diff --git a/app/views/admin/dashboard/_stats_users_table.html.haml b/app/views/admin/dashboard/_stats_users_table.html.haml
new file mode 100644
index 00000000000..473384b8961
--- /dev/null
+++ b/app/views/admin/dashboard/_stats_users_table.html.haml
@@ -0,0 +1,49 @@
+%table.table.gl-text-gray-500
+ %tr
+ %td.gl-p-5!
+ = s_('AdminArea|Users without a Group and Project')
+ = render_if_exists 'admin/dashboard/included_free_in_license_tooltip'
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.without_groups_and_projects
+ = render_if_exists 'admin/dashboard/minimal_access_stats_row', users_statistics: @users_statistics
+ %tr
+ %td.gl-p-5!
+ = s_('AdminArea|Users with highest role')
+ %strong
+ = s_('AdminArea|Reporter')
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.with_highest_role_reporter
+ %tr
+ %td.gl-p-5!
+ = s_('AdminArea|Users with highest role')
+ %strong
+ = s_('AdminArea|Developer')
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.with_highest_role_developer
+ %tr
+ %td.gl-p-5!
+ = s_('AdminArea|Users with highest role')
+ %strong
+ = s_('AdminArea|Maintainer')
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.with_highest_role_maintainer
+ %tr
+ %td.gl-p-5!
+ = s_('AdminArea|Users with highest role')
+ %strong
+ = s_('AdminArea|Owner')
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.with_highest_role_owner
+ %tr
+ %td.gl-p-5!
+ = s_('AdminArea|Users with highest role')
+ %strong
+ = s_('AdminArea|Guest')
+ = render_if_exists 'admin/dashboard/included_free_in_license_tooltip'
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.with_highest_role_guest
+ %tr
+ %td.gl-p-5!
+ = s_('AdminArea|Bots')
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.bots
diff --git a/app/views/admin/dashboard/stats.html.haml b/app/views/admin/dashboard/stats.html.haml
index e0701812ba3..0a5a425397f 100644
--- a/app/views/admin/dashboard/stats.html.haml
+++ b/app/views/admin/dashboard/stats.html.haml
@@ -2,63 +2,15 @@
%h3.gl-my-6
= s_('AdminArea|Users statistics')
+
+= render 'admin/dashboard/stats_users_table', user_statistics: @users_statistics
+
+%p.gl-font-weight-bold.gl-mt-8
+ = s_('AdminArea|Totals')
+
%table.table.gl-text-gray-500
- %tr
- %td.gl-p-5!
- = s_('AdminArea|Users without a Group and Project')
- = render_if_exists 'admin/dashboard/included_free_in_license_tooltip'
- %td.gl-text-right{ class: 'gl-p-5!' }
- = @users_statistics&.without_groups_and_projects
- = render_if_exists 'admin/dashboard/minimal_access_stats_row', users_statistics: @users_statistics
- %tr
- %td.gl-p-5!
- = s_('AdminArea|Users with highest role')
- %strong
- = s_('AdminArea|Guest')
- = render_if_exists 'admin/dashboard/included_free_in_license_tooltip'
- %td.gl-text-right{ class: 'gl-p-5!' }
- = @users_statistics&.with_highest_role_guest
- %tr
- %td.gl-p-5!
- = s_('AdminArea|Users with highest role')
- %strong
- = s_('AdminArea|Reporter')
- %td.gl-text-right{ class: 'gl-p-5!' }
- = @users_statistics&.with_highest_role_reporter
- %tr
- %td.gl-p-5!
- = s_('AdminArea|Users with highest role')
- %strong
- = s_('AdminArea|Developer')
- %td.gl-text-right{ class: 'gl-p-5!' }
- = @users_statistics&.with_highest_role_developer
- %tr
- %td.gl-p-5!
- = s_('AdminArea|Users with highest role')
- %strong
- = s_('AdminArea|Maintainer')
- %td.gl-text-right{ class: 'gl-p-5!' }
- = @users_statistics&.with_highest_role_maintainer
- %tr
- %td.gl-p-5!
- = s_('AdminArea|Users with highest role')
- %strong
- = s_('AdminArea|Owner')
- %td.gl-text-right{ class: 'gl-p-5!' }
- = @users_statistics&.with_highest_role_owner
- %tr
- %td.gl-p-5!
- = s_('AdminArea|Bots')
- %td.gl-text-right{ class: 'gl-p-5!' }
- = @users_statistics&.bots
- = render_if_exists 'admin/dashboard/billable_users_row'
- %tr.bg-gray-light.gl-text-gray-900
- %td.gl-p-5!
- %strong
- = s_('AdminArea|Active users')
- %td.gl-text-right{ class: 'gl-p-5!' }
- %strong
- = @users_statistics&.active
+ = render_if_exists 'admin/dashboard/stats_active_users_row', users_statistics: @users_statistics
+
%tr.bg-gray-light.gl-text-gray-900
%td.gl-p-5!
%strong
@@ -70,6 +22,8 @@
%td.gl-p-5!
%strong
= s_('AdminArea|Total users')
+ %span
+ (#{s_('AdminArea|active users + blocked users')})
%td.gl-text-right{ class: 'gl-p-5!' }
%strong
= @users_statistics&.total
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 24ef9cf4dec..7d5242224e7 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -76,12 +76,7 @@
= f.select :layout, layout_choices, {}, class: 'gl-form-select custom-select'
.form-text.text-muted
= s_('Preferences|Choose between fixed (max. 1280px) and fluid (%{percentage}) application layout.').html_safe % { percentage: '100%' }
- .form-group
- = f.label :dashboard, class: 'label-bold' do
- = s_('Preferences|Dashboard')
- = f.select :dashboard, dashboard_choices, {}, class: 'select2'
- .form-text.text-muted
- = s_('Preferences|Choose what content you want to see by default on your dashboard.')
+ #user_dashboard.js-listbox-input{ data: { label: s_('Preferences|Dashboard'), description: s_('Preferences|Choose what content you want to see by default on your dashboard.'), name: 'user[dashboard]', items: dashboard_choices.to_json, value: current_user.dashboard } }
= render_if_exists 'profiles/preferences/group_overview_selector', f: f # EE-specific
@@ -130,17 +125,12 @@
= succeed '.' do
= link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'localization'), target: '_blank', rel: 'noopener noreferrer'
.col-lg-8
- .form-group
- = f.label :preferred_language, class: 'label-bold' do
- = _('Language')
- = f.select :preferred_language, language_choices, {}, class: 'select2'
- .form-text.text-muted
- = s_('Preferences|This feature is experimental and translations are not yet complete.')
- %p
- = link_to help_page_url('development/i18n/translation'), class: 'text-nowrap', target: '_blank', rel: 'noopener noreferrer' do
- = _("Help translate GitLab into your language")
- %span{ aria: { label: _('Open new window') } }
- = sprite_icon('external-link')
+ #user_preferred_language.js-listbox-input{ data: { label: _('Language'), description: s_('Preferences|This feature is experimental and translations are not yet complete.'), name: 'user[preferred_language]', items: language_choices.to_json, value: current_user.preferred_language } }
+ %p.gl-mt-n5
+ = link_to help_page_url('development/i18n/translation'), class: 'text-nowrap', target: '_blank', rel: 'noopener noreferrer' do
+ = _("Help translate GitLab into your language")
+ %span{ aria: { label: _('Open new window') } }
+ = sprite_icon('external-link')
.form-group
= f.label :first_day_of_week, class: 'label-bold' do
= _('First day of the week')
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 71f8e4c32f5..b96d869e9d7 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -12,6 +12,7 @@
.issuable-main-info
.merge-request-title.title
%span.merge-request-title-text.js-onboarding-mr-item
+ = hidden_merge_request_icon(merge_request)
= link_to merge_request.title, merge_request_path(merge_request), class: 'js-prefetch-document'
- if merge_request.tasks?
%span.task-status.d-none.d-sm-inline-block
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index a73d2aa5cc4..9d25603994a 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -16,7 +16,7 @@
.detail-page-header.border-bottom-0.pt-0.pb-0.gl-display-block{ class: "gl-md-display-flex! #{'is-merge-request' if moved_mr_sidebar_enabled? && !fluid_layout}" }
.detail-page-header-body
.issuable-meta.gl-display-flex
- #js-issuable-header-warnings
+ #js-issuable-header-warnings{ data: { hidden: @merge_request.hidden?.to_s } }
%h1.title.page-title.gl-font-size-h-display.gl-my-0.gl-display-inline-block{ data: { qa_selector: 'title_content' } }
= markdown_field(@merge_request, :title)
diff --git a/config/feature_flags/development/hide_merge_requests_from_banned_users.yml b/config/feature_flags/development/hide_merge_requests_from_banned_users.yml
new file mode 100644
index 00000000000..6ba0bc24196
--- /dev/null
+++ b/config/feature_flags/development/hide_merge_requests_from_banned_users.yml
@@ -0,0 +1,8 @@
+---
+name: hide_merge_requests_from_banned_users
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107836
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/386726
+milestone: "15.8"
+type: development
+group: group::anti-abuse
+default_enabled: false
diff --git a/config/feature_flags/development/phabricator_import.yml b/config/feature_flags/development/phabricator_import.yml
deleted file mode 100644
index 5340caef140..00000000000
--- a/config/feature_flags/development/phabricator_import.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: phabricator_import
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/13569
-rollout_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/1197
-milestone: '12.0'
-type: development
-group: group::import
-default_enabled: true
diff --git a/doc/architecture/blueprints/ci_pipeline_components/index.md b/doc/architecture/blueprints/ci_pipeline_components/index.md
index 26c77edbad6..3b23f0b5191 100644
--- a/doc/architecture/blueprints/ci_pipeline_components/index.md
+++ b/doc/architecture/blueprints/ci_pipeline_components/index.md
@@ -179,6 +179,7 @@ A component YAML file:
- Should be **validated statically** (for example: using JSON schema validators).
```yaml
+---
spec:
inputs:
website:
@@ -189,7 +190,8 @@ spec:
- unit
- integration
- system
-content: { ... }
+---
+# content of the component
```
Components that are released in the catalog must have a `README.md` file at the root directory of the repository.
@@ -293,11 +295,12 @@ NOTE:
Nesting of components is not permitted.
This limitation encourages cohesion at project level and keeps complexity low.
-## Input parameters `spec:inputs:` parameters
+## `spec:inputs:` parameters
If the component takes any input parameters they must be specified according to the following schema:
```yaml
+---
spec:
inputs:
website: # by default all declared inputs are mandatory.
@@ -308,8 +311,15 @@ spec:
- unit
- integration
- system
+---
+# content of the component
+my-job:
+ script: echo
```
+The YAML in this case contains 2 documents. The first document represents the specifications while the
+second document represents the content.
+
When using the component we pass the input parameters as follows:
```yaml
@@ -327,27 +337,28 @@ possible [inputs provided upstream](#input-parameters-for-pipelines).
Input parameters are validated as soon as possible:
1. Read the file `gitlab-template.yml` inside `org/my-component`.
-1. Parse `spec:inputs` and validate the parameters against this schema.
-1. If successfully validated, proceed with parsing `content:`. Return an error otherwise.
-1. Interpolate input parameters inside the component's `content:`.
+1. Parse `spec:inputs` from the specifications and validate the parameters against this schema.
+1. If successfully validated, proceed with parsing the content. Return an error otherwise.
+1. Interpolate input parameters inside the component's content.
```yaml
+---
spec:
inputs:
environment:
options: [test, staging, production]
-content:
- "run-tests-$[[ inputs.environment ]]":
- script: ./run-test
-
- scan-website:
- script: ./scan-website $[[ inputs.environment ]]
- rules:
- - if: $[[ inputs.environment ]] == 'staging'
- - if: $[[ inputs.environment ]] == 'production'
+---
+"run-tests-$[[ inputs.environment ]]":
+ script: ./run-test
+
+scan-website:
+ script: ./scan-website $[[ inputs.environment ]]
+ rules:
+ - if: $[[ inputs.environment ]] == 'staging'
+ - if: $[[ inputs.environment ]] == 'production'
```
-With `$[[ inputs.XXX ]]` inputs are interpolated immediately after parsing the `content:`.
+With `$[[ inputs.XXX ]]` inputs are interpolated immediately after parsing the content.
### Why input parameters and not environment variables?
@@ -391,17 +402,19 @@ include:
foo: bar
```
-Then the configuration being included must specify the inputs:
+Then the configuration being included must specify the inputs by defining a specification section in the YAML:
```yaml
+---
spec:
inputs:
foo:
-
+---
# rest of the configuration
```
-If a YAML includes content using `with:` but the including YAML doesn't specify `inputs:`, an error should be raised.
+If a YAML includes content using `with:` but the including YAML doesn't define `inputs:` in the specifications,
+an error should be raised.
|`with:`| `inputs:` | result |
| --- | --- | --- |
@@ -433,9 +446,10 @@ deploy-app:
deploy_environment: staging
```
-To solve the problem of `Run Pipeline` UI form we could fully leverage the `spec:inputs` schema:
+To solve the problem of `Run Pipeline` UI form we could fully leverage the `inputs` specifications:
```yaml
+---
spec:
inputs:
concurrency:
@@ -448,9 +462,11 @@ spec:
- canary # 2nd option
- production # 3rd option
default: staging # selected by default in the UI.
- # if `default:` is not specified, the user must explicitly select
- # an option.
+ # if `default:` is not specified, the user must explicitly select
+ # an option.
description: Deployment environment # optional: render as input label.
+---
+# rest of the pipeline config
```
## Limits
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
index 1b0a1e50445..0ee5b70c958 100644
--- a/doc/integration/gitlab.md
+++ b/doc/integration/gitlab.md
@@ -77,8 +77,8 @@ GitLab.com generates an application ID and secret key for you to use.
label: "Provider name", # optional label for login button, defaults to "GitLab.com"
app_id: "YOUR_APP_ID",
app_secret: "YOUR_APP_SECRET",
- args: { scope: "read_user" # optional: defaults to the scopes of the application
- , client_options: { site: "https://gitlab.example.com" } }
+ args: { scope: "read_user", # optional: defaults to the scopes of the application
+ client_options: { site: "https://gitlab.example.com" } }
}
]
```
diff --git a/doc/integration/jira/development_panel.md b/doc/integration/jira/development_panel.md
index bdb79d65d5e..fc0de843d37 100644
--- a/doc/integration/jira/development_panel.md
+++ b/doc/integration/jira/development_panel.md
@@ -69,7 +69,7 @@ To simplify administration, we recommend that a GitLab group maintainer or group
| Jira usage | GitLab.com customers need | GitLab self-managed customers need |
|------------|---------------------------|------------------------------------|
-| [Atlassian cloud](https://www.atlassian.com/migration/assess/why-cloud) | The [GitLab.com for Jira Cloud app](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview) installed from the [Atlassian Marketplace](https://marketplace.atlassian.com). This offers real-time sync between GitLab.com and Jira. For more information, see the documentation for the [GitLab.com for Jira Cloud app](connect-app.md). | The [GitLab.com for Jira Cloud app](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview), using a workaround process. See the documentation for [installing the GitLab.com for Jira Cloud app for self-managed instances](connect-app.md#install-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances) for more information. |
+| [Atlassian cloud](https://www.atlassian.com/migration/assess/why-cloud) | The [GitLab.com for Jira Cloud app](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview) installed from the [Atlassian Marketplace](https://marketplace.atlassian.com). This method offers real-time sync between GitLab.com and Jira. For more information, see [GitLab.com for Jira Cloud app](connect-app.md). | The GitLab.com for Jira Cloud app [using a workaround](connect-app.md#install-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances). When the `jira_connect_oauth_self_managed` feature flag is enabled, you can install the app from the [Atlassian Marketplace](https://marketplace.atlassian.com/). For more information, see [Connect the GitLab.com for Jira Cloud app for self-managed instances](connect-app.md#connect-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances). |
| Your own server | The [Jira DVCS (distributed version control system) connector](dvcs.md). This syncs data hourly. | The [Jira DVCS (distributed version control system) connector](dvcs.md). This syncs data hourly. |
Each GitLab project can be configured to connect to an entire Jira instance. That means after
diff --git a/doc/user/admin_area/moderate_users.md b/doc/user/admin_area/moderate_users.md
index c0daf029b1f..117781f7222 100644
--- a/doc/user/admin_area/moderate_users.md
+++ b/doc/user/admin_area/moderate_users.md
@@ -223,7 +223,7 @@ On self-managed GitLab, by default this feature is available.
To hide the feature, ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `ban_user_feature_flag`.
On GitLab.com, this feature is available to GitLab.com administrators only.
-GitLab administrators can ban and unban users. Banned users are blocked, and their issues are hidden.
+GitLab administrators can ban and unban users. Banned users are blocked, and their issues and merge requests are hidden.
The banned user's comments are still displayed. Hiding a banned user's comments is [tracked in this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/327356).
### Ban a user
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 955c2121b32..5d1db760bff 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -430,13 +430,13 @@ To learn more, read through the documentation on
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40942) in GitLab 13.4.
-Owners can add members with a "minimal access" role to a parent group. Such users don't automatically have access to
+Owners can add members with a "minimal access" role to a root group. Such users don't automatically have access to
projects and subgroups underneath. Owners must explicitly add these "minimal access" users to the specific subgroups and
projects.
You can use minimal access to give the same member more than one role in a group:
-1. Add the member to the parent group with a minimal access role.
+1. Add the member to the root group with a minimal access role.
1. Invite the member as a direct member with a specific role in any subgroup or project in that group.
Because of an [outstanding issue](https://gitlab.com/gitlab-org/gitlab/-/issues/267996), when minimal access users:
diff --git a/doc/user/project/merge_requests/status_checks.md b/doc/user/project/merge_requests/status_checks.md
index 74c3b3e24b6..93ef76ebb9e 100644
--- a/doc/user/project/merge_requests/status_checks.md
+++ b/doc/user/project/merge_requests/status_checks.md
@@ -151,12 +151,18 @@ the status check and it **will not** be recoverable.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327634) in GitLab 14.1.
> - UI [updated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91504) in GitLab 15.2.
+> - Ability to retry failed external status checks [added](https://gitlab.com/gitlab-org/gitlab/-/issues/383200) in GitLab 15.8.
The status checks widget displays in merge requests and displays the following statuses:
- **pending** (**{status-neutral}**), while GitLab waits for a response from an external status check.
- **success** (**{status-success}**) or **failed** (**{status-failed}**), when GitLab receives a response from an external status check.
+To retry a failed status check:
+
+1. Expand the merge request widget to show the list of external status checks.
+1. Select **Retry** (**{retry}**) on the failed external status check row. The status check is put back into a pending state.
+
An organization might have a policy that does not allow merging merge requests if
external status checks do not pass. However, the details in the widget are for informational
purposes only. GitLab does not prevent merging of merge requests that fail status checks.
diff --git a/lib/gitlab/phabricator_import.rb b/lib/gitlab/phabricator_import.rb
index 3885a9934d5..49e01eceb5b 100644
--- a/lib/gitlab/phabricator_import.rb
+++ b/lib/gitlab/phabricator_import.rb
@@ -5,8 +5,7 @@ module Gitlab
BaseError = Class.new(StandardError)
def self.available?
- Feature.enabled?(:phabricator_import) &&
- Gitlab::CurrentSettings.import_sources.include?('phabricator')
+ Gitlab::CurrentSettings.import_sources.include?('phabricator')
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4a4655f111c..d05f0c9807d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2590,15 +2590,18 @@ msgstr ""
msgid "AdminArea|All users created in the instance, including users who are not %{billable_users_link_start}billable users%{billable_users_link_end}."
msgstr ""
-msgid "AdminArea|Billable users"
-msgstr ""
-
msgid "AdminArea|Blocked users"
msgstr ""
msgid "AdminArea|Bots"
msgstr ""
+msgid "AdminArea|Breakdown of Billable users"
+msgstr ""
+
+msgid "AdminArea|Breakdown of Non-Billable users"
+msgstr ""
+
msgid "AdminArea|Components"
msgstr ""
@@ -2680,9 +2683,18 @@ msgstr ""
msgid "AdminArea|Stopping jobs failed"
msgstr ""
+msgid "AdminArea|Total Billable users"
+msgstr ""
+
+msgid "AdminArea|Total Non-Billable users"
+msgstr ""
+
msgid "AdminArea|Total users"
msgstr ""
+msgid "AdminArea|Totals"
+msgstr ""
+
msgid "AdminArea|Updated %{last_update_time}"
msgstr ""
@@ -2710,6 +2722,12 @@ msgstr ""
msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running."
msgstr ""
+msgid "AdminArea|active users + blocked users"
+msgstr ""
+
+msgid "AdminArea|total billable + total non-billable"
+msgstr ""
+
msgid "AdminDashboard|Error loading the statistics. Please try again"
msgstr ""
@@ -3310,7 +3328,7 @@ msgstr ""
msgid "AdminUsers|Is using seat"
msgstr ""
-msgid "AdminUsers|Issues authored by this user are hidden from other users."
+msgid "AdminUsers|Issues and merge requests authored by this user are hidden from other users."
msgstr ""
msgid "AdminUsers|It's you!"
@@ -42582,6 +42600,9 @@ msgstr ""
msgid "This %{issuableDisplayName} is locked. Only project members can comment."
msgstr ""
+msgid "This %{issuable} is hidden because its author has been banned"
+msgstr ""
+
msgid "This %{issuable} is locked. Only %{strong_open}project members%{strong_close} can comment."
msgstr ""
diff --git a/qa/Gemfile b/qa/Gemfile
index 0de1f7b1b8e..c8ee26227d7 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -2,7 +2,7 @@
source 'https://rubygems.org'
-gem 'gitlab-qa', '~> 8', '>= 8.14.1', require: 'gitlab/qa'
+gem 'gitlab-qa', '~> 8', '>= 8.15.1', require: 'gitlab/qa'
gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile
gem 'allure-rspec', '~> 2.20.0'
gem 'capybara', '~> 3.38.0'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 906a3d32e79..673f88ec0c9 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -102,7 +102,7 @@ GEM
gitlab (4.18.0)
httparty (~> 0.18)
terminal-table (>= 1.5.1)
- gitlab-qa (8.14.1)
+ gitlab-qa (8.15.1)
activesupport (~> 6.1)
gitlab (~> 4.18.0)
http (~> 5.0)
@@ -317,7 +317,7 @@ DEPENDENCIES
faraday-retry (~> 2.0)
fog-core (= 2.1.0)
fog-google (~> 1.19)
- gitlab-qa (~> 8, >= 8.14.1)
+ gitlab-qa (~> 8, >= 8.15.1)
influxdb-client (~> 2.9)
knapsack (~> 4.0)
nokogiri (~> 1.13, >= 1.13.10)
diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb
index 659fe198d49..3eddd0fd33a 100644
--- a/qa/qa/page/project/settings/protected_branches.rb
+++ b/qa/qa/page/project/settings/protected_branches.rb
@@ -51,10 +51,15 @@ module QA
within_element(:"allowed_to_#{action}_dropdown_content") do
click_on allowed[:roles][:description]
- allowed[:users].each { |user| click_on user.username } if allowed.key?(:users)
- allowed[:groups].each { |group| click_on group.name } if allowed.key?(:groups)
+ allowed[:users].each { |user| select_name user.username } if allowed.key?(:users)
+ allowed[:groups].each { |group| select_name group.name } if allowed.key?(:groups)
end
end
+
+ def select_name(name)
+ fill_element(:dropdown_input_field, name)
+ click_on name
+ end
end
end
end
diff --git a/qa/qa/page/project/web_ide/vscode.rb b/qa/qa/page/project/web_ide/vscode.rb
new file mode 100644
index 00000000000..7ad2859dd5b
--- /dev/null
+++ b/qa/qa/page/project/web_ide/vscode.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+# VSCode WebIDE is built off an iFrame application therefore we are uanble to use `qa-selectors`
+module QA
+ module Page
+ module Project
+ module WebIDE
+ class VSCode < Page::Base
+ # Use to Pass Test::Sanity::Selectors temporarily until iframe [data-qa-* selector added
+ view 'app/views/shared/_broadcast_message.html.haml' do
+ element :broadcast_notification_container
+ element :close_button
+ end
+
+ # Used for stablility, due to feature_caching of vscode_web_ide
+ def wait_for_ide_to_load
+ Support::Waiter.wait_until(max_duration: 60, reload_page: page, retry_on_exception: true) do
+ within_vscode_editor do
+ # vscode file_explorer element
+ page.has_css?('.explorer-folders-view', visible: true)
+ end
+ end
+ end
+
+ def within_vscode_editor(&block)
+ iframe = find('#ide iframe')
+ page.within_frame(iframe, &block)
+ end
+
+ def create_new_folder(name)
+ within_vscode_editor do
+ # Use for stability, WebIDE inside an iframe is finnicky
+ Support::Waiter.wait_until(max_duration: 60, retry_on_exception: true) do
+ page.find('.explorer-folders-view').right_click
+ # new_folder_button
+ page.has_css?('[aria-label="New Folder..."]', visible: true)
+ end
+
+ # Additonal wait for stability, webdriver sometimes moves too fast
+ Support::Waiter.wait_until(max_duration: 60, retry_on_exception: true) do
+ page.find('[aria-label="New Folder..."]').click
+ # Verify New Folder button is triggered and textbox is waiting for input
+ page.find('.explorer-item-edited', visible: true)
+ send_keys(name, :enter)
+ page.has_content?(name)
+ end
+ end
+ end
+
+ def commit_and_push(folder_name)
+ within_vscode_editor do
+ # Commit Tab
+ page.find('a.codicon-source-control-view-icon').click
+ send_keys(folder_name)
+ page.has_content?(folder_name)
+
+ # Commit Button
+ page.find('a.monaco-description-button').click
+ page.has_css?('.notification-list-item-details-row', visible: true)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide_new/add_new_directory_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide_new/add_new_directory_in_web_ide_spec.rb
new file mode 100644
index 00000000000..7b40c8a62c1
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide_new/add_new_directory_in_web_ide_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create', feature_flag: { name: 'vscode_web_ide', scope: :global }, product_group: :editor do
+ describe 'Add a directory in Web IDE' do
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'add-directory-project'
+ project.initialize_with_readme = true
+ end
+ end
+
+ before do
+ Runtime::Feature.enable(:vscode_web_ide)
+ Flow::Login.sign_in
+ project.visit!
+ end
+
+ after do
+ Runtime::Feature.disable(:vscode_web_ide)
+ end
+
+ context 'when a directory with the same name already exists' do
+ let(:directory_name) { 'first_directory' }
+
+ before do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.add_files(
+ [
+ {
+ file_path: 'first_directory/test_file.txt',
+ content: "Test file content"
+ }
+ ])
+ end
+ project.visit!
+ end
+
+ it 'throws an error', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/386760' do
+ Page::Project::Show.perform(&:open_web_ide!)
+ Page::Project::WebIDE::VSCode.perform do |ide|
+ ide.wait_for_ide_to_load
+ ide.create_new_folder(directory_name)
+ ide.within_vscode_editor do
+ expect(page).to have_content('A file or folder first_directory already exists at this location.')
+ end
+ end
+ end
+ end
+
+ context 'when user adds a new empty directory' do
+ let(:directory_name) { 'new_empty_directory' }
+
+ before do
+ Page::Project::Show.perform(&:open_web_ide!)
+ end
+
+ it 'shows successfully but not able to be committed',
+testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/386761' do
+ Page::Project::WebIDE::VSCode.perform do |ide|
+ ide.wait_for_ide_to_load
+ ide.create_new_folder(directory_name)
+ ide.commit_and_push(directory_name)
+ ide.within_vscode_editor do
+ expect(page).to have_content('No changes found. Not able to commit.')
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index cc09ad0dc67..346edd818b8 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -362,13 +362,30 @@ function verify_deploy() {
if [[ "${deployed}" == "true" ]]; then
echoinfo "[$(date '+%H:%M:%S')] Review app is deployed to ${CI_ENVIRONMENT_URL}"
- return 0
else
echoerr "[$(date '+%H:%M:%S')] Review app is not available at ${CI_ENVIRONMENT_URL}: see the logs from cURL above for more details"
return 1
fi
}
+# We need to be able to access the GitLab API to run this method.
+# Since we are creating a personal access token in `disable_sign_ups`,
+# This method should be executed after it.
+function verify_commit_sha() {
+ echoinfo "[$(date '+%H:%M:%S')] Checking the correct commit is deployed in the review-app:"
+ echo "Expected commit sha: ${CI_COMMIT_SHA}"
+
+ review_app_revision=$(curl --header "PRIVATE-TOKEN: ${REVIEW_APPS_ROOT_TOKEN}" "${CI_ENVIRONMENT_URL}/api/v4/metadata" | jq -r .revision)
+ echo "review-app revision: ${review_app_revision}"
+
+ if [[ "${CI_COMMIT_SHA}" != "${review_app_revision}"* ]]; then
+ echoerr "[$(date '+%H:%M:%S')] Review app revision is not the same as the current commit!"
+ return 1
+ fi
+
+ return 0
+}
+
function display_deployment_debug() {
local namespace="${CI_ENVIRONMENT_SLUG}"
diff --git a/spec/controllers/import/phabricator_controller_spec.rb b/spec/controllers/import/phabricator_controller_spec.rb
index 9827a6d077c..9be85a40d82 100644
--- a/spec/controllers/import/phabricator_controller_spec.rb
+++ b/spec/controllers/import/phabricator_controller_spec.rb
@@ -14,25 +14,14 @@ RSpec.describe Import::PhabricatorController do
context 'when the import source is not available' do
before do
- stub_feature_flags(phabricator_import: true)
stub_application_setting(import_sources: [])
end
it { is_expected.to have_gitlab_http_status(:not_found) }
end
- context 'when the feature is disabled' do
+ context 'when the import source is available' do
before do
- stub_feature_flags(phabricator_import: false)
- stub_application_setting(import_sources: ['phabricator'])
- end
-
- it { is_expected.to have_gitlab_http_status(:not_found) }
- end
-
- context 'when the import is available' do
- before do
- stub_feature_flags(phabricator_import: true)
stub_application_setting(import_sources: ['phabricator'])
end
diff --git a/spec/features/admin/dashboard_spec.rb b/spec/features/admin/dashboard_spec.rb
index baca60134b9..06f9c531e74 100644
--- a/spec/features/admin/dashboard_spec.rb
+++ b/spec/features/admin/dashboard_spec.rb
@@ -49,8 +49,7 @@ RSpec.describe 'admin visits dashboard' do
end
expect(page).to have_content('Blocked users 7')
- expect(page).to have_content('Total users 78')
- expect(page).to have_content('Active users 71')
+ expect(page).to have_content('Total users (active users + blocked users) 78')
end
end
diff --git a/spec/features/merge_request/admin_views_hidden_merge_request_spec.rb b/spec/features/merge_request/admin_views_hidden_merge_request_spec.rb
new file mode 100644
index 00000000000..0dbb42a633b
--- /dev/null
+++ b/spec/features/merge_request/admin_views_hidden_merge_request_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Admin views hidden merge request', feature_category: :insider_threat do
+ context 'when signed in as admin and viewing a hidden merge request', :js do
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:author) { create(:user, :banned) }
+ let_it_be(:project) { create(:project, :repository) }
+ let!(:merge_request) { create(:merge_request, source_project: project, author: author) }
+
+ before do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
+ visit(project_merge_request_path(project, merge_request))
+ end
+
+ it 'shows a hidden merge request icon' do
+ page.within('.detail-page-header-body') do
+ tooltip = format(_('This %{issuable} is hidden because its author has been banned'),
+ issuable: _('merge request'))
+ expect(page).to have_css("div[data-testid='hidden'][title='#{tooltip}']")
+ expect(page).to have_css('svg[data-testid="spam-icon"]')
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_requests/admin_views_hidden_merge_requests_spec.rb b/spec/features/merge_requests/admin_views_hidden_merge_requests_spec.rb
new file mode 100644
index 00000000000..e7727fbb9dc
--- /dev/null
+++ b/spec/features/merge_requests/admin_views_hidden_merge_requests_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Admin views hidden merge requests', feature_category: :insider_threat do
+ context 'when signed in as admin and viewing a hidden merge request' do
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:author) { create(:user, :banned) }
+ let_it_be(:project) { create(:project) }
+ let!(:merge_request) { create(:merge_request, source_project: project, author: author) }
+
+ before do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
+ visit(project_merge_requests_path(project))
+ end
+
+ it 'shows a hidden merge request icon' do
+ page.within("#merge_request_#{merge_request.id}") do
+ tooltip = format(_('This %{issuable} is hidden because its author has been banned'),
+ issuable: _('merge request'))
+ expect(page).to have_css("span[title='#{tooltip}']")
+ expect(page).to have_css('svg[data-testid="spam-icon"]')
+ end
+ end
+ end
+end
diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
index 9eee1b85e5e..12b1d0234ce 100644
--- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'User visits the profile preferences page', :js, feature_category: :users do
- include Select2Helper
+ include ListboxInputHelper
let(:user) { create(:user) }
@@ -30,7 +30,7 @@ RSpec.describe 'User visits the profile preferences page', :js, feature_category
describe 'User changes their default dashboard', :js do
it 'creates a flash message' do
- select2('stars', from: '#user_dashboard')
+ listbox_input_value 'stars', from: '#user_dashboard'
click_button 'Save changes'
wait_for_requests
@@ -39,7 +39,7 @@ RSpec.describe 'User visits the profile preferences page', :js, feature_category
end
it 'updates their preference' do
- select2('stars', from: '#user_dashboard')
+ listbox_input_value 'stars', from: '#user_dashboard'
click_button 'Save changes'
wait_for_requests
@@ -58,7 +58,7 @@ RSpec.describe 'User visits the profile preferences page', :js, feature_category
describe 'User changes their language', :js do
it 'creates a flash message', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/31404' do
- select2('en', from: '#user_preferred_language')
+ listbox_input_value 'en', from: '#user_preferred_language'
click_button 'Save changes'
wait_for_requests
@@ -68,7 +68,7 @@ RSpec.describe 'User visits the profile preferences page', :js, feature_category
it 'updates their preference' do
wait_for_requests
- select2('pt_BR', from: '#user_preferred_language')
+ listbox_input_value 'pt_BR', from: '#user_preferred_language'
click_button 'Save changes'
wait_for_requests
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 349ffd09324..5dfbce991ba 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -993,4 +993,29 @@ RSpec.describe MergeRequestsFinder do
end
end
end
+
+ context 'when the author of a merge request is banned', feature_category: :insider_threat do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:banned_user) { create(:user, :banned) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:banned_merge_request) { create(:merge_request, author: banned_user, source_project: project) }
+
+ subject { described_class.new(user).execute }
+
+ it { is_expected.not_to include(banned_merge_request) }
+
+ context 'when the user is an admin', :enable_admin_mode do
+ let_it_be(:user) { create(:user, :admin) }
+
+ it { is_expected.to include(banned_merge_request) }
+ end
+
+ context 'when the `hide_merge_requests_from_banned_users` feature flag is disabled' do
+ before do
+ stub_feature_flags(hide_merge_requests_from_banned_users: false)
+ end
+
+ it { is_expected.to include(banned_merge_request) }
+ end
+ end
end
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index 5209d9c2d2c..fad09562594 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -5,6 +5,7 @@ import httpStatus, {
HTTP_STATUS_ACCEPTED,
HTTP_STATUS_CREATED,
HTTP_STATUS_NO_CONTENT,
+ HTTP_STATUS_NOT_FOUND,
} from '~/lib/utils/http_status';
jest.mock('~/flash');
@@ -1715,12 +1716,12 @@ describe('Api', () => {
it('returns 404 for non-existing branch', () => {
jest.spyOn(axios, 'get');
- mock.onGet(expectedUrl).replyOnce(httpStatus.NOT_FOUND, {
+ mock.onGet(expectedUrl).replyOnce(HTTP_STATUS_NOT_FOUND, {
message: '404 Not found',
});
return Api.projectProtectedBranch(dummyProjectId, branchName).catch((error) => {
- expect(error.response.status).toBe(httpStatus.NOT_FOUND);
+ expect(error.response.status).toBe(HTTP_STATUS_NOT_FOUND);
expect(axios.get).toHaveBeenCalledWith(expectedUrl);
});
});
diff --git a/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js b/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js
index b2a25bc93ea..f4588c199a7 100644
--- a/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js
+++ b/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js
@@ -7,6 +7,7 @@ import axios from '~/lib/utils/axios_utils';
import httpStatusCodes, {
HTTP_STATUS_CONFLICT,
HTTP_STATUS_METHOD_NOT_ALLOWED,
+ HTTP_STATUS_NOT_FOUND,
} from '~/lib/utils/http_status';
jest.mock('~/captcha/wait_for_captcha_to_be_solved');
@@ -73,7 +74,7 @@ describe('registerCaptchaModalInterceptor', () => {
await expect(() => axios[method]('/endpoint-with-unrelated-error')).rejects.toThrow(
expect.objectContaining({
response: expect.objectContaining({
- status: httpStatusCodes.NOT_FOUND,
+ status: HTTP_STATUS_NOT_FOUND,
data: AXIOS_RESPONSE,
}),
}),
diff --git a/spec/frontend/ide/stores/modules/commit/actions_spec.js b/spec/frontend/ide/stores/modules/commit/actions_spec.js
index 4e8467de759..8601e13f7ca 100644
--- a/spec/frontend/ide/stores/modules/commit/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/actions_spec.js
@@ -366,17 +366,38 @@ describe('IDE commit module actions', () => {
});
describe('merge request', () => {
- it('redirects to new merge request page', async () => {
- jest.spyOn(eventHub, '$on').mockImplementation();
+ it.each`
+ branchName | targetBranchName | branchNameInURL | targetBranchInURL
+ ${'foo'} | ${'main'} | ${'foo'} | ${'main'}
+ ${'foo#bar'} | ${'main'} | ${'foo%23bar'} | ${'main'}
+ ${'foo#bar'} | ${'not#so#main'} | ${'foo%23bar'} | ${'not%23so%23main'}
+ `(
+ 'redirects to the correct new MR page when new branch is "$branchName" and target branch is "$targetBranchName"',
+ async ({ branchName, targetBranchName, branchNameInURL, targetBranchInURL }) => {
+ Object.assign(store.state.projects.abcproject, {
+ branches: {
+ [targetBranchName]: {
+ name: targetBranchName,
+ workingReference: '1',
+ commit: {
+ id: TEST_COMMIT_SHA,
+ },
+ can_push: true,
+ },
+ },
+ });
+ store.state.currentBranchId = targetBranchName;
+ store.state.commit.newBranchName = branchName;
- store.state.commit.commitAction = COMMIT_TO_NEW_BRANCH;
- store.state.commit.shouldCreateMR = true;
+ store.state.commit.commitAction = COMMIT_TO_NEW_BRANCH;
+ store.state.commit.shouldCreateMR = true;
- await store.dispatch('commit/commitChanges');
- expect(visitUrl).toHaveBeenCalledWith(
- `webUrl/-/merge_requests/new?merge_request[source_branch]=${store.getters['commit/placeholderBranchName']}&merge_request[target_branch]=main&nav_source=webide`,
- );
- });
+ await store.dispatch('commit/commitChanges');
+ expect(visitUrl).toHaveBeenCalledWith(
+ `webUrl/-/merge_requests/new?merge_request[source_branch]=${branchNameInURL}&merge_request[target_branch]=${targetBranchInURL}&nav_source=webide`,
+ );
+ },
+ );
it('does not redirect to new merge request page when shouldCreateMR is not checked', async () => {
jest.spyOn(eventHub, '$on').mockImplementation();
diff --git a/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js b/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js
index 56a75b6b954..09be1e333b3 100644
--- a/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js
@@ -10,8 +10,9 @@ import {
import * as messages from '~/ide/stores/modules/terminal/messages';
import * as mutationTypes from '~/ide/stores/modules/terminal/mutation_types';
import axios from '~/lib/utils/axios_utils';
-import httpStatus, {
+import {
HTTP_STATUS_FORBIDDEN,
+ HTTP_STATUS_NOT_FOUND,
HTTP_STATUS_UNPROCESSABLE_ENTITY,
} from '~/lib/utils/http_status';
@@ -105,7 +106,7 @@ describe('IDE store terminal check actions', () => {
);
});
- [HTTP_STATUS_FORBIDDEN, httpStatus.NOT_FOUND].forEach((status) => {
+ [HTTP_STATUS_FORBIDDEN, HTTP_STATUS_NOT_FOUND].forEach((status) => {
it(`hides tab, when status is ${status}`, () => {
const payload = { response: { status } };
diff --git a/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js b/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js
index df365442c67..9fd5f1a38d7 100644
--- a/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js
@@ -6,7 +6,7 @@ import { STARTING, PENDING, STOPPING, STOPPED } from '~/ide/stores/modules/termi
import * as messages from '~/ide/stores/modules/terminal/messages';
import * as mutationTypes from '~/ide/stores/modules/terminal/mutation_types';
import axios from '~/lib/utils/axios_utils';
-import httpStatus, { HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status';
+import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status';
jest.mock('~/flash');
@@ -285,7 +285,7 @@ describe('IDE store terminal session controls actions', () => {
);
});
- [httpStatus.NOT_FOUND, HTTP_STATUS_UNPROCESSABLE_ENTITY].forEach((status) => {
+ [HTTP_STATUS_NOT_FOUND, HTTP_STATUS_UNPROCESSABLE_ENTITY].forEach((status) => {
it(`dispatches request and startSession on ${status}`, () => {
mock
.onPost(state.session.retryPath, { branch: rootState.currentBranchId, format: 'json' })
diff --git a/spec/frontend/ide/stores/modules/terminal/messages_spec.js b/spec/frontend/ide/stores/modules/terminal/messages_spec.js
index d4fe432e7cf..f99496a4b98 100644
--- a/spec/frontend/ide/stores/modules/terminal/messages_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal/messages_spec.js
@@ -1,8 +1,9 @@
import { escape } from 'lodash';
import { TEST_HOST } from 'spec/test_constants';
import * as messages from '~/ide/stores/modules/terminal/messages';
-import httpStatus, {
+import {
HTTP_STATUS_FORBIDDEN,
+ HTTP_STATUS_NOT_FOUND,
HTTP_STATUS_UNPROCESSABLE_ENTITY,
} from '~/lib/utils/http_status';
import { sprintf } from '~/locale';
@@ -35,7 +36,7 @@ describe('IDE store terminal messages', () => {
});
it('returns unexpected error, with unexpected status', () => {
- const result = messages.configCheckError(httpStatus.NOT_FOUND, TEST_HELP_URL);
+ const result = messages.configCheckError(HTTP_STATUS_NOT_FOUND, TEST_HELP_URL);
expect(result).toBe(messages.UNEXPECTED_ERROR_CONFIG);
});
diff --git a/spec/frontend/issuable/components/issuable_by_email_spec.js b/spec/frontend/issuable/components/issuable_by_email_spec.js
index 01abf239e57..c472bfc42e8 100644
--- a/spec/frontend/issuable/components/issuable_by_email_spec.js
+++ b/spec/frontend/issuable/components/issuable_by_email_spec.js
@@ -5,7 +5,7 @@ import MockAdapter from 'axios-mock-adapter';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
-import httpStatus from '~/lib/utils/http_status';
+import httpStatus, { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status';
const initialEmail = 'user@gitlab.com';
@@ -144,7 +144,7 @@ describe('IssuableByEmail', () => {
});
it('should show a toast message when the request fails', async () => {
- mockAxios.onPut(resetPath).reply(httpStatus.NOT_FOUND, {});
+ mockAxios.onPut(resetPath).reply(HTTP_STATUS_NOT_FOUND, {});
wrapper = createComponent({
issuableType: 'issue',
diff --git a/spec/frontend/issuable/components/issuable_header_warnings_spec.js b/spec/frontend/issuable/components/issuable_header_warnings_spec.js
index e3a36dc8820..99aa6778e1e 100644
--- a/spec/frontend/issuable/components/issuable_header_warnings_spec.js
+++ b/spec/frontend/issuable/components/issuable_header_warnings_spec.js
@@ -7,7 +7,7 @@ import createIssueStore from '~/notes/stores';
import IssuableHeaderWarnings from '~/issuable/components/issuable_header_warnings.vue';
const ISSUABLE_TYPE_ISSUE = 'issue';
-const ISSUABLE_TYPE_MR = 'merge request';
+const ISSUABLE_TYPE_MR = 'merge_request';
Vue.use(Vuex);
@@ -57,6 +57,7 @@ describe('IssuableHeaderWarnings', () => {
beforeEach(() => {
store.getters.getNoteableData.confidential = confidentialStatus;
store.getters.getNoteableData.discussion_locked = lockStatus;
+ store.getters.getNoteableData.targetType = issuableType;
createComponent({ store, provide: { hidden: hiddenStatus } });
});
@@ -84,7 +85,7 @@ describe('IssuableHeaderWarnings', () => {
if (hiddenStatus) {
expect(hiddenIcon.attributes('title')).toBe(
- 'This issue is hidden because its author has been banned',
+ `This ${issuableType.replace('_', ' ')} is hidden because its author has been banned`,
);
expect(getBinding(hiddenIcon.element, 'gl-tooltip')).not.toBeUndefined();
}
diff --git a/spec/frontend/lib/utils/poll_until_complete_spec.js b/spec/frontend/lib/utils/poll_until_complete_spec.js
index 3ce17ecfc8c..e8ca2bddc4e 100644
--- a/spec/frontend/lib/utils/poll_until_complete_spec.js
+++ b/spec/frontend/lib/utils/poll_until_complete_spec.js
@@ -1,7 +1,10 @@
import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
-import httpStatusCodes, { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status';
+import httpStatusCodes, {
+ HTTP_STATUS_NO_CONTENT,
+ HTTP_STATUS_NOT_FOUND,
+} from '~/lib/utils/http_status';
import pollUntilComplete from '~/lib/utils/poll_until_complete';
const endpoint = `${TEST_HOST}/foo`;
@@ -66,7 +69,7 @@ describe('pollUntilComplete', () => {
const errorMessage = 'error message';
beforeEach(() => {
- mock.onGet(endpoint).replyOnce(httpStatusCodes.NOT_FOUND, errorMessage);
+ mock.onGet(endpoint).replyOnce(HTTP_STATUS_NOT_FOUND, errorMessage);
});
it('rejects with the error response', () =>
diff --git a/spec/frontend/notifications/components/custom_notifications_modal_spec.js b/spec/frontend/notifications/components/custom_notifications_modal_spec.js
index cd04adac72d..a787c9ebac0 100644
--- a/spec/frontend/notifications/components/custom_notifications_modal_spec.js
+++ b/spec/frontend/notifications/components/custom_notifications_modal_spec.js
@@ -5,7 +5,7 @@ import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import httpStatus from '~/lib/utils/http_status';
+import httpStatus, { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status';
import CustomNotificationsModal from '~/notifications/components/custom_notifications_modal.vue';
import { i18n } from '~/notifications/constants';
@@ -173,7 +173,7 @@ describe('CustomNotificationsModal', () => {
});
it('shows a toast message when the request fails', async () => {
- mockAxios.onGet('/api/v4/notification_settings').reply(httpStatus.NOT_FOUND, {});
+ mockAxios.onGet('/api/v4/notification_settings').reply(HTTP_STATUS_NOT_FOUND, {});
wrapper = createComponent();
wrapper.findComponent(GlModal).vm.$emit('show');
@@ -241,7 +241,7 @@ describe('CustomNotificationsModal', () => {
);
it('shows a toast message when the request fails', async () => {
- mockAxios.onPut('/api/v4/notification_settings').reply(httpStatus.NOT_FOUND, {});
+ mockAxios.onPut('/api/v4/notification_settings').reply(HTTP_STATUS_NOT_FOUND, {});
wrapper = createComponent();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
diff --git a/spec/frontend/notifications/components/notifications_dropdown_spec.js b/spec/frontend/notifications/components/notifications_dropdown_spec.js
index 7a98b374095..9eb29ce6ff6 100644
--- a/spec/frontend/notifications/components/notifications_dropdown_spec.js
+++ b/spec/frontend/notifications/components/notifications_dropdown_spec.js
@@ -4,7 +4,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import waitForPromises from 'helpers/wait_for_promises';
-import httpStatus from '~/lib/utils/http_status';
+import httpStatus, { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status';
import CustomNotificationsModal from '~/notifications/components/custom_notifications_modal.vue';
import NotificationsDropdown from '~/notifications/components/notifications_dropdown.vue';
import NotificationsDropdownItem from '~/notifications/components/notifications_dropdown_item.vue';
@@ -245,7 +245,7 @@ describe('NotificationsDropdown', () => {
});
it("won't update the selectedNotificationLevel and shows a toast message when the request fails and", async () => {
- mockAxios.onPut('/api/v4/notification_settings').reply(httpStatus.NOT_FOUND, {});
+ mockAxios.onPut('/api/v4/notification_settings').reply(HTTP_STATUS_NOT_FOUND, {});
wrapper = createComponent();
await clickDropdownItemAt(1);
diff --git a/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js b/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js
index f7c705c495a..7ed6a59c844 100644
--- a/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js
+++ b/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js
@@ -23,6 +23,7 @@ describe('ListboxInput', () => {
options: [{ text: 'Item 3', value: '3' }],
},
];
+ const id = 'id';
// Finders
const findGlFormGroup = () => wrapper.findComponent(GlFormGroup);
@@ -39,6 +40,9 @@ describe('ListboxInput', () => {
items,
...propsData,
},
+ attrs: {
+ id,
+ },
});
};
@@ -79,6 +83,10 @@ describe('ListboxInput', () => {
it('is not filterable with few items', () => {
expect(findGlListbox().props('searchable')).toBe(false);
});
+
+ it('passes attributes to the root element', () => {
+ expect(findGlFormGroup().attributes('id')).toBe(id);
+ });
});
describe('toggle text', () => {
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 2fb7c07cd36..f2e3e401766 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -605,4 +605,28 @@ RSpec.describe IssuablesHelper do
expect(helper.sidebar_milestone_tooltip_label(milestone)).to eq('&lt;img onerror=alert(1)&gt;<br/>Milestone')
end
end
+
+ describe '#hidden_issuable_icon', feature_category: :insider_threat do
+ let_it_be(:mock_svg) { '<svg></svg>'.html_safe }
+
+ before do
+ allow(helper).to receive(:sprite_icon).and_return(mock_svg)
+ end
+
+ context 'when issuable is an issue' do
+ let_it_be(:issuable) { build(:issue) }
+
+ it 'returns icon with tooltip' do
+ expect(helper.hidden_issuable_icon(issuable)).to eq("<span class=\"has-tooltip\" title=\"This issue is hidden because its author has been banned\">#{mock_svg}</span>")
+ end
+ end
+
+ context 'when issuable is a merge request' do
+ let_it_be(:issuable) { build(:merge_request) }
+
+ it 'returns icon with tooltip' do
+ expect(helper.hidden_issuable_icon(issuable)).to eq("<span class=\"has-tooltip\" title=\"This merge request is hidden because its author has been banned\">#{mock_svg}</span>")
+ end
+ end
+ end
end
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index 99f750bb858..898999e328e 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -25,15 +25,15 @@ RSpec.describe PreferencesHelper do
it 'provides better option descriptions' do
expect(helper.dashboard_choices).to match_array [
- ['Your Projects (default)', 'projects'],
- ['Starred Projects', 'stars'],
- ["Your Projects' Activity", 'project_activity'],
- ["Starred Projects' Activity", 'starred_project_activity'],
- ["Followed Users' Activity", 'followed_user_activity'],
- ["Your Groups", 'groups'],
- ["Your To-Do List", 'todos'],
- ["Assigned Issues", 'issues'],
- ["Assigned merge requests", 'merge_requests']
+ { text: "Your Projects (default)", value: 'projects' },
+ { text: "Starred Projects", value: 'stars' },
+ { text: "Your Projects' Activity", value: 'project_activity' },
+ { text: "Starred Projects' Activity", value: 'starred_project_activity' },
+ { text: "Followed Users' Activity", value: 'followed_user_activity' },
+ { text: "Your Groups", value: 'groups' },
+ { text: "Your To-Do List", value: 'todos' },
+ { text: "Assigned Issues", value: 'issues' },
+ { text: "Assigned merge requests", value: 'merge_requests' }
]
end
end
@@ -214,9 +214,9 @@ RSpec.describe PreferencesHelper do
stub_user(preferred_language: :en)
expect(helper.language_choices).to eq([
- '<option selected="selected" value="en">English (100% translated)</option>',
- '<option value="es">Spanish - español (65% translated)</option>'
- ].join("\n"))
+ { text: "English (100% translated)", value: 'en' },
+ { text: "Spanish - español (65% translated)", value: 'es' }
+ ])
end
end
diff --git a/spec/lib/gitlab/database/indexing_exclusive_lease_guard.rb_spec.rb b/spec/lib/gitlab/database/indexing_exclusive_lease_guard_spec.rb
index ddc9cdee92f..ddc9cdee92f 100644
--- a/spec/lib/gitlab/database/indexing_exclusive_lease_guard.rb_spec.rb
+++ b/spec/lib/gitlab/database/indexing_exclusive_lease_guard_spec.rb
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 02b3921ea55..7ccefbbcfba 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -165,6 +165,25 @@ RSpec.describe MergeRequest, factory_default: :keep do
expect(described_class.drafts).to eq([merge_request4])
end
end
+
+ describe '.without_hidden', feature_category: :insider_threat do
+ let_it_be(:banned_user) { create(:user, :banned) }
+ let_it_be(:hidden_merge_request) { create(:merge_request, :unique_branches, author: banned_user) }
+
+ it 'only returns public issuables' do
+ expect(described_class.without_hidden).not_to include(hidden_merge_request)
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(hide_merge_requests_from_banned_users: false)
+ end
+
+ it 'returns public and hidden issuables' do
+ expect(described_class.without_hidden).to include(hidden_merge_request)
+ end
+ end
+ end
end
describe '#squash?' do
@@ -5484,4 +5503,27 @@ RSpec.describe MergeRequest, factory_default: :keep do
it { is_expected.to be_empty }
end
+
+ describe '#hidden?', feature_category: :insider_threat do
+ let_it_be(:author) { create(:user) }
+ let(:merge_request) { build_stubbed(:merge_request, author: author) }
+
+ subject { merge_request.hidden? }
+
+ it { is_expected.to eq(false) }
+
+ context 'when the author is banned' do
+ let_it_be(:author) { create(:user, :banned) }
+
+ it { is_expected.to eq(true) }
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(hide_merge_requests_from_banned_users: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+ end
end
diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb
index 741a0db3009..c21e1244402 100644
--- a/spec/policies/merge_request_policy_spec.rb
+++ b/spec/policies/merge_request_policy_spec.rb
@@ -461,4 +461,34 @@ RSpec.describe MergeRequestPolicy do
end
end
end
+
+ context 'when the author of the merge request is banned', feature_category: :insider_threat do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:admin) { create(:user, :admin) }
+ let_it_be(:author) { create(:user, :banned) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:hidden_merge_request) { create(:merge_request, source_project: project, author: author) }
+
+ it 'does not allow non-admin user to read the merge_request' do
+ expect(permissions(user, hidden_merge_request)).not_to be_allowed(:read_merge_request)
+ end
+
+ it 'allows admin to read the merge_request', :enable_admin_mode do
+ expect(permissions(admin, hidden_merge_request)).to be_allowed(:read_merge_request)
+ end
+
+ context 'when the `hide_merge_requests_from_banned_users` feature flag is disabled' do
+ before do
+ stub_feature_flags(hide_merge_requests_from_banned_users: false)
+ end
+
+ it 'allows non-admin users to read the merge_request' do
+ expect(permissions(user, hidden_merge_request)).to be_allowed(:read_merge_request)
+ end
+
+ it 'allows admin users to read the merge_request', :enable_admin_mode do
+ expect(permissions(admin, hidden_merge_request)).to be_allowed(:read_merge_request)
+ end
+ end
+ end
end
diff --git a/spec/requests/projects/merge_requests_controller_spec.rb b/spec/requests/projects/merge_requests_controller_spec.rb
index ab0b0caff69..f441438a95a 100644
--- a/spec/requests/projects/merge_requests_controller_spec.rb
+++ b/spec/requests/projects/merge_requests_controller_spec.rb
@@ -10,18 +10,32 @@ RSpec.describe Projects::MergeRequestsController, feature_category: :source_code
describe 'GET #show' do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
- let_it_be(:project) { create :project, group: group }
+ let_it_be(:project) { create(:project, :public, group: group) }
- let_it_be(:merge_request) { create :merge_request, source_project: project, author: user }
+ let(:merge_request) { create :merge_request, source_project: project, author: user }
- before do
- login_as(user)
+ context 'when logged in' do
+ before do
+ login_as(user)
+ end
+
+ it_behaves_like "observability csp policy", described_class do
+ let(:tested_path) do
+ project_merge_request_path(project, merge_request)
+ end
+ end
end
- it_behaves_like "observability csp policy", described_class do
- let(:tested_path) do
- project_merge_request_path(project, merge_request)
+ context 'when the author of the merge request is banned', feature_category: :insider_threat do
+ let_it_be(:user) { create(:user, :banned) }
+
+ subject { response }
+
+ before do
+ get project_merge_request_path(project, merge_request)
end
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
end
end
diff --git a/spec/support/helpers/listbox_input_helper.rb b/spec/support/helpers/listbox_input_helper.rb
index ca7fbac5daa..c69190387f1 100644
--- a/spec/support/helpers/listbox_input_helper.rb
+++ b/spec/support/helpers/listbox_input_helper.rb
@@ -9,6 +9,12 @@ module ListboxInputHelper
end
end
+ def listbox_input_value(value, from:)
+ open_listbox_input(from) do
+ find("[role='option'][data-testid='listbox-item-#{value}']").click
+ end
+ end
+
def open_listbox_input(selector)
page.within(selector) do
page.find('button[aria-haspopup="listbox"]').click
diff --git a/spec/views/profiles/preferences/show.html.haml_spec.rb b/spec/views/profiles/preferences/show.html.haml_spec.rb
index 4e4499c3252..6e0c6d67d85 100644
--- a/spec/views/profiles/preferences/show.html.haml_spec.rb
+++ b/spec/views/profiles/preferences/show.html.haml_spec.rb
@@ -54,8 +54,9 @@ RSpec.describe 'profiles/preferences/show' do
end
it 'has helpful homepage setup guidance' do
- expect(rendered).to have_field('Dashboard')
- expect(rendered).to have_content('Choose what content you want to see by default on your dashboard.')
+ expect(rendered).to have_selector('[data-label="Dashboard"]')
+ expect(rendered).to have_selector("[data-description=" \
+ "'Choose what content you want to see by default on your dashboard.']")
end
end
diff --git a/workhorse/go.mod b/workhorse/go.mod
index 6d96784f6e3..b72e7e9ba8e 100644
--- a/workhorse/go.mod
+++ b/workhorse/go.mod
@@ -7,7 +7,7 @@ require (
github.com/BurntSushi/toml v1.2.1
github.com/FZambia/sentinel v1.1.1
github.com/alecthomas/chroma/v2 v2.4.0
- github.com/aws/aws-sdk-go v1.44.162
+ github.com/aws/aws-sdk-go v1.44.167
github.com/disintegration/imaging v1.6.2
github.com/getsentry/raven-go v0.2.0
github.com/golang-jwt/jwt/v4 v4.4.3
@@ -17,7 +17,7 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
- github.com/johannesboyne/gofakes3 v0.0.0-20221128113635-c2f5cc6b5294
+ github.com/johannesboyne/gofakes3 v0.0.0-20230104192229-1065b17a924f
github.com/jpillora/backoff v1.0.0
github.com/mitchellh/copystructure v1.2.0
github.com/prometheus/client_golang v1.14.0
diff --git a/workhorse/go.sum b/workhorse/go.sum
index 2f4ddb5fd1f..eed04007d1b 100644
--- a/workhorse/go.sum
+++ b/workhorse/go.sum
@@ -221,15 +221,15 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:W
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
-github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.33.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.43.11/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.45/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.68/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
-github.com/aws/aws-sdk-go v1.44.162 h1:hKAd+X+/BLxVMzH+4zKxbQcQQGrk2UhFX0OTu1Mhon8=
-github.com/aws/aws-sdk-go v1.44.162/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.167 h1:kQmBhGdZkQLU7AiHShSkBJ15zr8agy0QeaxXduvyp2E=
+github.com/aws/aws-sdk-go v1.44.167/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/aws/aws-sdk-go-v2 v1.16.8 h1:gOe9UPR98XSf7oEJCcojYg+N2/jCRm4DdeIsP85pIyQ=
github.com/aws/aws-sdk-go-v2 v1.16.8/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw=
@@ -629,6 +629,7 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -974,13 +975,14 @@ github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
-github.com/johannesboyne/gofakes3 v0.0.0-20221128113635-c2f5cc6b5294 h1:AJISYN7tPo3lGqwYmEYQdlftcQz48i8LNk/BRUKCTig=
-github.com/johannesboyne/gofakes3 v0.0.0-20221128113635-c2f5cc6b5294/go.mod h1:LIAXxPvcUXwOcTIj9LSNSUpE9/eMHalTWxsP/kmWxQI=
+github.com/johannesboyne/gofakes3 v0.0.0-20230104192229-1065b17a924f h1:m/Y0+A6QxQ00DPR0XmpOzoCmBOWO9J4XqBGiHonbMj8=
+github.com/johannesboyne/gofakes3 v0.0.0-20230104192229-1065b17a924f/go.mod h1:Cnosl0cRZIfKjTMuH49sQog2LeNsU5Hf4WnPIDWIDV0=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
@@ -1680,7 +1682,6 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190310074541-c10a0554eabf/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=