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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2024-01-23 21:10:52 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-23 21:10:52 +0300
commitabd2c6b32aabff4654b6be9cb98b59dcd3193fc4 (patch)
treefa4464b978bf45b40774bcea2e774b43f36aa27b
parent118083ac69c8cba0bc60633a15b9bb44e5f78281 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/layout/space_in_lambda_literal.yml1
-rw-r--r--app/assets/javascripts/pages/sessions/new/index.js18
-rw-r--r--app/assets/javascripts/pages/sessions/new/oauth_remember_me.js34
-rw-r--r--app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js86
-rw-r--r--app/assets/javascripts/repository/components/commit_info.vue17
-rw-r--r--app/assets/javascripts/tracking/internal_events.js11
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue1
-rw-r--r--app/assets/stylesheets/pages/commits.scss4
-rw-r--r--app/controllers/groups/autocomplete_sources_controller.rb2
-rw-r--r--app/controllers/projects/autocomplete_sources_controller.rb2
-rw-r--r--app/graphql/resolvers/projects/is_forked_resolver.rb25
-rw-r--r--app/graphql/types/project_type.rb6
-rw-r--r--app/models/ci/pipeline_artifact.rb3
-rw-r--r--app/models/ci/pipeline_config.rb3
-rw-r--r--app/models/ci/pipeline_metadata.rb3
-rw-r--r--app/models/fork_network_member.rb3
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/preview_markdown_service.rb2
-rw-r--r--app/services/quick_actions/target_service.rb14
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml2
-rw-r--r--db/post_migrate/20240123131916_remove_partition_id_default_value_for_ci_pipeline_metadata.rb13
-rw-r--r--db/post_migrate/20240123132014_remove_partition_id_default_value_for_ci_pipeline_artifact.rb13
-rw-r--r--db/post_migrate/20240123132048_remove_partition_id_default_value_for_ci_pipeline_config.rb13
-rw-r--r--db/schema_migrations/202401231319161
-rw-r--r--db/schema_migrations/202401231320141
-rw-r--r--db/schema_migrations/202401231320481
-rw-r--r--db/structure.sql6
-rw-r--r--doc/.vale/gitlab/Substitutions.yml6
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/api/members.md3
-rw-r--r--doc/ci/review_apps/index.md2
-rw-r--r--doc/ci/triggers/index.md8
-rw-r--r--doc/development/internal_analytics/internal_event_instrumentation/migration.md6
-rw-r--r--doc/user/application_security/sast/index.md5
-rw-r--r--doc/user/enterprise_user/index.md5
-rw-r--r--lib/api/helpers.rb6
-rw-r--r--lib/api/invitations.rb4
-rw-r--r--lib/bulk_imports/common/pipelines/members_pipeline.rb4
-rw-r--r--locale/gitlab.pot21
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock8
-rw-r--r--spec/features/users/login_spec.rb4
-rw-r--r--spec/frontend/fixtures/static/oauth_remember_me.html21
-rw-r--r--spec/frontend/oauth_remember_me_spec.js36
-rw-r--r--spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js90
-rw-r--r--spec/frontend/repository/components/commit_info_spec.js21
-rw-r--r--spec/frontend/tracking/internal_events_spec.js15
-rw-r--r--spec/graphql/types/project_type_spec.rb53
-rw-r--r--spec/lib/api/helpers_spec.rb21
-rw-r--r--spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status_spec.rb12
-rw-r--r--spec/models/fork_network_member_spec.rb26
-rw-r--r--spec/requests/groups/autocomplete_sources_spec.rb36
-rw-r--r--spec/services/quick_actions/target_service_spec.rb30
-rw-r--r--spec/support/helpers/login_helpers.rb2
55 files changed, 519 insertions, 222 deletions
diff --git a/.rubocop_todo/layout/space_in_lambda_literal.yml b/.rubocop_todo/layout/space_in_lambda_literal.yml
index 708944acbc7..1285fe6ef3f 100644
--- a/.rubocop_todo/layout/space_in_lambda_literal.yml
+++ b/.rubocop_todo/layout/space_in_lambda_literal.yml
@@ -277,7 +277,6 @@ Layout/SpaceInLambdaLiteral:
- 'ee/lib/ee/api/entities/list.rb'
- 'ee/lib/ee/api/entities/member.rb'
- 'ee/lib/ee/api/entities/project_approval_rule.rb'
- - 'ee/lib/ee/api/entities/user_basic.rb'
- 'ee/lib/ee/api/entities/vulnerability_issue_link.rb'
- 'ee/lib/ee/gitlab/background_migration/backfill_epic_cache_counts.rb'
- 'ee/lib/ee/gitlab/background_migration/delete_approval_rules_with_vulnerability.rb'
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js
index ee1a7633a11..21456564d3b 100644
--- a/app/assets/javascripts/pages/sessions/new/index.js
+++ b/app/assets/javascripts/pages/sessions/new/index.js
@@ -1,12 +1,14 @@
-import $ from 'jquery';
import initVueAlerts from '~/vue_alerts';
import NoEmojiValidator from '~/emoji/no_emoji_validator';
import { initLanguageSwitcher } from '~/language_switcher';
import LengthValidator from '~/validators/length_validator';
import mountEmailVerificationApplication from '~/sessions/new';
import { renderGFM } from '~/behaviors/markdown/render_gfm';
-import OAuthRememberMe from './oauth_remember_me';
-import preserveUrlFragment from './preserve_url_fragment';
+import {
+ appendUrlFragment,
+ appendRedirectQuery,
+ toggleRememberMeQuery,
+} from './preserve_url_fragment';
import SigninTabsMemoizer from './signin_tabs_memoizer';
import UsernameValidator from './username_validator';
@@ -15,13 +17,9 @@ new LengthValidator(); // eslint-disable-line no-new
new SigninTabsMemoizer(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new
-new OAuthRememberMe({
- container: $('.js-oauth-login'),
-}).bindEvents();
-
-// Save the URL fragment from the current window location. This will be present if the user was
-// redirected to sign-in after attempting to access a protected URL that included a fragment.
-preserveUrlFragment(window.location.hash);
+appendUrlFragment();
+appendRedirectQuery();
+toggleRememberMeQuery();
initVueAlerts();
initLanguageSwitcher();
mountEmailVerificationApplication();
diff --git a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
deleted file mode 100644
index 3336b094560..00000000000
--- a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import $ from 'jquery';
-import { mergeUrlParams, removeParams } from '~/lib/utils/url_utility';
-
-/**
- * OAuth-based login buttons have a separate "remember me" checkbox.
- *
- * Toggling this checkbox adds/removes a `remember_me` parameter to the
- * login buttons' parent form action, which is passed on to the omniauth callback.
- */
-
-export default class OAuthRememberMe {
- constructor(opts = {}) {
- this.container = opts.container || '';
- }
-
- bindEvents() {
- $('#remember_me_omniauth', this.container).on('click', this.toggleRememberMe);
- }
-
- toggleRememberMe(event) {
- const rememberMe = $(event.target).is(':checked');
-
- $('.js-oauth-login form', this.container).each((_, form) => {
- const $form = $(form);
- const href = $form.attr('action');
-
- if (rememberMe) {
- $form.attr('action', mergeUrlParams({ remember_me: 1 }, href));
- } else {
- $form.attr('action', removeParams(['remember_me'], href));
- }
- });
- }
-}
diff --git a/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js b/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js
index 54ec3c52f62..de48a457bcd 100644
--- a/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js
+++ b/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js
@@ -1,32 +1,72 @@
-import { mergeUrlParams, setUrlFragment } from '~/lib/utils/url_utility';
+import { mergeUrlParams, removeParams, setUrlFragment } from '~/lib/utils/url_utility';
/**
- * Ensure the given URL fragment is preserved by appending it to sign-in/sign-up form actions and
- * OAuth/SAML login links.
+ * Append the fragment to all non-OAuth login form actions so it is preserved
+ * when the user is eventually redirected back to the originally requested URL.
*
* @param fragment {string} - url fragment to be preserved
*/
-export default function preserveUrlFragment(fragment = '') {
- if (fragment) {
- const normalFragment = fragment.replace(/^#/, '');
-
- // Append the fragment to all sign-in/sign-up form actions so it is preserved when the user is
- // eventually redirected back to the originally requested URL.
- const forms = document.querySelectorAll('.js-non-oauth-login form');
- Array.prototype.forEach.call(forms, (form) => {
- const actionWithFragment = setUrlFragment(form.getAttribute('action'), `#${normalFragment}`);
- form.setAttribute('action', actionWithFragment);
- });
+export function appendUrlFragment(fragment = document.location.hash) {
+ if (!fragment) {
+ return;
+ }
+
+ const normalFragment = fragment.replace(/^#/, '');
+ const forms = document.querySelectorAll('.js-non-oauth-login form');
+ forms.forEach((form) => {
+ const actionWithFragment = setUrlFragment(form.getAttribute('action'), `#${normalFragment}`);
+ form.setAttribute('action', actionWithFragment);
+ });
+}
+
+/**
+ * Append a redirect_fragment query param to all OAuth login form actions. The
+ * redirect_fragment query param will be available in the omniauth callback upon
+ * successful authentication.
+ *
+ * @param {string} fragment - url fragment to be preserved
+ */
+export function appendRedirectQuery(fragment = document.location.hash) {
+ if (!fragment) {
+ return;
+ }
+
+ const normalFragment = fragment.replace(/^#/, '');
+ const oauthForms = document.querySelectorAll('.js-oauth-login form');
+ oauthForms.forEach((oauthForm) => {
+ const newHref = mergeUrlParams(
+ { redirect_fragment: normalFragment },
+ oauthForm.getAttribute('action'),
+ );
+ oauthForm.setAttribute('action', newHref);
+ });
+}
+
+/**
+ * OAuth login buttons have a separate "remember me" checkbox.
+ *
+ * Toggling this checkbox adds/removes a `remember_me` parameter to the
+ * login form actions, which is passed on to the omniauth callback.
+ */
+export function toggleRememberMeQuery() {
+ const oauthForms = document.querySelectorAll('.js-oauth-login form');
+ const checkbox = document.querySelector('#js-remember-me-omniauth');
+
+ if (oauthForms.length === 0 || !checkbox) {
+ return;
+ }
+
+ checkbox.addEventListener('change', ({ currentTarget }) => {
+ oauthForms.forEach((oauthForm) => {
+ const href = oauthForm.getAttribute('action');
+ let newHref;
+ if (currentTarget.checked) {
+ newHref = mergeUrlParams({ remember_me: '1' }, href);
+ } else {
+ newHref = removeParams(['remember_me'], href);
+ }
- // Append a redirect_fragment query param to all oauth provider links. The redirect_fragment
- // query param will be available in the omniauth callback upon successful authentication
- const oauthForms = document.querySelectorAll('.js-oauth-login form');
- Array.prototype.forEach.call(oauthForms, (oauthForm) => {
- const newHref = mergeUrlParams(
- { redirect_fragment: normalFragment },
- oauthForm.getAttribute('action'),
- );
oauthForm.setAttribute('action', newHref);
});
- }
+ });
}
diff --git a/app/assets/javascripts/repository/components/commit_info.vue b/app/assets/javascripts/repository/components/commit_info.vue
index 319ce2cea84..9b6b0f1cb2a 100644
--- a/app/assets/javascripts/repository/components/commit_info.vue
+++ b/app/assets/javascripts/repository/components/commit_info.vue
@@ -26,6 +26,11 @@ export default {
type: Object,
required: true,
},
+ span: {
+ type: Number,
+ required: false,
+ default: null,
+ },
prevBlameLink: {
type: String,
required: false,
@@ -43,6 +48,9 @@ export default {
avatarLinkAltText() {
return sprintf(__(`%{username}'s avatar`), { username: this.commit.authorName });
},
+ truncateAuthorName() {
+ return typeof this.span === 'number' && this.span < 3;
+ },
},
methods: {
toggleShowDescription() {
@@ -102,18 +110,23 @@ export default {
@click="toggleShowDescription"
/>
</div>
- <div class="committer gl-flex-basis-full">
+ <div
+ class="committer gl-flex-basis-full"
+ :class="truncateAuthorName ? 'gl-display-inline-flex' : ''"
+ data-testid="committer"
+ >
<gl-link
v-if="commit.author"
:href="commit.author.webPath"
class="commit-author-link js-user-link"
+ :class="truncateAuthorName ? 'gl-display-inline-block gl-text-truncate' : ''"
>
{{ commit.author.name }}</gl-link
>
<template v-else>
{{ commit.authorName }}
</template>
- {{ $options.i18n.authored }}
+ {{ $options.i18n.authored }}&nbsp;
<timeago-tooltip :time="commit.authoredDate" tooltip-placement="bottom" />
</div>
<pre
diff --git a/app/assets/javascripts/tracking/internal_events.js b/app/assets/javascripts/tracking/internal_events.js
index 7da6da16d6f..d4469382be4 100644
--- a/app/assets/javascripts/tracking/internal_events.js
+++ b/app/assets/javascripts/tracking/internal_events.js
@@ -9,10 +9,13 @@ const InternalEvents = {
/**
*
* @param {string} event
+ * @param {string} category - The category of the event. This is optional and
+ * defaults to the page name where the event was triggered. It's advised not to use
+ * this parameter for new events unless absolutely necessary.
*/
- trackEvent(event) {
+ trackEvent(event, category = undefined) {
API.trackInternalEvent(event);
- Tracking.event(undefined, event, {
+ Tracking.event(category, event, {
context: {
schema: SERVICE_PING_SCHEMA,
data: {
@@ -30,8 +33,8 @@ const InternalEvents = {
mixin() {
return {
methods: {
- trackEvent(event) {
- InternalEvents.trackEvent(event);
+ trackEvent(event, category = undefined) {
+ InternalEvents.trackEvent(event, category);
},
},
};
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue
index e2fd4477f0a..3205ec2b73b 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue
@@ -30,6 +30,7 @@ export default {
class="gl-display-flex gl-absolute gl-px-3"
:style="{ top: blame.blameOffset }"
:commit="blame.commit"
+ :span="blame.span"
:prev-blame-link="blame.commitData && blame.commitData.projectBlameLink"
/>
</div>
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index b0519119bd8..e455682dcff 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -136,6 +136,10 @@
.commit-author-link {
color: $gl-text-color;
}
+
+ .commit-author-link.gl-text-truncate {
+ max-width: 20ch;
+ }
}
}
diff --git a/app/controllers/groups/autocomplete_sources_controller.rb b/app/controllers/groups/autocomplete_sources_controller.rb
index 191720f69a0..8a3ec13f720 100644
--- a/app/controllers/groups/autocomplete_sources_controller.rb
+++ b/app/controllers/groups/autocomplete_sources_controller.rb
@@ -51,7 +51,7 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController
# TODO https://gitlab.com/gitlab-org/gitlab/-/issues/388541
# type_id is a misnomer. QuickActions::TargetService actually requires an iid.
QuickActions::TargetService
- .new(nil, current_user, group: @group)
+ .new(container: @group, current_user: current_user)
.execute(params[:type], params[:type_id])
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb
index dc10004c62b..c496a326051 100644
--- a/app/controllers/projects/autocomplete_sources_controller.rb
+++ b/app/controllers/projects/autocomplete_sources_controller.rb
@@ -59,7 +59,7 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController
# TODO https://gitlab.com/gitlab-org/gitlab/-/issues/388541
# type_id is a misnomer. QuickActions::TargetService actually requires an iid.
QuickActions::TargetService
- .new(project, current_user)
+ .new(container: project, current_user: current_user)
.execute(target_type, params[:type_id])
end
diff --git a/app/graphql/resolvers/projects/is_forked_resolver.rb b/app/graphql/resolvers/projects/is_forked_resolver.rb
new file mode 100644
index 00000000000..f1413543b7c
--- /dev/null
+++ b/app/graphql/resolvers/projects/is_forked_resolver.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Projects
+ class IsForkedResolver < BaseResolver
+ type GraphQL::Types::Boolean, null: false
+
+ def resolve
+ lazy_fork_network_members = BatchLoader::GraphQL.for(object.id).batch do |ids, loader|
+ ForkNetworkMember.by_projects(ids)
+ .with_fork_network
+ .find_each do |fork_network_member|
+ loader.call(fork_network_member.project_id, fork_network_member)
+ end
+ end
+
+ Gitlab::Graphql::Lazy.with_value(lazy_fork_network_members) do |fork_network_member|
+ next false if fork_network_member.nil?
+
+ fork_network_member.fork_network.root_project_id != object.id
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index aacd67e269e..bedcd08fcb4 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -685,6 +685,12 @@ module Types
description: 'Project allows assigning multiple reviewers to a merge request.',
null: false
+ field :is_forked,
+ GraphQL::Types::Boolean,
+ resolver: Resolvers::Projects::IsForkedResolver,
+ description: 'Project is forked.',
+ null: false
+
def timelog_categories
object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories)
end
diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb
index e0e6906f211..05c9535cef1 100644
--- a/app/models/ci/pipeline_artifact.rb
+++ b/app/models/ci/pipeline_artifact.rb
@@ -10,6 +10,9 @@ module Ci
include FileStoreMounter
include Lockable
include Presentable
+ include SafelyChangeColumnDefault
+
+ columns_changing_default :partition_id
FILE_SIZE_LIMIT = 10.megabytes.freeze
EXPIRATION_DATE = 1.week.freeze
diff --git a/app/models/ci/pipeline_config.rb b/app/models/ci/pipeline_config.rb
index 11decd3fc66..8e992aae2c5 100644
--- a/app/models/ci/pipeline_config.rb
+++ b/app/models/ci/pipeline_config.rb
@@ -3,6 +3,9 @@
module Ci
class PipelineConfig < Ci::ApplicationRecord
include Ci::Partitionable
+ include SafelyChangeColumnDefault
+
+ columns_changing_default :partition_id
self.table_name = 'ci_pipelines_config'
self.primary_key = :pipeline_id
diff --git a/app/models/ci/pipeline_metadata.rb b/app/models/ci/pipeline_metadata.rb
index 21d102374f0..39e2ef5cebb 100644
--- a/app/models/ci/pipeline_metadata.rb
+++ b/app/models/ci/pipeline_metadata.rb
@@ -4,6 +4,9 @@ module Ci
class PipelineMetadata < Ci::ApplicationRecord
include Ci::Partitionable
include Importable
+ include SafelyChangeColumnDefault
+
+ columns_changing_default :partition_id
self.primary_key = :pipeline_id
diff --git a/app/models/fork_network_member.rb b/app/models/fork_network_member.rb
index f18c306cf91..023f948d5f9 100644
--- a/app/models/fork_network_member.rb
+++ b/app/models/fork_network_member.rb
@@ -9,6 +9,9 @@ class ForkNetworkMember < ApplicationRecord
after_destroy :cleanup_fork_network
+ scope :by_projects, ->(ids) { where(project_id: ids) }
+ scope :with_fork_network, -> { joins(:fork_network).includes(:fork_network) }
+
private
def cleanup_fork_network
diff --git a/app/models/user.rb b/app/models/user.rb
index ab5572e5b19..05e35b217f4 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -631,6 +631,8 @@ class User < MainClusterwide::ApplicationRecord
.trusted_with_spam)
end
+ scope :preload_user_detail, -> { preload(:user_detail) }
+
def self.supported_keyset_orderings
{
id: [:asc, :desc],
diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb
index 10aef87332a..31f79bc7164 100644
--- a/app/services/preview_markdown_service.rb
+++ b/app/services/preview_markdown_service.rb
@@ -55,7 +55,7 @@ class PreviewMarkdownService < BaseService
def find_commands_target
QuickActions::TargetService
- .new(project, current_user, group: params[:group])
+ .new(container: project, current_user: current_user, params: { group: params[:group] })
.execute(target_type, target_id)
end
diff --git a/app/services/quick_actions/target_service.rb b/app/services/quick_actions/target_service.rb
index 04ae5287302..63e2c58fc55 100644
--- a/app/services/quick_actions/target_service.rb
+++ b/app/services/quick_actions/target_service.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QuickActions
- class TargetService < BaseService
+ class TargetService < BaseContainerService
def execute(type, type_iid)
case type&.downcase
when 'workitem'
@@ -19,15 +19,15 @@ module QuickActions
# rubocop: disable CodeReuse/ActiveRecord
def work_item(type_iid)
- WorkItems::WorkItemsFinder.new(current_user, project_id: project.id).find_by(iid: type_iid)
+ WorkItems::WorkItemsFinder.new(current_user, **parent_params).find_by(iid: type_iid)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def issue(type_iid)
- return project.issues.build if type_iid.nil?
+ return container.issues.build if type_iid.nil?
- IssuesFinder.new(current_user, project_id: project.id).find_by(iid: type_iid) || project.issues.build
+ IssuesFinder.new(current_user, **parent_params).find_by(iid: type_iid) || container.issues.build
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -42,7 +42,11 @@ module QuickActions
def commit(type_iid)
project.commit(type_iid)
end
+
+ def parent_params
+ group_container? ? { group_id: group.id } : { project_id: project.id }
+ end
end
end
-QuickActions::TargetService.prepend_mod_with('QuickActions::TargetService')
+QuickActions::TargetService.prepend_mod
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index 8197abcc787..5fbb20f7535 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -12,6 +12,6 @@
data: { testid: test_id_for_provider(provider) },
id: "oauth-login-#{provider}"
- if render_remember_me
- = render Pajamas::CheckboxTagComponent.new(name: 'remember_me_omniauth', value: nil) do |c|
+ = render Pajamas::CheckboxTagComponent.new(name: 'js-remember-me-omniauth', value: nil) do |c|
- c.with_label do
= _('Remember me')
diff --git a/db/post_migrate/20240123131916_remove_partition_id_default_value_for_ci_pipeline_metadata.rb b/db/post_migrate/20240123131916_remove_partition_id_default_value_for_ci_pipeline_metadata.rb
new file mode 100644
index 00000000000..a2a0cc7be87
--- /dev/null
+++ b/db/post_migrate/20240123131916_remove_partition_id_default_value_for_ci_pipeline_metadata.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class RemovePartitionIdDefaultValueForCiPipelineMetadata < Gitlab::Database::Migration[2.2]
+ milestone '16.9'
+ enable_lock_retries!
+
+ TABLE_NAME = :ci_pipeline_metadata
+ COLUM_NAME = :partition_id
+
+ def change
+ change_column_default(TABLE_NAME, COLUM_NAME, from: 100, to: nil)
+ end
+end
diff --git a/db/post_migrate/20240123132014_remove_partition_id_default_value_for_ci_pipeline_artifact.rb b/db/post_migrate/20240123132014_remove_partition_id_default_value_for_ci_pipeline_artifact.rb
new file mode 100644
index 00000000000..205c7b19db6
--- /dev/null
+++ b/db/post_migrate/20240123132014_remove_partition_id_default_value_for_ci_pipeline_artifact.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class RemovePartitionIdDefaultValueForCiPipelineArtifact < Gitlab::Database::Migration[2.2]
+ milestone '16.9'
+ enable_lock_retries!
+
+ TABLE_NAME = :ci_pipeline_artifacts
+ COLUM_NAME = :partition_id
+
+ def change
+ change_column_default(TABLE_NAME, COLUM_NAME, from: 100, to: nil)
+ end
+end
diff --git a/db/post_migrate/20240123132048_remove_partition_id_default_value_for_ci_pipeline_config.rb b/db/post_migrate/20240123132048_remove_partition_id_default_value_for_ci_pipeline_config.rb
new file mode 100644
index 00000000000..b0a92807f09
--- /dev/null
+++ b/db/post_migrate/20240123132048_remove_partition_id_default_value_for_ci_pipeline_config.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class RemovePartitionIdDefaultValueForCiPipelineConfig < Gitlab::Database::Migration[2.2]
+ milestone '16.9'
+ enable_lock_retries!
+
+ TABLE_NAME = :ci_pipelines_config
+ COLUM_NAME = :partition_id
+
+ def change
+ change_column_default(TABLE_NAME, COLUM_NAME, from: 100, to: nil)
+ end
+end
diff --git a/db/schema_migrations/20240123131916 b/db/schema_migrations/20240123131916
new file mode 100644
index 00000000000..5377f7f4fb9
--- /dev/null
+++ b/db/schema_migrations/20240123131916
@@ -0,0 +1 @@
+43ff332582062a104cef5449444034363c1a71d288bcae7dfdeefbd69500186e \ No newline at end of file
diff --git a/db/schema_migrations/20240123132014 b/db/schema_migrations/20240123132014
new file mode 100644
index 00000000000..719730631bd
--- /dev/null
+++ b/db/schema_migrations/20240123132014
@@ -0,0 +1 @@
+29392953f2fce7fb1a24dbc49f1ea30c49b1006551599bff98edc4de8061106b \ No newline at end of file
diff --git a/db/schema_migrations/20240123132048 b/db/schema_migrations/20240123132048
new file mode 100644
index 00000000000..a8a046d0a04
--- /dev/null
+++ b/db/schema_migrations/20240123132048
@@ -0,0 +1 @@
+d14905475e591b7fa855097434d0e810fbb5a0890d7feb7b4fe8a22d5d75335f \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index cd9e8be6d35..7631fecfe7a 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -14603,7 +14603,7 @@ CREATE TABLE ci_pipeline_artifacts (
verification_checksum bytea,
verification_failure text,
locked smallint DEFAULT 2,
- partition_id bigint DEFAULT 100 NOT NULL,
+ partition_id bigint NOT NULL,
CONSTRAINT check_191b5850ec CHECK ((char_length(file) <= 255)),
CONSTRAINT check_abeeb71caf CHECK ((file IS NOT NULL)),
CONSTRAINT ci_pipeline_artifacts_verification_failure_text_limit CHECK ((char_length(verification_failure) <= 255))
@@ -14658,7 +14658,7 @@ CREATE TABLE ci_pipeline_metadata (
name text,
auto_cancel_on_new_commit smallint DEFAULT 0 NOT NULL,
auto_cancel_on_job_failure smallint DEFAULT 0 NOT NULL,
- partition_id bigint DEFAULT 100 NOT NULL,
+ partition_id bigint NOT NULL,
CONSTRAINT check_9d3665463c CHECK ((char_length(name) <= 255))
);
@@ -14782,7 +14782,7 @@ CREATE TABLE ci_pipelines (
CREATE TABLE ci_pipelines_config (
pipeline_id bigint NOT NULL,
content text NOT NULL,
- partition_id bigint DEFAULT 100 NOT NULL
+ partition_id bigint NOT NULL
);
CREATE SEQUENCE ci_pipelines_id_seq
diff --git a/doc/.vale/gitlab/Substitutions.yml b/doc/.vale/gitlab/Substitutions.yml
index 0d49ac583dd..26caf353314 100644
--- a/doc/.vale/gitlab/Substitutions.yml
+++ b/doc/.vale/gitlab/Substitutions.yml
@@ -28,8 +28,10 @@ swap:
raketask: Rake task
raketasks: Rake tasks
rspec: RSpec
- self hosted: self-managed
- self-hosted: self-managed
+ GitLab self hosted: GitLab self-managed # https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html#gitlab-self-managed
+ GitLab self-hosted: GitLab self-managed # https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html#gitlab-self-managed
+ self hosted GitLab: GitLab self-managed # https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html#gitlab-self-managed
+ self-hosted GitLab: GitLab self-managed # https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html#gitlab-self-managed
styleguide: style guide
to login: to log in
can login: can log in
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 0f2f16ffc45..b21e82c9b54 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -24351,6 +24351,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectimportstatus"></a>`importStatus` | [`String`](#string) | Status of import background job of the project. |
| <a id="projectincidentmanagementtimelineeventtags"></a>`incidentManagementTimelineEventTags` | [`[TimelineEventTagType!]`](#timelineeventtagtype) | Timeline event tags for the project. |
| <a id="projectiscatalogresource"></a>`isCatalogResource` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 15.11. This feature is an Experiment. It can be changed or removed at any time. Indicates if a project is a catalog resource. |
+| <a id="projectisforked"></a>`isForked` | [`Boolean!`](#boolean) | Project is forked. |
| <a id="projectissuesaccesslevel"></a>`issuesAccessLevel` | [`ProjectFeatureAccess`](#projectfeatureaccess) | Access level required for issues access. |
| <a id="projectissuesenabled"></a>`issuesEnabled` | [`Boolean`](#boolean) | Indicates if Issues are enabled for the current user. |
| <a id="projectjiraimportstatus"></a>`jiraImportStatus` | [`String`](#string) | Status of Jira import background job of the project. |
diff --git a/doc/api/members.md b/doc/api/members.md
index 9d7aa85ba93..ead5b3d6be7 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -29,8 +29,7 @@ In GitLab 14.8 and earlier, projects in personal namespaces have an `access_leve
The `group_saml_identity` attribute is only visible to group owners for [SSO-enabled groups](../user/group/saml_sso/index.md).
-The `email` attribute is only visible to group owners for users provisioned by the group with [SCIM](../user/group/saml_sso/scim_setup.md).
-[Issue 391453](https://gitlab.com/gitlab-org/gitlab/-/issues/391453) proposes to change the criteria for access to the `email` attribute from provisioned users to [enterprise users](../user/enterprise_user/index.md).
+The `email` attribute is only visible to group owners for [enterprise users](../user/enterprise_user/index.md) of the group when an API request is sent to the group itself, or that group's subgroups or projects.
## List all members of a group or project
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index 5854704521b..eafb31241b0 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -252,7 +252,7 @@ Ideally, you should use [CI/CD variables](../variables/predefined_variables.md)
to replace those values at runtime when each review app is created:
- `data-project-id` is the project ID, which can be found by the `CI_PROJECT_ID`
- variable.
+ variable or on the [project overview page](../../user/project/working_with_projects.md#access-the-project-overview-page-by-using-the-project-id).
- `data-merge-request-id` is the merge request ID, which can be found by the
`CI_MERGE_REQUEST_IID` variable. `CI_MERGE_REQUEST_IID` is available only if
[`rules:if: $CI_PIPELINE_SOURCE == "merge_request_event`](../pipelines/merge_request_pipelines.md#use-rules-to-add-jobs)
diff --git a/doc/ci/triggers/index.md b/doc/ci/triggers/index.md
index 49ff0ee2356..b628159ad21 100644
--- a/doc/ci/triggers/index.md
+++ b/doc/ci/triggers/index.md
@@ -78,7 +78,7 @@ In each example, replace:
- `<token>` with your trigger token.
- `<ref_name>` with a branch or tag name, like `main`.
- `<project_id>` with your project ID, like `123456`. The project ID is displayed
- at the top of every project's landing page.
+ on the [project overview page](../../user/project/working_with_projects.md#access-the-project-overview-page-by-using-the-project-id).
### Use a CI/CD job
@@ -100,8 +100,8 @@ trigger_pipeline:
In this example:
-- `1234` is the project ID for `project-B`. The project ID is displayed at the top
- of every project's landing page.
+- `1234` is the project ID for `project-B`. The project ID is displayed on the
+ [project overview page](../../user/project/working_with_projects.md#access-the-project-overview-page-by-using-the-project-id).
- The [`rules`](../yaml/index.md#rules) cause the job to run every time a tag is added to `project-A`.
- `MY_TRIGGER_TOKEN` is a [masked CI/CD variables](../variables/index.md#mask-a-cicd-variable)
that contains the trigger token.
@@ -119,7 +119,7 @@ Replace:
- The URL with `https://gitlab.com` or the URL of your instance.
- `<project_id>` with your project ID, like `123456`. The project ID is displayed
- at the top of the project's landing page.
+ on the [project overview page](../../user/project/working_with_projects.md#access-the-project-overview-page-by-using-the-project-id).
- `<ref_name>` with a branch or tag name, like `main`. This value takes precedence over the `ref_name` in the webhook payload.
The payload's `ref` is the branch that fired the trigger in the source repository.
You must URL-encode the `ref_name` if it contains slashes.
diff --git a/doc/development/internal_analytics/internal_event_instrumentation/migration.md b/doc/development/internal_analytics/internal_event_instrumentation/migration.md
index 79ca45ed84c..7ed1adfb187 100644
--- a/doc/development/internal_analytics/internal_event_instrumentation/migration.md
+++ b/doc/development/internal_analytics/internal_event_instrumentation/migration.md
@@ -21,7 +21,7 @@ If you are already tracking events in Snowplow, you can also start collecting me
The event triggered by Internal Events has some special properties compared to previously tracking with Snowplow directly:
1. The `label`, `property` and `value` attributes are not used within Internal Events and are always empty.
-1. The `category` is automatically set to `InternalEventTracking`
+1. The `category` is automatically set to the location where the event happened. For Frontend events it is the page name and for Backend events it is a class name. If the page name or class name is not used, the default value of `"InternalEventTracking"` will be used.
Make sure that you are okay with this change before you migrate and dashboards are changed accordingly.
@@ -73,9 +73,11 @@ import { InternalEvents } from '~/tracking';
mixins: [InternalEvents.mixin()]
...
...
-this.trackEvent('action')
+this.trackEvent('action', 'category')
```
+If you are currently passing `category` and need to keep it, it can be passed as the second argument in the `trackEvent` method, as illustrated in the previous example. Nonetheless, it is strongly advised against using the `category` parameter for new events. This is because, by default, the category field is populated with information about where the event was triggered.
+
You can use [this MR](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123901/diffs) as an example. It migrates the `devops_adoption_app` component to use Internal Events Tracking.
If you are using `data-track-action` in the component, you have to change it to `data-event-tracking` to migrate to Internal Events Tracking.
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index e0a97568b5b..2a8a0766323 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -81,11 +81,10 @@ For more information about our plans for language support in SAST, see the [cate
| TypeScript | [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep/#sast-rules) | 13.10 |
<html>
-<small>Footnotes:
+ Footnotes:
<ol>
- <li>The SpotBugs-based analyzer supports [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/), and [SBT](https://www.scala-sbt.org/). It can also be used with variants like the [Gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html), [Grails](https://grails.org/), and the [Maven wrapper](https://github.com/takari/maven-wrapper). However, SpotBugs has [limitations](https://gitlab.com/gitlab-org/gitlab/-/issues/350801) when used against [Ant](https://ant.apache.org/)-based projects. We recommend using the Semgrep-based analyzer for Ant-based Java or Scala projects.</li>
+ <li>The SpotBugs-based analyzer supports <a href="https://gradle.org/">Gradle</a>, <a href="https://maven.apache.org/">Maven</a>, and <a href="https://www.scala-sbt.org/">SBT</a>. It can also be used with variants like the <a href="https://docs.gradle.org/current/userguide/gradle_wrapper.html">Gradle wrapper</a>, <a href="https://grails.org/">Grails</a>, and the <a href="https://github.com/takari/maven-wrapper">Maven wrapper</a>. However, SpotBugs has <a href="https://gitlab.com/gitlab-org/gitlab/-/issues/350801">limitations</a> when used against <a href="https://ant.apache.org/">Ant</a>-based projects. You should use the Semgrep-based analyzer for Ant-based Java or Scala projects.</li>
</ol>
-</small>
</html>
## End of supported analyzers
diff --git a/doc/user/enterprise_user/index.md b/doc/user/enterprise_user/index.md
index cc7e0e3b499..5a6ee56b775 100644
--- a/doc/user/enterprise_user/index.md
+++ b/doc/user/enterprise_user/index.md
@@ -203,10 +203,7 @@ A top-level group Owner can [set up verified domains to bypass confirmation emai
### Get users' email addresses through the API
A top-level group Owner can use the [group and project members API](../../api/members.md) to access
-users' information. For users provisioned by the group with [SCIM](../group/saml_sso/scim_setup.md),
-this information includes users' email addresses.
-
-[Issue 391453](https://gitlab.com/gitlab-org/gitlab/-/issues/391453) proposes to change the criteria for access to email addresses from provisioned users to enterprise users.
+users' information. For enterprise users of the group this information includes users' email addresses.
### Remove enterprise management features from an account
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index a59734d643d..9b2c6c37fd6 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -738,6 +738,12 @@ module API
namespace: namespace,
project: project
)
+ rescue Gitlab::InternalEvents::UnknownEventError => e
+ Gitlab::ErrorTracking.track_exception(e, event_name: event_name)
+
+ # We want to keep the error silent on production to keep the behavior
+ # consistent with StandardError rescue
+ unprocessable_entity!(e.message) if Gitlab.dev_or_test_env?
rescue StandardError => e
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, event_name: event_name)
end
diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb
index d625b2c0fe6..09bb336e19c 100644
--- a/lib/api/invitations.rb
+++ b/lib/api/invitations.rb
@@ -73,7 +73,7 @@ module API
end
desc 'Updates a group or project invitation.' do
- success Entities::Member
+ success Entities::Invitation
tags %w[invitations]
end
params do
@@ -103,7 +103,7 @@ module API
updated_member = result[:members].first
if result[:status] == :success
- present_members updated_member
+ present_member_invitations updated_member
else
render_validation_error!(updated_member)
end
diff --git a/lib/bulk_imports/common/pipelines/members_pipeline.rb b/lib/bulk_imports/common/pipelines/members_pipeline.rb
index 548b191dc25..90df8453d77 100644
--- a/lib/bulk_imports/common/pipelines/members_pipeline.rb
+++ b/lib/bulk_imports/common/pipelines/members_pipeline.rb
@@ -27,7 +27,9 @@ module BulkImports
return if user_membership && user_membership[:access_level] >= data[:access_level]
# Create new membership for any other access level
- portable.members.create!(data)
+ member = portable.members.new(data)
+ member.importing = true # avoid sending new member notification to the invited user
+ member.save!
end
private
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c1b1daf08e9..dad32def094 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2918,6 +2918,9 @@ msgstr ""
msgid "Add an impersonation token"
msgstr ""
+msgid "Add another branch"
+msgstr ""
+
msgid "Add another link"
msgstr ""
@@ -44054,6 +44057,9 @@ msgstr ""
msgid "SecurityOrchestration|Add new approver"
msgstr ""
+msgid "SecurityOrchestration|Add project full path after @ to following branches: %{branches}"
+msgstr ""
+
msgid "SecurityOrchestration|Add protected branches"
msgstr ""
@@ -44123,6 +44129,9 @@ msgstr ""
msgid "SecurityOrchestration|Choose approver type"
msgstr ""
+msgid "SecurityOrchestration|Choose exception branches"
+msgstr ""
+
msgid "SecurityOrchestration|Choose specific role"
msgstr ""
@@ -44192,6 +44201,9 @@ msgstr ""
msgid "SecurityOrchestration|Every time a pipeline runs for %{branches}%{branchExceptionsString}"
msgstr ""
+msgid "SecurityOrchestration|Exception branches"
+msgstr ""
+
msgid "SecurityOrchestration|Exceptions"
msgstr ""
@@ -44207,6 +44219,9 @@ msgstr ""
msgid "SecurityOrchestration|Failed to load images."
msgstr ""
+msgid "SecurityOrchestration|Fill in branch name with project name in the format of %{boldStart}branch-name@project-path,%{boldEnd} separate with `,`"
+msgstr ""
+
msgid "SecurityOrchestration|Following projects:"
msgstr ""
@@ -44276,6 +44291,9 @@ msgstr ""
msgid "SecurityOrchestration|No actions defined - policy will not run."
msgstr ""
+msgid "SecurityOrchestration|No branches yet"
+msgstr ""
+
msgid "SecurityOrchestration|No compliance frameworks"
msgstr ""
@@ -44320,6 +44338,9 @@ msgstr ""
msgid "SecurityOrchestration|Overwrite the current CI/CD code with the new file's content?"
msgstr ""
+msgid "SecurityOrchestration|Please remove duplicated values"
+msgstr ""
+
msgid "SecurityOrchestration|Policies"
msgstr ""
diff --git a/qa/Gemfile b/qa/Gemfile
index 72ce6cfe43c..ec053724906 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -2,7 +2,7 @@
source 'https://rubygems.org'
-gem 'gitlab-qa', '~> 13', '>= 13.1.0', require: 'gitlab/qa'
+gem 'gitlab-qa', '~> 14', require: 'gitlab/qa'
gem 'gitlab_quality-test_tooling', '~> 1.11.0', require: false
gem 'gitlab-utils', path: '../gems/gitlab-utils'
gem 'activesupport', '~> 7.0.8' # This should stay in sync with the root's Gemfile
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 126061e83cb..26dae330ef6 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -118,8 +118,8 @@ GEM
gitlab (4.19.0)
httparty (~> 0.20)
terminal-table (>= 1.5.1)
- gitlab-qa (13.1.0)
- activesupport (>= 6.1, < 7.1)
+ gitlab-qa (14.0.0)
+ activesupport (>= 6.1, < 7.2)
gitlab (~> 4.19)
http (~> 5.0)
nokogiri (~> 1.10)
@@ -354,7 +354,7 @@ DEPENDENCIES
faraday-retry (~> 2.2)
fog-core (= 2.1.0)
fog-google (~> 1.19)
- gitlab-qa (~> 13, >= 13.1.0)
+ gitlab-qa (~> 14)
gitlab-utils!
gitlab_quality-test_tooling (~> 1.11.0)
influxdb-client (~> 3.0)
@@ -380,4 +380,4 @@ DEPENDENCIES
zeitwerk (~> 2.6, >= 2.6.12)
BUNDLED WITH
- 2.5.4
+ 2.5.5
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index c5ad7bca824..1d8a44de7d9 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -402,7 +402,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
it 'displays the remember me checkbox' do
visit new_user_session_path
- expect(page).to have_field('remember_me_omniauth')
+ expect(page).to have_field('js-remember-me-omniauth')
end
context 'when remember me is not enabled' do
@@ -413,7 +413,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
it 'does not display the remember me checkbox' do
visit new_user_session_path
- expect(page).not_to have_field('remember_me_omniauth')
+ expect(page).not_to have_field('js-remember-me-omniauth')
end
end
diff --git a/spec/frontend/fixtures/static/oauth_remember_me.html b/spec/frontend/fixtures/static/oauth_remember_me.html
deleted file mode 100644
index d7519dd695f..00000000000
--- a/spec/frontend/fixtures/static/oauth_remember_me.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<div class="js-oauth-login">
- <input id="remember_me_omniauth" type="checkbox" />
-
- <form method="post" action="http://example.com/">
- <button class="twitter" type="submit">
- <span>Twitter</span>
- </button>
- </form>
-
- <form method="post" action="http://example.com/">
- <button class="github" type="submit">
- <span>GitHub</span>
- </button>
- </form>
-
- <form method="post" action="http://example.com/?redirect_fragment=L1">
- <button class="facebook" type="submit">
- <span>Facebook</span>
- </button>
- </form>
-</div>
diff --git a/spec/frontend/oauth_remember_me_spec.js b/spec/frontend/oauth_remember_me_spec.js
deleted file mode 100644
index 4fea216302f..00000000000
--- a/spec/frontend/oauth_remember_me_spec.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import $ from 'jquery';
-import htmlOauthRememberMe from 'test_fixtures_static/oauth_remember_me.html';
-import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me';
-
-describe('OAuthRememberMe', () => {
- const findFormAction = (selector) => {
- return $(`.js-oauth-login ${selector}`).parent('form').attr('action');
- };
-
- beforeEach(() => {
- setHTMLFixture(htmlOauthRememberMe);
-
- new OAuthRememberMe({ container: $('.js-oauth-login') }).bindEvents();
- });
-
- afterEach(() => {
- resetHTMLFixture();
- });
-
- it('adds and removes the "remember_me" query parameter from all OAuth login buttons', () => {
- $('.js-oauth-login #remember_me_omniauth').click();
-
- expect(findFormAction('.twitter')).toBe('http://example.com/?remember_me=1');
- expect(findFormAction('.github')).toBe('http://example.com/?remember_me=1');
- expect(findFormAction('.facebook')).toBe(
- 'http://example.com/?redirect_fragment=L1&remember_me=1',
- );
-
- $('.js-oauth-login #remember_me_omniauth').click();
-
- expect(findFormAction('.twitter')).toBe('http://example.com/');
- expect(findFormAction('.github')).toBe('http://example.com/');
- expect(findFormAction('.facebook')).toBe('http://example.com/?redirect_fragment=L1');
- });
-});
diff --git a/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js b/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js
index 7607381a981..60cf5dc65a2 100644
--- a/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js
+++ b/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js
@@ -1,13 +1,12 @@
-import $ from 'jquery';
import htmlSessionsNew from 'test_fixtures/sessions/new.html';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment';
+import {
+ appendUrlFragment,
+ appendRedirectQuery,
+ toggleRememberMeQuery,
+} from '~/pages/sessions/new/preserve_url_fragment';
describe('preserve_url_fragment', () => {
- const findFormAction = (selector) => {
- return $(`.js-oauth-login ${selector}`).parent('form').attr('action');
- };
-
beforeEach(() => {
setHTMLFixture(htmlSessionsNew);
});
@@ -16,41 +15,74 @@ describe('preserve_url_fragment', () => {
resetHTMLFixture();
});
- it('adds the url fragment to the login form actions', () => {
- preserveUrlFragment('#L65');
+ describe('non-OAuth login forms', () => {
+ describe('appendUrlFragment', () => {
+ const findFormAction = () => document.querySelector('.js-non-oauth-login form').action;
- expect($('#new_user').attr('action')).toBe('http://test.host/users/sign_in#L65');
- });
+ it('adds the url fragment to the login form actions', () => {
+ appendUrlFragment('#L65');
- it('does not add an empty url fragment to the login form actions', () => {
- preserveUrlFragment();
+ expect(findFormAction()).toBe('http://test.host/users/sign_in#L65');
+ });
- expect($('#new_user').attr('action')).toBe('http://test.host/users/sign_in');
+ it('does not add an empty url fragment to the login form actions', () => {
+ appendUrlFragment();
+
+ expect(findFormAction()).toBe('http://test.host/users/sign_in');
+ });
+ });
});
- it('does not add an empty query parameter to OmniAuth login buttons', () => {
- preserveUrlFragment();
+ describe('OAuth login forms', () => {
+ const findFormAction = (selector) =>
+ document.querySelector(`.js-oauth-login #oauth-login-${selector}`).parentElement.action;
- expect(findFormAction('#oauth-login-auth0')).toBe('http://test.host/users/auth/auth0');
- });
+ describe('appendRedirectQuery', () => {
+ it('does not add an empty query parameter to the login form actions', () => {
+ appendRedirectQuery();
+
+ expect(findFormAction('auth0')).toBe('http://test.host/users/auth/auth0');
+ });
+
+ describe('adds "redirect_fragment" query parameter to the login form actions', () => {
+ it('when "remember_me" is not present', () => {
+ appendRedirectQuery('#L65');
- describe('adds "redirect_fragment" query parameter to OmniAuth login buttons', () => {
- it('when "remember_me" is not present', () => {
- preserveUrlFragment('#L65');
+ expect(findFormAction('auth0')).toBe(
+ 'http://test.host/users/auth/auth0?redirect_fragment=L65',
+ );
+ });
- expect(findFormAction('#oauth-login-auth0')).toBe(
- 'http://test.host/users/auth/auth0?redirect_fragment=L65',
- );
+ it('when "remember_me" is present', () => {
+ document
+ .querySelectorAll('form')
+ .forEach((form) => form.setAttribute('action', `${form.action}?remember_me=1`));
+
+ appendRedirectQuery('#L65');
+
+ expect(findFormAction('auth0')).toBe(
+ 'http://test.host/users/auth/auth0?remember_me=1&redirect_fragment=L65',
+ );
+ });
+ });
});
- it('when "remember-me" is present', () => {
- $('.js-oauth-login form').attr('action', (i, href) => `${href}?remember_me=1`);
+ describe('toggleRememberMeQuery', () => {
+ const rememberMe = () => document.querySelector('#js-remember-me-omniauth');
+
+ it('toggles "remember_me" query parameter', () => {
+ toggleRememberMeQuery();
+
+ expect(findFormAction('auth0')).toBe('http://test.host/users/auth/auth0');
+
+ rememberMe().click();
+
+ expect(findFormAction('auth0')).toBe('http://test.host/users/auth/auth0?remember_me=1');
- preserveUrlFragment('#L65');
+ rememberMe().click();
- expect(findFormAction('#oauth-login-auth0')).toBe(
- 'http://test.host/users/auth/auth0?remember_me=1&redirect_fragment=L65',
- );
+ expect(findFormAction('auth0')).toBe('http://test.host/users/auth/auth0');
+ });
});
});
});
diff --git a/spec/frontend/repository/components/commit_info_spec.js b/spec/frontend/repository/components/commit_info_spec.js
index 4e570346d97..f868bc0623e 100644
--- a/spec/frontend/repository/components/commit_info_spec.js
+++ b/spec/frontend/repository/components/commit_info_spec.js
@@ -16,14 +16,15 @@ const commit = {
const findTextExpander = () => wrapper.findComponent(GlButton);
const findUserLink = () => wrapper.findByText(commit.author.name);
+const findCommitterWrapper = () => wrapper.findByTestId('committer');
const findUserAvatarLink = () => wrapper.findComponent(UserAvatarLink);
const findAuthorName = () => wrapper.findByText(`${commit.authorName} authored`);
const findCommitRowDescription = () => wrapper.find('pre');
const findTitleHtml = () => wrapper.findByText(commit.titleHtml);
-const createComponent = async ({ commitMock = {}, prevBlameLink } = {}) => {
+const createComponent = async ({ commitMock = {}, prevBlameLink, span = 3 } = {}) => {
wrapper = shallowMountExtended(CommitInfo, {
- propsData: { commit: { ...commit, ...commitMock }, prevBlameLink },
+ propsData: { commit: { ...commit, ...commitMock }, prevBlameLink, span },
});
await nextTick();
@@ -46,6 +47,22 @@ describe('Repository last commit component', () => {
expect(findAuthorName().exists()).toBe(true);
});
+ it('truncates author name when commit spans less than 3 lines', () => {
+ createComponent({ span: 2 });
+
+ expect(findCommitterWrapper().classes()).toEqual([
+ 'committer',
+ 'gl-flex-basis-full',
+ 'gl-display-inline-flex',
+ ]);
+ expect(findUserLink().classes()).toEqual([
+ 'commit-author-link',
+ 'js-user-link',
+ 'gl-display-inline-block',
+ 'gl-text-truncate',
+ ]);
+ });
+
it('does not render description expander when description is null', () => {
createComponent();
diff --git a/spec/frontend/tracking/internal_events_spec.js b/spec/frontend/tracking/internal_events_spec.js
index 295b08f4b1c..194d33ae6b9 100644
--- a/spec/frontend/tracking/internal_events_spec.js
+++ b/spec/frontend/tracking/internal_events_spec.js
@@ -4,6 +4,7 @@ import InternalEvents from '~/tracking/internal_events';
import { LOAD_INTERNAL_EVENTS_SELECTOR } from '~/tracking/constants';
import * as utils from '~/tracking/utils';
import { Tracker } from '~/tracking/tracker';
+import Tracking from '~/tracking';
jest.mock('~/api', () => ({
trackInternalEvent: jest.fn(),
@@ -20,13 +21,23 @@ const event = 'TestEvent';
describe('InternalEvents', () => {
describe('trackEvent', () => {
+ const category = 'TestCategory';
+
it('trackEvent calls API.trackInternalEvent with correct arguments', () => {
- InternalEvents.trackEvent(event);
+ InternalEvents.trackEvent(event, category);
expect(API.trackInternalEvent).toHaveBeenCalledTimes(1);
expect(API.trackInternalEvent).toHaveBeenCalledWith(event);
});
+ it('trackEvent calls Tracking.event with correct arguments including category', () => {
+ jest.spyOn(Tracking, 'event').mockImplementation(() => {});
+
+ InternalEvents.trackEvent(event, category);
+
+ expect(Tracking.event).toHaveBeenCalledWith(category, event, expect.any(Object));
+ });
+
it('trackEvent calls trackBrowserSDK with correct arguments', () => {
jest.spyOn(InternalEvents, 'trackBrowserSDK').mockImplementation(() => {});
@@ -63,7 +74,7 @@ describe('InternalEvents', () => {
await wrapper.findByTestId('button').trigger('click');
expect(trackEventSpy).toHaveBeenCalledTimes(1);
- expect(trackEventSpy).toHaveBeenCalledWith(event);
+ expect(trackEventSpy).toHaveBeenCalledWith(event, undefined);
});
});
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index f6e178f5b28..96201f0827f 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe GitlabSchema.types['Project'], feature_category: :groups_and_proj
timelog_categories fork_targets branch_rules ci_config_variables pipeline_schedules languages
incident_management_timeline_event_tags visible_forks inherited_ci_variables autocomplete_users
ci_cd_settings detailed_import_status value_streams ml_models
- allows_multiple_merge_request_assignees allows_multiple_merge_request_reviewers
+ allows_multiple_merge_request_assignees allows_multiple_merge_request_reviewers is_forked
]
expect(described_class).to include_graphql_fields(*expected_fields)
@@ -771,6 +771,57 @@ RSpec.describe GitlabSchema.types['Project'], feature_category: :groups_and_proj
end
end
+ describe 'is_forked' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:unforked_project) { create(:project, :public) }
+ let!(:forked_project) { fork_project(unforked_project) }
+ let(:project) { nil }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ isForked
+ }
+ }
+ )
+ end
+
+ let(:response) { GitlabSchema.execute(query).as_json }
+
+ subject(:is_forked) { response.dig('data', 'project', 'isForked') }
+
+ context 'when project has a fork network' do
+ context 'when fork is itself' do
+ let(:project) { unforked_project }
+
+ it { is_expected.to be false }
+ end
+
+ context 'when fork is not itself' do
+ let(:project) { forked_project }
+
+ it { is_expected.to be true }
+
+ it 'avoids N+1 queries' do
+ query_count = ActiveRecord::QueryRecorder.new { response }
+
+ expect(query_count).not_to exceed_query_limit(8)
+ end
+ end
+ end
+
+ context 'when project does not have a fork network' do
+ let(:project) { unforked_project }
+
+ before do
+ allow(project).to receive(:fork_network).and_return(nil)
+ end
+
+ it { is_expected.to be false }
+ end
+ end
+
describe 'branch_rules' do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public) }
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index d1dee70e34d..6a2449cbcdb 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -860,13 +860,30 @@ RSpec.describe API::Helpers, feature_category: :shared do
)
end
- it 'logs an exception for unknown event' do
+ it 'tracks an exception and renders 422 for unknown event', :aggregate_failures do
expect(Gitlab::InternalEvents).to receive(:track_event).and_raise(Gitlab::InternalEvents::UnknownEventError, "Unknown event: #{unknown_event}")
- expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
+
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
.with(
instance_of(Gitlab::InternalEvents::UnknownEventError),
event_name: unknown_event
)
+ expect(helper).to receive(:unprocessable_entity!).with("Unknown event: #{unknown_event}")
+
+ helper.track_event(unknown_event,
+ user: user,
+ namespace_id: namespace.id,
+ project_id: project.id
+ )
+ end
+
+ it 'logs an exception for tracking errors' do
+ expect(Gitlab::InternalEvents).to receive(:track_event).and_raise(ArgumentError, "Error message")
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
+ .with(
+ instance_of(ArgumentError),
+ event_name: unknown_event
+ )
helper.track_event(unknown_event,
user: user,
diff --git a/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb b/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb
index 65d4e8b4978..5fc0c8fa239 100644
--- a/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb
@@ -87,6 +87,12 @@ RSpec.describe BulkImports::Common::Pipelines::MembersPipeline, feature_category
expect(member.expires_at).to eq(nil)
end
+ it 'does not send new member notification' do
+ expect(NotificationService).not_to receive(:new)
+
+ subject.load(context, member_data)
+ end
+
context 'when user_id is current user id' do
it 'does not create new membership' do
data = { user_id: user.id }
diff --git a/spec/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status_spec.rb b/spec/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status_spec.rb
index fad10aba882..dc62a520d07 100644
--- a/spec/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status_spec.rb
+++ b/spec/lib/gitlab/background_migration/update_ci_pipeline_artifacts_unknown_locked_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::UpdateCiPipelineArtifactsUnknownLockedStatus do
+RSpec.describe Gitlab::BackgroundMigration::UpdateCiPipelineArtifactsUnknownLockedStatus, feature_category: :build_artifacts do
describe '#perform' do
let(:batch_table) { :ci_pipeline_artifacts }
let(:batch_column) { :id }
@@ -30,11 +30,11 @@ RSpec.describe Gitlab::BackgroundMigration::UpdateCiPipelineArtifactsUnknownLock
let(:locked_pipeline) { pipelines.create!(locked: locked, partition_id: 100) }
# rubocop:disable Layout/LineLength
- let!(:locked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: locked_pipeline.id, size: 1024, file_type: 0, file_format: 'gzip', file: 'a.gz', locked: unknown) }
- let!(:unlocked_artifact_1) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 2048, file_type: 1, file_format: 'raw', file: 'b', locked: unknown) }
- let!(:unlocked_artifact_2) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 4096, file_type: 2, file_format: 'gzip', file: 'c.gz', locked: unknown) }
- let!(:already_unlocked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 8192, file_type: 3, file_format: 'raw', file: 'd', locked: unlocked) }
- let!(:already_locked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: locked_pipeline.id, size: 8192, file_type: 3, file_format: 'raw', file: 'd', locked: locked) }
+ let!(:locked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: locked_pipeline.id, size: 1024, file_type: 0, file_format: 'gzip', file: 'a.gz', locked: unknown, partition_id: 100) }
+ let!(:unlocked_artifact_1) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 2048, file_type: 1, file_format: 'raw', file: 'b', locked: unknown, partition_id: 100) }
+ let!(:unlocked_artifact_2) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 4096, file_type: 2, file_format: 'gzip', file: 'c.gz', locked: unknown, partition_id: 100) }
+ let!(:already_unlocked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: unlocked_pipeline.id, size: 8192, file_type: 3, file_format: 'raw', file: 'd', locked: unlocked, partition_id: 100) }
+ let!(:already_locked_artifact) { pipeline_artifacts.create!(project_id: project.id, pipeline_id: locked_pipeline.id, size: 8192, file_type: 3, file_format: 'raw', file: 'd', locked: locked, partition_id: 100) }
# rubocop:enable Layout/LineLength
subject do
diff --git a/spec/models/fork_network_member_spec.rb b/spec/models/fork_network_member_spec.rb
index b34eb7964ca..edefddfc9d7 100644
--- a/spec/models/fork_network_member_spec.rb
+++ b/spec/models/fork_network_member_spec.rb
@@ -25,4 +25,30 @@ RSpec.describe ForkNetworkMember do
expect(ForkNetwork.count).to eq(1)
end
end
+
+ describe '#by_projects' do
+ let_it_be(:fork_network_member_1) { create(:fork_network_member) }
+ let_it_be(:fork_network_member_2) { create(:fork_network_member) }
+
+ it 'returns fork network members by project ids' do
+ expect(
+ described_class.by_projects(
+ [fork_network_member_1.project_id, fork_network_member_2.project_id]
+ )
+ ).to match_array([fork_network_member_1, fork_network_member_2])
+ end
+ end
+
+ describe '#with_fork_network' do
+ let_it_be(:fork_network_member_1) { create(:fork_network_member) }
+ let_it_be(:fork_network_member_2) { create(:fork_network_member) }
+
+ it 'avoids N+1 queries' do
+ query_count = ActiveRecord::QueryRecorder.new do
+ described_class.all.with_fork_network.find_each(&:fork_network)
+ end
+
+ expect(query_count).not_to exceed_query_limit(1)
+ end
+ end
end
diff --git a/spec/requests/groups/autocomplete_sources_spec.rb b/spec/requests/groups/autocomplete_sources_spec.rb
index 02fb04a4af8..5d190074534 100644
--- a/spec/requests/groups/autocomplete_sources_spec.rb
+++ b/spec/requests/groups/autocomplete_sources_spec.rb
@@ -14,6 +14,42 @@ RSpec.describe 'groups autocomplete', feature_category: :groups_and_projects do
sign_in(user)
end
+ describe '#members' do
+ context 'when type is WorkItem' do
+ let(:type) { 'Workitem' }
+
+ it 'returns the correct response', :aggregate_failures do
+ work_item = create(:work_item, :group_level, namespace: group, author: user)
+
+ get members_group_autocomplete_sources_path(group, type_id: work_item.iid, type: type)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response).to contain_exactly(
+ hash_including('type' => 'User', 'username' => user.username),
+ hash_including('type' => 'Group', 'username' => group.full_path)
+ )
+ end
+ end
+
+ context 'when type is Issue' do
+ let(:type) { 'Issue' }
+
+ it 'returns the correct response', :aggregate_failures do
+ issue = create(:issue, :group_level, namespace: group, author: user)
+
+ get members_group_autocomplete_sources_path(group, type_id: issue.iid, type: type)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response).to contain_exactly(
+ hash_including('type' => 'User', 'username' => user.username),
+ hash_including('type' => 'Group', 'username' => group.full_path)
+ )
+ end
+ end
+ end
+
describe '#issues' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/services/quick_actions/target_service_spec.rb b/spec/services/quick_actions/target_service_spec.rb
index 5f4e92cf955..311f2680379 100644
--- a/spec/services/quick_actions/target_service_spec.rb
+++ b/spec/services/quick_actions/target_service_spec.rb
@@ -3,13 +3,11 @@
require 'spec_helper'
RSpec.describe QuickActions::TargetService, feature_category: :team_planning do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:service) { described_class.new(project, user) }
-
- before do
- project.add_maintainer(user)
- end
+ let_it_be(:group) { create(:group) }
+ let_it_be_with_reload(:project) { create(:project, group: group) }
+ let_it_be(:user) { create(:user).tap { |u| project.add_maintainer(u) } }
+ let(:container) { project }
+ let(:service) { described_class.new(container: container, current_user: user) }
describe '#execute' do
shared_examples 'no target' do |type_iid:|
@@ -32,7 +30,7 @@ RSpec.describe QuickActions::TargetService, feature_category: :team_planning do
it 'builds a new target' do
target = service.execute(type, type_iid)
- expect(target.project).to eq(project)
+ expect(target.resource_parent).to eq(container)
expect(target).to be_new_record
end
end
@@ -45,6 +43,15 @@ RSpec.describe QuickActions::TargetService, feature_category: :team_planning do
it_behaves_like 'find target'
it_behaves_like 'build target', type_iid: nil
it_behaves_like 'build target', type_iid: -1
+
+ context 'when issue belongs to a group' do
+ let(:container) { group }
+ let(:target) { create(:issue, :group_level, namespace: group) }
+
+ it_behaves_like 'find target'
+ it_behaves_like 'build target', type_iid: nil
+ it_behaves_like 'build target', type_iid: -1
+ end
end
context 'for work item' do
@@ -53,6 +60,13 @@ RSpec.describe QuickActions::TargetService, feature_category: :team_planning do
let(:type) { 'WorkItem' }
it_behaves_like 'find target'
+
+ context 'when work item belongs to a group' do
+ let(:container) { group }
+ let(:target) { create(:work_item, :group_level, namespace: group) }
+
+ it_behaves_like 'find target'
+ end
end
context 'for merge request' do
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index cc45cb1292d..7752488ab44 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -112,7 +112,7 @@ module LoginHelpers
visit new_user_session_path
expect(page).to have_css('.js-oauth-login')
- check 'remember_me_omniauth' if remember_me
+ check 'js-remember-me-omniauth' if remember_me
click_button "oauth-login-#{provider}"
end