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>2023-06-02 21:09:02 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-02 21:09:02 +0300
commitf3c61892ecbcad3bfe57f06f197ae9e8996970db (patch)
tree3333bd204168492ca70378a8c22928bee5998b72
parentf5f72042cbd7cf07f6a621de495f053d576fa752 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/CODEOWNERS2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock8
-rw-r--r--app/assets/javascripts/branches/components/branch_more_actions.vue114
-rw-r--r--app/assets/javascripts/branches/components/delete_branch_button.vue85
-rw-r--r--app/assets/javascripts/branches/components/delete_merged_branches.vue10
-rw-r--r--app/assets/javascripts/branches/components/sort_dropdown.vue2
-rw-r--r--app/assets/javascripts/branches/init_branch_more_actions.js (renamed from app/assets/javascripts/branches/init_delete_branch_button.js)16
-rw-r--r--app/assets/javascripts/pages/projects/branches/index/index.js6
-rw-r--r--app/controllers/projects/branches_controller.rb10
-rw-r--r--app/graphql/resolvers/releases_resolver.rb25
-rw-r--r--app/graphql/types/ci/catalog/resource_type.rb84
-rw-r--r--app/helpers/branches_helper.rb21
-rw-r--r--app/helpers/users/callouts_helper.rb6
-rw-r--r--app/models/merge_request.rb7
-rw-r--r--app/views/projects/branches/_branch.html.haml109
-rw-r--r--app/views/projects/branches/_commit.html.haml6
-rw-r--r--app/views/projects/branches/_delete_branch_modal_button.html.haml18
-rw-r--r--app/views/projects/buttons/_download.html.haml3
-rw-r--r--config/feature_categories.yml2
-rw-r--r--config/feature_flags/development/fill_in_mr_template.yml8
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--doc/administration/integration/plantuml.md186
-rw-r--r--doc/api/graphql/reference/index.md24
-rw-r--r--doc/ci/pipelines/cicd_minutes.md80
-rw-r--r--doc/ci/pipelines/img/group_cicd_minutes_quota.pngbin21010 -> 0 bytes
-rw-r--r--doc/ci/variables/predefined_variables.md2
-rw-r--r--locale/gitlab.pot42
-rw-r--r--qa/qa/page/project/branches/show.rb2
-rw-r--r--spec/controllers/every_controller_spec.rb2
-rw-r--r--spec/features/projects/branches/user_deletes_branch_spec.rb3
-rw-r--r--spec/features/projects/branches/user_views_branches_spec.rb43
-rw-r--r--spec/features/projects/branches_spec.rb20
-rw-r--r--spec/features/projects/environments/environment_spec.rb5
-rw-r--r--spec/features/projects/merge_request_button_spec.rb11
-rw-r--r--spec/features/protected_branches_spec.rb6
-rw-r--r--spec/frontend/branches/components/__snapshots__/delete_merged_branches_spec.js.snap23
-rw-r--r--spec/frontend/branches/components/branch_more_actions_spec.js70
-rw-r--r--spec/frontend/branches/components/delete_branch_button_spec.js92
-rw-r--r--spec/graphql/resolvers/releases_resolver_spec.rb6
-rw-r--r--spec/graphql/types/ci/catalog/resource_type_spec.rb24
-rw-r--r--spec/helpers/branches_helper_spec.rb51
-rw-r--r--spec/lib/api/every_api_endpoint_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb20
-rw-r--r--spec/support/shared_examples/requests/api/graphql/releases_and_group_releases_shared_examples.rb15
-rw-r--r--spec/views/projects/branches/index.html.haml_spec.rb1
48 files changed, 729 insertions, 553 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index acacd7c6c04..5b15ea2c4d8 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -257,7 +257,7 @@ Dangerfile
/ee/app/workers/merge_requests/ @garyh @patrickbajao @marc_shaw @kerrizor @dskim_gitlab
/ee/app/workers/merge_request_reset_approvals_worker.rb @garyh @patrickbajao @marc_shaw @kerrizor @dskim_gitlab
-^[Merge Requests frontend] @viktomas @jboyson @iamphill @thomasrandolph
+^[Merge Requests frontend] @slashmanov @iamphill @thomasrandolph
/app/assets/javascripts/diffs
/app/assets/javascripts/batch_comments/
/app/assets/javascripts/notes
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 729fda5e8c0..1528d1d6716 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-81496efc0d26dba7799d1392c80b06bce943cc29
+01c879aca2c628e691e28d791f24185a22db55f2
diff --git a/Gemfile b/Gemfile
index fcc4751d576..5780e43fc6a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -195,7 +195,7 @@ gem 'asciidoctor-plantuml', '~> 0.0.16'
gem 'asciidoctor-kroki', '~> 0.8.0', require: false
gem 'rouge', '~> 4.1.2'
gem 'truncato', '~> 0.7.12'
-gem 'nokogiri', '~> 1.15'
+gem 'nokogiri', '~> 1.15', '>= 1.15.2'
# Calendar rendering
gem 'icalendar'
@@ -290,7 +290,7 @@ gem 'sanitize', '~> 6.0'
gem 'babosa', '~> 2.0'
# Sanitizes SVG input
-gem 'loofah', '~> 2.21.1'
+gem 'loofah', '~> 2.21.3'
# Working with license
# Detects the open source license the repository includes
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 605b11430c4..7776eff7487 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -342,7 +342,7 @@
{"name":"locale","version":"2.1.3","platform":"ruby","checksum":"b6ddee011e157817cb98e521b3ce7cb626424d5882f1e844aafdee3e8b212725"},
{"name":"lockbox","version":"1.1.1","platform":"ruby","checksum":"0af16b14c54f791c148615a0115387b51903d868c7fe622f49606c97071c2ac0"},
{"name":"lograge","version":"0.11.2","platform":"ruby","checksum":"4cbd1554b86f545d795eff15a0c24fd25057d2ac4e1caa5fc186168b3da932ef"},
-{"name":"loofah","version":"2.21.1","platform":"ruby","checksum":"f8e1584c56195e7b6139d53c50d6d9cf1adbc5997a7f4e60a3e23095c4900765"},
+{"name":"loofah","version":"2.21.3","platform":"ruby","checksum":"43d21a8bb96c380199a8f66e0298649eaa7362fcd32f3a6114f39775e524e4dc"},
{"name":"lookbook","version":"2.0.1","platform":"ruby","checksum":"0f14729c8c992810de0792a0be865a5792e5765fbaea5950cce74c6e5c73fc4a"},
{"name":"lru_redux","version":"1.1.0","platform":"ruby","checksum":"ee71d0ccab164c51de146c27b480a68b3631d5b4297b8ffe8eda1c72de87affb"},
{"name":"lumberjack","version":"1.2.7","platform":"ruby","checksum":"a5c6aae6b4234f1420dbcd80b23e3bca0817bd239440dde097ebe3fa63c63b1f"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 95cb3700f71..3ebb7134a43 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -913,9 +913,9 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
- loofah (2.21.1)
+ loofah (2.21.3)
crass (~> 1.0.2)
- nokogiri (>= 1.5.9)
+ nokogiri (>= 1.12.0)
lookbook (2.0.1)
activemodel
css_parser
@@ -1814,7 +1814,7 @@ DEPENDENCIES
listen (~> 3.7)
lockbox (~> 1.1.1)
lograge (~> 0.5)
- loofah (~> 2.21.1)
+ loofah (~> 2.21.3)
lookbook (~> 2.0, >= 2.0.1)
lru_redux
mail (= 2.8.1)
@@ -1830,7 +1830,7 @@ DEPENDENCIES
net-ldap (~> 0.18.0)
net-ntp
net-protocol (~> 0.1.3)
- nokogiri (~> 1.15)
+ nokogiri (~> 1.15, >= 1.15.2)
oauth2 (~> 2.0)
octokit (~> 4.15)
ohai (~> 17.9)
diff --git a/app/assets/javascripts/branches/components/branch_more_actions.vue b/app/assets/javascripts/branches/components/branch_more_actions.vue
new file mode 100644
index 00000000000..c646dab2760
--- /dev/null
+++ b/app/assets/javascripts/branches/components/branch_more_actions.vue
@@ -0,0 +1,114 @@
+<script>
+import { GlDisclosureDropdown, GlTooltipDirective } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import eventHub from '../event_hub';
+
+export default {
+ name: 'BranchMoreActions',
+ components: { GlDisclosureDropdown },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ branchName: {
+ type: String,
+ required: true,
+ },
+ defaultBranchName: {
+ type: String,
+ required: true,
+ },
+ canDeleteBranch: {
+ type: Boolean,
+ required: true,
+ },
+ isProtectedBranch: {
+ type: Boolean,
+ required: true,
+ },
+ merged: {
+ type: Boolean,
+ required: true,
+ },
+ comparePath: {
+ type: String,
+ required: true,
+ },
+ deletePath: {
+ type: String,
+ required: true,
+ },
+ },
+ i18n: {
+ toggleText: __('More actions'),
+ compare: s__('Branches|Compare'),
+ deleteBranch: s__('Branches|Delete branch'),
+ deleteProtectedBranch: s__('Branches|Delete protected branch'),
+ },
+ computed: {
+ deleteBranchText() {
+ return this.isProtectedBranch
+ ? this.$options.i18n.deleteProtectedBranch
+ : this.$options.i18n.deleteBranch;
+ },
+ dropdownItems() {
+ const items = [
+ {
+ text: this.$options.i18n.compare,
+ href: this.comparePath,
+ extraAttrs: {
+ class: 'js-onboarding-compare-branches',
+ 'data-testid': 'compare-branch-button',
+ 'data-method': 'post',
+ },
+ },
+ ];
+
+ if (this.canDeleteBranch) {
+ items.push({
+ text: this.deleteBranchText,
+ action: () => {
+ this.openModal();
+ },
+ extraAttrs: {
+ class: 'js-delete-branch-button gl-text-red-500!',
+ 'aria-label': this.deleteBranchText,
+ 'data-testid': 'delete-branch-button',
+ 'data-qa-selector': 'delete_branch_button',
+ },
+ });
+ }
+
+ return items;
+ },
+ },
+ methods: {
+ openModal() {
+ eventHub.$emit('openModal', {
+ branchName: this.branchName,
+ defaultBranchName: this.defaultBranchName,
+ deletePath: this.deletePath,
+ isProtectedBranch: this.isProtectedBranch,
+ merged: this.merged,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-disclosure-dropdown
+ v-gl-tooltip.hover.top="{
+ title: $options.i18n.toggleText,
+ boundary: 'viewport',
+ }"
+ :items="dropdownItems"
+ :toggle-text="$options.i18n.toggleText"
+ icon="ellipsis_v"
+ category="tertiary"
+ placement="right"
+ data-testid="branch-more-actions"
+ text-sr-only
+ no-caret
+ />
+</template>
diff --git a/app/assets/javascripts/branches/components/delete_branch_button.vue b/app/assets/javascripts/branches/components/delete_branch_button.vue
deleted file mode 100644
index 6a6d4d48c52..00000000000
--- a/app/assets/javascripts/branches/components/delete_branch_button.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<script>
-import { GlButton, GlTooltipDirective } from '@gitlab/ui';
-import { s__ } from '~/locale';
-import eventHub from '../event_hub';
-
-export default {
- name: 'DeleteBranchButton',
- components: { GlButton },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- props: {
- branchName: {
- type: String,
- required: false,
- default: '',
- },
- defaultBranchName: {
- type: String,
- required: false,
- default: '',
- },
- deletePath: {
- type: String,
- required: false,
- default: '',
- },
- tooltip: {
- type: String,
- required: false,
- default: s__('Branches|Delete branch'),
- },
- disabled: {
- type: Boolean,
- required: false,
- default: false,
- },
- isProtectedBranch: {
- type: Boolean,
- required: false,
- default: false,
- },
- merged: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- computed: {
- title() {
- if (this.isProtectedBranch && this.disabled) {
- return s__('Branches|Only a project maintainer or owner can delete a protected branch');
- } else if (this.isProtectedBranch) {
- return s__('Branches|Delete protected branch');
- }
- return this.tooltip;
- },
- },
- methods: {
- openModal() {
- eventHub.$emit('openModal', {
- branchName: this.branchName,
- defaultBranchName: this.defaultBranchName,
- deletePath: this.deletePath,
- isProtectedBranch: this.isProtectedBranch,
- merged: this.merged,
- });
- },
- },
-};
-</script>
-
-<template>
- <gl-button
- v-gl-tooltip.hover
- icon="remove"
- class="js-delete-branch-button"
- data-qa-selector="delete_branch_button"
- :disabled="disabled"
- variant="default"
- :title="title"
- :aria-label="title"
- @click="openModal"
- />
-</template>
diff --git a/app/assets/javascripts/branches/components/delete_merged_branches.vue b/app/assets/javascripts/branches/components/delete_merged_branches.vue
index d9d8f1d742d..117c15be907 100644
--- a/app/assets/javascripts/branches/components/delete_merged_branches.vue
+++ b/app/assets/javascripts/branches/components/delete_merged_branches.vue
@@ -103,8 +103,18 @@ export default {
no-caret
placement="right"
data-qa-selector="delete_merged_branches_dropdown_button"
+ class="gl-display-none gl-md-display-block!"
:items="dropdownItems"
/>
+ <gl-button
+ data-qa-selector="delete_merged_branches_button"
+ category="secondary"
+ variant="danger"
+ class="gl-display-block gl-md-display-none!"
+ @click="openModal"
+ >
+ {{ $options.i18n.deleteButtonText }}
+ </gl-button>
<gl-modal
ref="modal"
size="sm"
diff --git a/app/assets/javascripts/branches/components/sort_dropdown.vue b/app/assets/javascripts/branches/components/sort_dropdown.vue
index 99c82fc9a5a..4866d506988 100644
--- a/app/assets/javascripts/branches/components/sort_dropdown.vue
+++ b/app/assets/javascripts/branches/components/sort_dropdown.vue
@@ -52,7 +52,7 @@ export default {
};
</script>
<template>
- <div class="gl-display-flex">
+ <div class="gl-display-flex gl-flex-grow-1">
<gl-search-box-by-click
v-model="searchTerm"
:placeholder="$options.i18n.searchPlaceholder"
diff --git a/app/assets/javascripts/branches/init_delete_branch_button.js b/app/assets/javascripts/branches/init_branch_more_actions.js
index 43df5d993a4..62f3c314c43 100644
--- a/app/assets/javascripts/branches/init_delete_branch_button.js
+++ b/app/assets/javascripts/branches/init_branch_more_actions.js
@@ -1,8 +1,8 @@
import Vue from 'vue';
-import DeleteBranchButton from '~/branches/components/delete_branch_button.vue';
+import DeleteBranchButton from '~/branches/components/branch_more_actions.vue';
import { parseBoolean } from '~/lib/utils/common_utils';
-export default function initDeleteBranchButton(el) {
+export default function initBranchMoreActions(el) {
if (!el) {
return false;
}
@@ -10,11 +10,11 @@ export default function initDeleteBranchButton(el) {
const {
branchName,
defaultBranchName,
- deletePath,
- tooltip,
- disabled,
+ canDeleteBranch,
isProtectedBranch,
merged,
+ comparePath,
+ deletePath,
} = el.dataset;
return new Vue({
@@ -24,11 +24,11 @@ export default function initDeleteBranchButton(el) {
props: {
branchName,
defaultBranchName,
- deletePath,
- tooltip,
- disabled: parseBoolean(disabled),
+ canDeleteBranch: parseBoolean(canDeleteBranch),
isProtectedBranch: parseBoolean(isProtectedBranch),
merged: parseBoolean(merged),
+ comparePath,
+ deletePath,
},
}),
});
diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js
index ac5e0b28dd1..f5cd03ac48d 100644
--- a/app/assets/javascripts/pages/projects/branches/index/index.js
+++ b/app/assets/javascripts/pages/projects/branches/index/index.js
@@ -1,9 +1,9 @@
import initDeprecatedRemoveRowBehavior from '~/behaviors/deprecated_remove_row_behavior';
import BranchSortDropdown from '~/branches/branch_sort_dropdown';
import initDiverganceGraph from '~/branches/divergence_graph';
-import initDeleteBranchButton from '~/branches/init_delete_branch_button';
import initDeleteBranchModal from '~/branches/init_delete_branch_modal';
import initDeleteMergedBranches from '~/branches/init_delete_merged_branches';
+import initBranchMoreActions from '~/branches/init_branch_more_actions';
const { divergingCountsEndpoint, defaultBranch } = document.querySelector(
'.js-branch-list',
@@ -14,8 +14,6 @@ BranchSortDropdown();
initDeprecatedRemoveRowBehavior();
initDeleteMergedBranches();
-document
- .querySelectorAll('.js-delete-branch-button')
- .forEach((elem) => initDeleteBranchButton(elem));
+document.querySelectorAll('.js-branch-more-actions').forEach((elem) => initBranchMoreActions(elem));
initDeleteBranchModal();
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 1e17dd586c7..e60544129ff 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -27,6 +27,7 @@ class Projects::BranchesController < Projects::ApplicationController
# Fetch branches for the specified mode
fetch_branches_by_mode
+ fetch_merge_requests_for_branches
@refs_pipelines = @project.ci_pipelines.latest_successful_for_refs(@branches.map(&:name))
@merged_branch_names = repository.merged_branch_names(@branches.map(&:name))
@@ -199,6 +200,15 @@ class Projects::BranchesController < Projects::ApplicationController
Projects::BranchesByModeService.new(@project, params.merge(sort: @sort, mode: @mode)).execute
end
+ def fetch_merge_requests_for_branches
+ @related_merge_requests = @project
+ .source_of_merge_requests
+ .including_target_project
+ .by_target_branch(@project.default_branch)
+ .by_sorted_source_branches(@branches.map(&:name))
+ .group_by(&:source_branch)
+ end
+
def fetch_branches_for_overview
# Here we get one more branch to indicate if there are more data we're not showing
limit = @overview_max_branches + 1
diff --git a/app/graphql/resolvers/releases_resolver.rb b/app/graphql/resolvers/releases_resolver.rb
index 8c9235c2b5f..06f4ca2065c 100644
--- a/app/graphql/resolvers/releases_resolver.rb
+++ b/app/graphql/resolvers/releases_resolver.rb
@@ -8,6 +8,8 @@ module Resolvers
required: false, default_value: :released_at_desc,
description: 'Sort releases by given criteria.'
+ alias_method :project, :object
+
# This resolver has a custom singular resolver
def self.single
Resolvers::ReleaseResolver
@@ -21,24 +23,11 @@ module Resolvers
}.freeze
def resolve(sort:)
- BatchLoader::GraphQL.for(project).batch do |projects, loader|
- releases = ReleasesFinder.new(
- projects,
- current_user,
- SORT_TO_PARAMS_MAP[sort]
- ).execute
-
- # group_by will not cause N+1 queries here because ReleasesFinder preloads projects
- releases.group_by(&:project).each do |project, versions|
- loader.call(project, versions)
- end
- end
- end
-
- private
-
- def project
- object.respond_to?(:project) ? object.project : object
+ ReleasesFinder.new(
+ project,
+ current_user,
+ SORT_TO_PARAMS_MAP[sort]
+ ).execute
end
end
end
diff --git a/app/graphql/types/ci/catalog/resource_type.rb b/app/graphql/types/ci/catalog/resource_type.rb
deleted file mode 100644
index ac45f299a1a..00000000000
--- a/app/graphql/types/ci/catalog/resource_type.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-# frozen_string_literal: true
-
-module Types
- module Ci
- module Catalog
- # rubocop: disable Graphql/AuthorizeTypes
- class ResourceType < BaseObject
- graphql_name 'CiCatalogResource'
-
- connection_type_class(Types::CountableConnectionType)
-
- field :id, GraphQL::Types::ID, null: false, description: 'ID of the catalog resource.',
- alpha: { milestone: '15.11' }
-
- field :name, GraphQL::Types::String, null: true, description: 'Name of the catalog resource.',
- alpha: { milestone: '15.11' }
-
- field :description, GraphQL::Types::String, null: true, description: 'Description of the catalog resource.',
- alpha: { milestone: '15.11' }
-
- field :icon, GraphQL::Types::String, null: true, description: 'Icon for the catalog resource.',
- method: :avatar_path, alpha: { milestone: '15.11' }
-
- field :web_path, GraphQL::Types::String, null: true, description: 'Web path of the catalog resource.',
- alpha: { milestone: '16.1' }
-
- field :versions, Types::ReleaseType.connection_type, null: true,
- description: 'Versions of the catalog resource.',
- resolver: Resolvers::ReleasesResolver,
- alpha: { milestone: '16.1' }
-
- field :star_count, GraphQL::Types::Int, null: false,
- description: 'Number of times the catalog resource has been starred.',
- alpha: { milestone: '16.1' }
-
- field :forks_count, GraphQL::Types::Int, null: false, calls_gitaly: true,
- description: 'Number of times the catalog resource has been forked.',
- alpha: { milestone: '16.1' }
-
- field :root_namespace, Types::NamespaceType, null: true,
- description: 'Root namespace of the catalog resource.',
- alpha: { milestone: '16.1' }
-
- markdown_field :readme_html, null: false
-
- def web_path
- ::Gitlab::Routing.url_helpers.project_path(object.project)
- end
-
- def forks_count
- BatchLoader::GraphQL.wrap(object.forks_count)
- end
-
- def root_namespace
- BatchLoader::GraphQL.for(object.project_id).batch do |project_ids, loader|
- projects = Project.id_in(project_ids)
-
- # This preloader uses traversal_ids to obtain Group-type root namespaces.
- # It also preloads each project's immediate parent namespace, which effectively
- # preloads the User-type root namespaces since they cannot be nested (parent == root).
- Preloaders::ProjectRootAncestorPreloader.new(projects, :group).execute
- root_namespaces = projects.map(&:root_ancestor)
-
- # NamespaceType requires the `:read_namespace` ability. We must preload the policy for
- # Group-type namespaces to avoid N+1 queries caused by the authorization requests.
- group_root_namespaces = root_namespaces.select { |n| n.type == ::Group.sti_name }
- Preloaders::GroupPolicyPreloader.new(group_root_namespaces, current_user).execute
-
- # For User-type namespaces, the authorization request requires preloading the owner objects.
- user_root_namespaces = root_namespaces.select { |n| n.type == ::Namespaces::UserNamespace.sti_name }
- ActiveRecord::Associations::Preloader.new(records: user_root_namespaces, associations: :owner).call
-
- projects.each { |project| loader.call(project.id, project.root_ancestor) }
- end
- end
-
- def readme_html_resolver
- ::MarkupHelper.markdown(object.project.repository.readme&.data, context.to_h.dup)
- end
- end
- # rubocop: enable Graphql/AuthorizeTypes
- end
- end
-end
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index a500a695029..9fadd5ece14 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -20,6 +20,27 @@ module BranchesHelper
end
end
end
+
+ def merge_request_status(merge_request)
+ return unless merge_request.present?
+ return if merge_request.closed?
+
+ if merge_request.open? || merge_request.locked?
+ variant = :success
+ variant = :warning if merge_request.draft?
+
+ mr_icon = 'merge-request-open'
+ mr_status = _('Open')
+ elsif merge_request.merged?
+ variant = :info
+ mr_icon = 'merge'
+ mr_status = _('Merged')
+ else
+ return
+ end
+
+ { icon: mr_icon, title: "#{mr_status} - #{merge_request.title}", variant: variant }
+ end
end
BranchesHelper.prepend_mod_with('BranchesHelper')
diff --git a/app/helpers/users/callouts_helper.rb b/app/helpers/users/callouts_helper.rb
index bd0ae18448b..27af19db685 100644
--- a/app/helpers/users/callouts_helper.rb
+++ b/app/helpers/users/callouts_helper.rb
@@ -107,12 +107,6 @@ module Users
def dismissed_callout?(object, query)
current_user.dismissed_callout_for_project?(project: object, **query)
end
-
- def user_dismissed_before?(feature_name, dismissed_before)
- return false unless current_user
-
- current_user.dismissed_callout_before?(feature_name, dismissed_before)
- end
end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index e4b2b81005c..d6e9be11d36 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -307,6 +307,13 @@ class MergeRequest < ApplicationRecord
scope :open_and_closed, -> { with_states(:opened, :closed) }
scope :drafts, -> { where(draft: true) }
scope :from_source_branches, ->(branches) { where(source_branch: branches) }
+ scope :by_sorted_source_branches, ->(branches) do
+ from_source_branches(branches)
+ .order(source_branch: :asc, id: :desc)
+ end
+ scope :including_target_project, -> do
+ includes(:target_project)
+ end
scope :by_commit_sha, ->(sha) do
where('EXISTS (?)', MergeRequestDiff.select(1).where('merge_requests.latest_merge_request_diff_id = merge_request_diffs.id').by_commit_sha(sha)).reorder(nil)
end
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index dbc1fe24d96..adff64fad5a 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -1,51 +1,62 @@
- merged = local_assigns.fetch(:merged, false)
- commit = @repository.commit(branch.dereferenced_target)
-- merge_project = merge_request_source_project_for_project(@project)
-%li{ class: "branch-item gl-py-3! js-branch-item js-branch-#{branch.name}", data: { name: branch.name, qa_selector: 'branch_container', qa_name: branch.name } }
- .branch-item-content.gl-display-flex.gl-align-items-center.gl-px-3.gl-py-2
- .branch-info
- .gl-display-flex.gl-align-items-center
- = sprite_icon('branch', size: 12, css_class: 'gl-flex-shrink-0')
- = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name gl-ml-3', data: { qa_selector: 'branch_link' } do
- = branch.name
- = clipboard_button(text: branch.name, title: _("Copy branch name"))
- - if branch.name == @repository.root_ref
- = gl_badge_tag s_('DefaultBranchLabel|default'), { variant: :info, size: :sm }, { class: 'gl-ml-2', data: { qa_selector: 'badge_content' } }
- - elsif merged
- = gl_badge_tag s_('Branches|merged'), { variant: :info, size: :sm }, { class: 'gl-ml-2', title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref }, data: { toggle: 'tooltip', container: 'body', qa_selector: 'badge_content' } }
- - if protected_branch?(@project, branch)
- = gl_badge_tag s_('Branches|protected'), { variant: :success, size: :sm }, { class: 'gl-ml-2', data: { qa_selector: 'badge_content' } }
-
- = render_if_exists 'projects/branches/diverged_from_upstream', branch: branch
-
- .block-truncated
- - if commit
- = render 'projects/branches/commit', commit: commit, project: @project
- - else
- = s_('Branches|Can’t find HEAD commit for this branch')
-
- - if branch.name != @repository.root_ref
- .js-branch-divergence-graph
-
- .controls.d-none.d-md-block<
- - if commit_status
- = render 'ci/status/icon', size: 24, status: commit_status, option_css_classes: 'gl-display-inline-flex gl-vertical-align-middle gl-mr-5'
- - elsif show_commit_status
- .gl-display-inline-flex.gl-vertical-align-middle.gl-mr-5
- %svg.s24
-
- - if merge_project && create_mr_button?(from: branch.name, source_project: @project)
- = render Pajamas::ButtonComponent.new(href: create_mr_path(from: branch.name, source_project: @project)) do
- = _('Merge request')
-
- - if branch.name != @repository.root_ref
- = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name),
- class: "gl-button btn btn-default js-onboarding-compare-branches #{'gl-ml-3' unless merge_project}",
- method: :post,
- title: s_('Branches|Compare') do
- = s_('Branches|Compare')
-
- = render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name], class: 'gl-vertical-align-top'
-
- - if can?(current_user, :push_code, @project)
- = render 'projects/branches/delete_branch_modal_button', project: @project, branch: branch, merged: merged
+- related_merge_request = @related_merge_requests[branch.name]&.first
+- mr_status = merge_request_status(related_merge_request)
+- is_default_branch = branch.name == @repository.root_ref
+
+%li{ class: "branch-item gl-display-flex! gl-align-items-center! js-branch-item js-branch-#{branch.name} gl-pl-3!", data: { name: branch.name, qa_selector: 'branch_container', qa_name: branch.name } }
+ .branch-info
+ .gl-display-flex.gl-align-items-center
+ = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name', data: { qa_selector: 'branch_link' } do
+ = branch.name
+ = clipboard_button(text: branch.name, title: _("Copy branch name"))
+ - if is_default_branch
+ = gl_badge_tag s_('DefaultBranchLabel|default'), { variant: :neutral, size: :sm }, { class: 'gl-ml-2', data: { qa_selector: 'badge_content' } }
+ - if protected_branch?(@project, branch)
+ = gl_badge_tag s_('Branches|protected'), { variant: :muted, size: :sm }, { class: 'gl-ml-2', data: { qa_selector: 'badge_content' } }
+
+ = render_if_exists 'projects/branches/diverged_from_upstream', branch: branch
+
+ .block-truncated
+ - if commit
+ = render 'projects/branches/commit', commit: commit, project: @project
+ - else
+ = s_('Branches|Can’t find HEAD commit for this branch')
+
+ - if branch.name != @repository.root_ref
+ .js-branch-divergence-graph
+
+ .pipeline-status.d-none.d-md-block<
+ - if commit_status
+ = render 'ci/status/icon', size: 16, status: commit_status, option_css_classes: 'gl-display-inline-flex gl-vertical-align-middle gl-mr-5'
+ - elsif show_commit_status
+ .gl-display-inline-flex.gl-vertical-align-middle.gl-mr-5
+ %svg.s16
+
+
+ - if mr_status.present?
+ .issuable-reference.gl-display-flex.gl-justify-content-end.gl-min-w-10.gl-ml-5.gl-mr-4
+ = gl_badge_tag issuable_reference(related_merge_request),
+ { icon: mr_status[:icon], variant: mr_status[:variant], size: :md, href: merge_request_path(related_merge_request) },
+ { class: 'gl-mr-2', title: mr_status[:title], data: { toggle: 'tooltip', container: 'body', qa_selector: 'badge_content' } }
+
+ .controls.d-none.d-md-block<
+ - if mr_status.nil? && create_mr_button?(from: branch.name, source_project: @project)
+ = render Pajamas::ButtonComponent.new(icon: 'merge-request', href: create_mr_path(from: branch.name, source_project: @project), button_options: { class: 'has-tooltip gl-mr-2!', title: _('New merge request') }) do
+ = _('New')
+
+ = render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name], css_class: 'gl-mr-1!'
+
+ - if !is_default_branch
+ .js-branch-more-actions{ data: {
+ branch_name: branch.name,
+ default_branch_name: @repository.root_ref,
+ can_delete_branch: user_access(@project).can_delete_branch?(branch.name).to_s,
+ is_protected_branch: protected_branch?(@project, branch).to_s,
+ merged: merged.to_s,
+ compare_path: project_compare_index_path(@project, from: @repository.root_ref, to: branch.name),
+ delete_path: project_branch_path(@project, branch.name),
+ } }
+ - else
+ .gl-display-inline-flex.gl-w-7
+ &nbsp;
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index cfa0cf6d07b..6bbd0617598 100644
--- a/app/views/projects/branches/_commit.html.haml
+++ b/app/views/projects/branches/_commit.html.haml
@@ -1,9 +1,7 @@
-.branch-commit.cgray
- .icon-container.commit-icon
- = custom_icon("icon_commit")
+.branch-commit.gl-font-sm.gl-text-gray-500
= link_to commit.short_id, project_commit_path(project, commit.id), class: "commit-sha"
&middot;
%span.str-truncated
- = link_to_markdown commit.title, project_commit_path(project, commit.id), class: "commit-row-message cgray"
+ = link_to_markdown commit.title, project_commit_path(project, commit.id), class: "commit-row-message gl-text-gray-500!"
&middot;
%span.gl-text-secondary= time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/branches/_delete_branch_modal_button.html.haml b/app/views/projects/branches/_delete_branch_modal_button.html.haml
deleted file mode 100644
index 829a459ad2c..00000000000
--- a/app/views/projects/branches/_delete_branch_modal_button.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-- if branch.name == @project.repository.root_ref
- .js-delete-branch-button{ data: { tooltip: s_('Branches|The default branch cannot be deleted'),
- disabled: true.to_s } }
-- elsif protected_branch?(@project, branch)
- - if can?(current_user, :push_to_delete_protected_branch, @project)
- .js-delete-branch-button{ data: { branch_name: branch.name,
- is_protected_branch: true.to_s,
- merged: merged.to_s,
- default_branch_name: @project.repository.root_ref,
- delete_path: project_branch_path(@project, branch.name) } }
- - else
- .js-delete-branch-button{ data: { is_protected_branch: true.to_s,
- disabled: true.to_s } }
-- else
- .js-delete-branch-button{ data: { branch_name: branch.name,
- merged: merged.to_s,
- default_branch_name: @project.repository.root_ref,
- delete_path: project_branch_path(@project, branch.name) } }
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 1fbc399c3ff..bbee7d66dcb 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -1,10 +1,11 @@
- project = local_assigns.fetch(:project)
- ref = local_assigns.fetch(:ref)
- pipeline = local_assigns.fetch(:pipeline) { project.latest_successful_pipeline_for(ref) }
+- css_class = local_assigns.fetch(:css_class, '')
- if !project.empty_repo? && can?(current_user, :download_code, project)
- archive_prefix = "#{project.path}-#{ref.tr('/', '-')}"
- .project-action-button.dropdown.gl-dropdown.inline>
+ .project-action-button.dropdown.gl-dropdown.inline{ class: css_class }>
%button.gl-button.btn.btn-default.dropdown-toggle.gl-dropdown-toggle.dropdown-icon-only.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download'), 'data-display' => 'static', data: { qa_selector: 'download_source_code_button' } }
= sprite_icon('download', css_class: 'gl-icon dropdown-icon')
%span.sr-only= _('Select Archive Format')
diff --git a/config/feature_categories.yml b/config/feature_categories.yml
index 74d0763c318..048d7384937 100644
--- a/config/feature_categories.yml
+++ b/config/feature_categories.yml
@@ -50,6 +50,7 @@
- disaster_recovery
- dora_metrics
- dynamic_application_security_testing
+- editor_extensions
- environment_management
- error_budgets
- error_tracking
@@ -125,6 +126,7 @@
- source_code_management
- static_application_security_testing
- subscription_management
+- switchboard
- system_access
- team_planning
- tracing
diff --git a/config/feature_flags/development/fill_in_mr_template.yml b/config/feature_flags/development/fill_in_mr_template.yml
new file mode 100644
index 00000000000..871d2d09f2e
--- /dev/null
+++ b/config/feature_flags/development/fill_in_mr_template.yml
@@ -0,0 +1,8 @@
+---
+name: fill_in_mr_template
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121233
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/412796
+milestone: '16.1'
+type: development
+group: group::code review
+default_enabled: false
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 970df2d4465..5c6ec888e54 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -551,6 +551,8 @@
- 1
- - security_scan_result_policies_sync_opened_merge_requests
- 1
+- - security_scan_result_policies_sync_project
+ - 1
- - security_scans
- 2
- - security_scans_purge_by_job_id
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index fcfae6cbe70..0e625590262 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -10,12 +10,7 @@ type: reference, howto
With the [PlantUML](https://plantuml.com) integration, you can create diagrams in snippets, wikis, and repositories.
This integration is enabled on GitLab.com for all SaaS users and does not require any additional configuration.
-To set up the integration on a self-managed instance, you must:
-
-1. [Configure your PlantUML server](#configure-your-plantuml-server).
-1. [Configure local PlantUML access](#configure-local-plantuml-access).
-1. [Configure PlantUML security](#configure-plantuml-security).
-1. [Enable the integration](#enable-plantuml-integration).
+To set up the integration on a self-managed instance, you must [configure your PlantUML server](#configure-your-plantuml-server).
After completing the integration, PlantUML converts `plantuml`
blocks to an HTML image tag, with the source pointing to the PlantUML instance. The PlantUML
@@ -123,41 +118,7 @@ services:
container_name: plantuml
```
-### Debian/Ubuntu
-
-You can install and configure a PlantUML server in Debian/Ubuntu distributions
-using Tomcat:
-
-1. Run these commands to create a `plantuml.war` file from the source code:
-
- ```shell
- sudo apt-get install graphviz openjdk-8-jdk git-core maven
- git clone https://github.com/plantuml/plantuml-server.git
- cd plantuml-server
- mvn package
- ```
-
-1. Deploy the `.war` file from the previous step with these commands:
-
- ```shell
- sudo apt-get install tomcat8
- sudo cp target/plantuml.war /var/lib/tomcat8/webapps/plantuml.war
- sudo chown tomcat8:tomcat8 /var/lib/tomcat8/webapps/plantuml.war
- sudo service tomcat8 restart
- ```
-
-The Tomcat service should restart. After the restart is complete, the
-PlantUML integration is ready and listening for requests on port 8080:
-`http://localhost:8080/plantuml`
-
-To change these defaults, edit the `/etc/tomcat8/server.xml` file.
-
-NOTE:
-The default URL is different when using this approach. The Docker-based image
-makes the service available at the root URL, with no relative path. Adjust
-the configuration below accordingly.
-
-## Configure local PlantUML access
+#### Configure local PlantUML access
The PlantUML server runs locally on your server, so it can't be accessed
externally by default. Your server must catch external PlantUML
@@ -180,9 +141,6 @@ To enable this redirection:
```ruby
# Docker deployment
nginx['custom_gitlab_server_config'] = "location /-/plantuml/ { \n rewrite ^/-/plantuml/(.*) /$1 break;\n proxy_cache off; \n proxy_pass http://plantuml:8080/; \n}\n"
-
- # Built from source
- nginx['custom_gitlab_server_config'] = "location /-/plantuml { \n rewrite ^/-/plantuml/(.*) /$1 break;\n proxy_cache off; \n proxy_pass http://localhost:8080/plantuml; \n}\n"
```
1. To activate the changes, run the following command:
@@ -191,6 +149,146 @@ To enable this redirection:
sudo gitlab-ctl reconfigure
```
+### Debian/Ubuntu
+
+You can install and configure a PlantUML server in Debian/Ubuntu distributions
+using Tomcat or Jetty.
+
+Prerequisites:
+
+- JRE/JDK version 11 or later.
+- Apache Maven version 3.0.2 or later.
+- (Recommended) Jetty version 11 or later.
+- (Recommended) Tomcat version 10 or later.
+
+#### Installation
+
+PlantUML recommends to install Tomcat 10 or above. The scope of this page only
+includes setting up a basic Tomcat server. For more production-ready configurations,
+see the [Tomcat Documentation](https://tomcat.apache.org/tomcat-10.1-doc/index.html).
+
+1. Install JDK/JRE 11 and Maven:
+
+ ```shell
+ sudo apt update
+ sudo apt-get install graphviz default-jdk git-core maven
+ ```
+
+1. Add a user for Tomcat:
+
+ ```shell
+ sudo useradd -m -d /opt/tomcat -U -s /bin/false tomcat
+ ```
+
+1. Install and configure Tomcat 10:
+
+ ```shell
+ cd /tmp & wget https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.9/bin/apache-tomcat-10.1.9.tar.gz
+ sudo tar xzvf apache-tomcat-10*tar.gz -C /opt/tomcat --strip-components=1
+ sudo chown -R tomcat:tomcat /opt/tomcat/
+ sudo chmod -R u+x /opt/tomcat/bin
+ ```
+
+1. Create a systemd service. Edit the `/etc/systemd/system/tomcat.service` file and add:
+
+ ```shell
+ [Unit]
+ Description=Tomcat
+ After=network.target
+
+ [Service]
+ Type=forking
+
+ User=tomcat
+ Group=tomcat
+
+ Environment="JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64"
+ Environment="JAVA_OPTS=-Djava.security.egd=file:///dev/urandom"
+ Environment="CATALINA_BASE=/opt/tomcat"
+ Environment="CATALINA_HOME=/opt/tomcat"
+ Environment="CATALINA_PID=/opt/tomcat/temp/tomcat.pid"
+ Environment="CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC"
+
+ ExecStart=/opt/tomcat/bin/startup.sh
+ ExecStop=/opt/tomcat/bin/shutdown.sh
+
+ RestartSec=10
+ Restart=always
+
+ [Install]
+ WantedBy=multi-user.target
+ ```
+
+ `JAVA_HOME` should be the same path as seen in `sudo update-java-alternatives -l`.
+
+1. To configure ports, edit your `/opt/tomcat/conf/server.xml` and choose your
+ ports. Avoid using port `8080`, as [Puma](../operations/puma.md) listens on port `8080` for metrics.
+
+ ```shell
+ <Server port="8006" shutdown="SHUTDOWN">
+ ...
+ <Connector port="8005" protocol="HTTP/1.1"
+ ...
+ ```
+
+1. Reload and start Tomcat:
+
+ ```shell
+ sudo systemctl daemon-reload
+ sudo systemctl start tomcat
+ sudo systemctl status tomcat
+ sudo systemctl enable tomcat
+ ```
+
+ The Java process should be listening on these ports:
+
+ ```shell
+ root@gitlab-omnibus:/plantuml-server# netstat -plnt | grep java
+ tcp6 0 0 127.0.0.1:8006 :::* LISTEN 14935/java
+ tcp6 0 0 :::8005 :::* LISTEN 14935/java
+ ```
+
+1. Modify your NGINX configuration. The `proxy_pass` port matches the Connector port in the `server.xml`:
+
+ ```shell
+ nginx['custom_gitlab_server_config'] = "location /-/plantuml {
+ rewrite ^/-/(plantuml.*) /$1 break;
+ proxy_set_header HOST $host;
+ proxy_set_header X-Forwarded-Host $host;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_cache off;
+ proxy_pass http://localhost:8005/plantuml;
+ }"
+ ```
+
+1. Reconfigure GitLab to read the new changes:
+
+ ```shell
+ sudo gitlab-ctl reconfigure
+ ```
+
+1. Install PlantUML and copy the `.war` file:
+
+ ```shell
+ cd / & git clone https://github.com/plantuml/plantuml-server.git
+ cd plantuml-server
+ mvn package
+ cp /plantuml-server/target/plantuml.war /opt/tomcat/webapps/plantuml.war
+ chown tomcat:tomcat /opt/tomcat/webapps/plantuml.war
+ systemctl restart tomcat
+ ```
+
+The Tomcat service should restart. After the restart is complete, the
+PlantUML integration is ready and listening for requests on port `8005`:
+`http://localhost:8005/plantuml`
+
+To change the Tomcat defaults, edit the `/opt/tomcat/conf/server.xml` file.
+
+NOTE:
+The default URL is different when using this approach. The Docker-based image
+makes the service available at the root URL, with no relative path. Adjust
+the configuration below accordingly.
+
### Configure PlantUML security
PlantUML has features that allow fetching network resources. If you self-host the
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 816fb8ab439..76bf554c0d8 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -115,7 +115,7 @@ Returns [`CiConfig`](#ciconfig).
### `Query.ciMinutesUsage`
-CI/CD minutes usage data for a namespace.
+Compute usage data for a namespace.
Returns [`CiMinutesNamespaceMonthlyUsageConnection`](#ciminutesnamespacemonthlyusageconnection).
@@ -128,7 +128,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="queryciminutesusagedate"></a>`date` | [`Date`](#date) | Date for which to retrieve the usage data, should be the first day of a month. |
-| <a id="queryciminutesusagenamespaceid"></a>`namespaceId` | [`NamespaceID`](#namespaceid) | Global ID of the Namespace for the monthly CI/CD minutes usage. |
+| <a id="queryciminutesusagenamespaceid"></a>`namespaceId` | [`NamespaceID`](#namespaceid) | Global ID of the Namespace for the monthly compute usage. |
### `Query.ciPipelineStage`
@@ -1043,6 +1043,7 @@ Input type: `AiActionInput`
| <a id="mutationaiactionclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationaiactionexplaincode"></a>`explainCode` | [`AiExplainCodeInput`](#aiexplaincodeinput) | Input for explain_code AI action. |
| <a id="mutationaiactionexplainvulnerability"></a>`explainVulnerability` | [`AiExplainVulnerabilityInput`](#aiexplainvulnerabilityinput) | Input for explain_vulnerability AI action. |
+| <a id="mutationaiactionfillinmergerequesttemplate"></a>`fillInMergeRequestTemplate` | [`AiFillInMergeRequestTemplateInput`](#aifillinmergerequesttemplateinput) | Input for fill_in_merge_request_template AI action. |
| <a id="mutationaiactiongeneratecommitmessage"></a>`generateCommitMessage` | [`AiGenerateCommitMessageInput`](#aigeneratecommitmessageinput) | Input for generate_commit_message AI action. |
| <a id="mutationaiactiongeneratedescription"></a>`generateDescription` | [`AiGenerateDescriptionInput`](#aigeneratedescriptioninput) | Input for generate_description AI action. |
| <a id="mutationaiactiongeneratetestfile"></a>`generateTestFile` | [`GenerateTestFileInput`](#generatetestfileinput) | Input for generate_test_file AI action. |
@@ -12818,10 +12819,10 @@ CI/CD variables given to a manual job.
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="ciminutesnamespacemonthlyusageminutes"></a>`minutes` | [`Int`](#int) | Total number of minutes used by all projects in the namespace. |
+| <a id="ciminutesnamespacemonthlyusageminutes"></a>`minutes` | [`Int`](#int) | Total number of units of compute used by all projects in the namespace. |
| <a id="ciminutesnamespacemonthlyusagemonth"></a>`month` | [`String`](#string) | Month related to the usage data. |
| <a id="ciminutesnamespacemonthlyusagemonthiso8601"></a>`monthIso8601` | [`ISO8601Date`](#iso8601date) | Month related to the usage data in ISO 8601 date format. |
-| <a id="ciminutesnamespacemonthlyusageprojects"></a>`projects` | [`CiMinutesProjectMonthlyUsageConnection`](#ciminutesprojectmonthlyusageconnection) | CI/CD minutes usage data for projects in the namespace. (see [Connections](#connections)) |
+| <a id="ciminutesnamespacemonthlyusageprojects"></a>`projects` | [`CiMinutesProjectMonthlyUsageConnection`](#ciminutesprojectmonthlyusageconnection) | Compute usage data for projects in the namespace. (see [Connections](#connections)) |
| <a id="ciminutesnamespacemonthlyusagesharedrunnersduration"></a>`sharedRunnersDuration` | [`Int`](#int) | Total duration (in seconds) of shared runners use by the namespace for the month. |
### `CiMinutesProjectMonthlyUsage`
@@ -12830,7 +12831,7 @@ CI/CD variables given to a manual job.
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="ciminutesprojectmonthlyusageminutes"></a>`minutes` | [`Int`](#int) | Number of CI/CD minutes used by the project in the month. |
+| <a id="ciminutesprojectmonthlyusageminutes"></a>`minutes` | [`Int`](#int) | Number of units of compute used by the project in the month. |
| <a id="ciminutesprojectmonthlyusagename"></a>`name` **{warning-solid}** | [`String`](#string) | **Deprecated** in 15.6. Use `project.name`. |
| <a id="ciminutesprojectmonthlyusageproject"></a>`project` | [`Project`](#project) | Project having the recorded usage. |
| <a id="ciminutesprojectmonthlyusagesharedrunnersduration"></a>`sharedRunnersDuration` | [`Int`](#int) | Total duration (in seconds) of shared runners use by the project for the month. |
@@ -27963,6 +27964,19 @@ see the associated mutation type above.
| ---- | ---- | ----------- |
| <a id="aiexplainvulnerabilityinputresourceid"></a>`resourceId` | [`AiModelID!`](#aimodelid) | Global ID of the resource to mutate. |
+### `AiFillInMergeRequestTemplateInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="aifillinmergerequesttemplateinputcontent"></a>`content` | [`String!`](#string) | Template content to fill in. |
+| <a id="aifillinmergerequesttemplateinputresourceid"></a>`resourceId` | [`AiModelID!`](#aimodelid) | Global ID of the resource to mutate. |
+| <a id="aifillinmergerequesttemplateinputsourcebranch"></a>`sourceBranch` | [`String!`](#string) | Source branch of the changes. |
+| <a id="aifillinmergerequesttemplateinputsourceprojectid"></a>`sourceProjectId` | [`ID`](#id) | ID of the project where the changes are from. |
+| <a id="aifillinmergerequesttemplateinputtargetbranch"></a>`targetBranch` | [`String!`](#string) | Target branch of where the changes will be merged into. |
+| <a id="aifillinmergerequesttemplateinputtitle"></a>`title` | [`String!`](#string) | Title of the merge request to be created. |
+
### `AiGenerateCommitMessageInput`
#### Arguments
diff --git a/doc/ci/pipelines/cicd_minutes.md b/doc/ci/pipelines/cicd_minutes.md
index 2da3c773004..41a7f853de1 100644
--- a/doc/ci/pipelines/cicd_minutes.md
+++ b/doc/ci/pipelines/cicd_minutes.md
@@ -16,7 +16,7 @@ is tracked with a quota of CI/CD minutes.
By default, one minute of execution time by a single job uses
one CI/CD minute. The total amount of CI/CD minutes used by a pipeline is
-[the sum of all its jobs' durations](#how-cicd-minute-usage-is-calculated).
+[the sum of all its jobs' durations](#how-compute-usage-is-calculated).
Jobs can run concurrently, so the total CI/CD minute usage can be higher than the
end-to-end duration of a pipeline.
@@ -86,7 +86,7 @@ NOTE:
You can set a compute quota for only top-level groups or user namespaces.
If you set a quota for a subgroup, it is not used.
-## View CI/CD minutes
+## View compute usage
Prerequisite:
@@ -101,17 +101,15 @@ Prerequisite:
- You must have the Owner role for the group.
-To view CI/CD minutes being used for your group:
+To view compute usage for your group:
1. On the top bar, select **Main menu > Groups** and find your group. The group must not be a subgroup.
1. On the left sidebar, select **Settings > Usage Quotas**.
1. Select the **Pipelines** tab.
-![Group CI/CD minutes quota](img/group_cicd_minutes_quota.png)
-
-The projects list shows projects with CI/CD minute usage or shared runners usage
+The projects list shows projects with compute usage or shared runners usage
in the current month only. The list includes all projects in the namespace and its
-subgroups, sorted in descending order of CI/CD minute usage.
+subgroups, sorted in descending order of compute usage.
### View Usage Quota reports for a personal namespace
@@ -121,15 +119,15 @@ Prerequisite:
- The namespace must be your personal namespace.
-You can view the number of CI/CD minutes being used by a personal namespace:
+You can view the compute usage for a personal namespace:
1. On the top bar, in the upper-right corner, select your avatar.
1. Select **Edit profile**.
1. On the left sidebar, select **Usage Quotas**.
The projects list shows [personal projects](../../user/project/working_with_projects.md#view-personal-projects)
-with CI/CD minutes usage or shared runners usage in the current month only. The list
-is sorted in descending order of CI/CD minute usage.
+with compute usage or shared runners usage in the current month only. The list
+is sorted in descending order of compute usage.
## Purchase additional CI/CD minutes **(FREE SAAS)**
@@ -191,9 +189,9 @@ To purchase additional minutes for your personal namespace:
After your payment is processed, the additional CI/CD minutes are added to your personal
namespace.
-## How CI/CD minute usage is calculated
+## How compute usage is calculated
-GitLab uses this formula to calculate the CI/CD minute usage of a job:
+GitLab uses this formula to calculate the compute usage of a job:
```plaintext
Job duration * Cost factor
@@ -203,18 +201,18 @@ Job duration * Cost factor
not including time spent in the `created` or `pending` statuses.
- [**Cost factor**](#cost-factor): A number based on project visibility.
-The value is transformed into minutes and added to the count of used CI/CD minutes
+The value is transformed into units of compute and added to the count of used units
in the job's top-level namespace.
For example, if a user `alice` runs a pipeline:
-- Under the `gitlab-org` namespace, the CI/CD minutes used by each job in the pipeline are
+- Under the `gitlab-org` namespace, the units of compute used by each job in the pipeline are
added to the overall consumption for the `gitlab-org` namespace, not the `alice` namespace.
-- For one of the personal projects in their namespace, the CI/CD minutes are added
+- For one of the personal projects in their namespace, the units of compute are added
to the overall consumption for the `alice` namespace.
-The CI/CD minutes used by one pipeline is the total CI/CD minutes used by all the jobs
-that ran in the pipeline. Jobs can run concurrently, so the total CI/CD minutes usage
+The compute used by one pipeline is the total units of compute used by all the jobs
+that ran in the pipeline. Jobs can run concurrently, so the total compute usage
can be higher than the end-to-end duration of a pipeline.
### Cost factor
@@ -225,24 +223,24 @@ The cost factors for jobs running on shared runners on GitLab.com are:
- Exceptions for public projects:
- `0.5` for projects in the [GitLab for Open Source program](../../subscriptions/community_programs.md#gitlab-for-open-source).
- `0.008` for forks of projects in the [GitLab for Open Source program](../../subscriptions/community_programs.md#gitlab-for-open-source). For every 125 minutes of job execution time,
- you use 1 CI/CD minute.
+ you use 1 unit of compute.
- Discounted dynamically for [community contributions to GitLab projects](#cost-factor-for-community-contributions-to-gitlab-projects).
The cost factors on self-managed instances are:
-- `0` for public projects, so they do not consume CI/CD minutes.
+- `0` for public projects, so they do not consume units of compute.
- `1` for internal and private projects.
#### Cost factor for community contributions to GitLab projects
Community contributors can use up to 300,000 minutes on shared runners when contributing to open source projects
maintained by GitLab. The maximum of 300,000 minutes would only be possible if contributing exclusively to projects [part of the GitLab product](https://about.gitlab.com/handbook/engineering/metrics/#projects-that-are-part-of-the-product). The total number of minutes available on shared runners
-is reduced by the CI/CD minutes used by pipelines from other projects.
+is reduced by the units of compute used by pipelines from other projects.
The 300,000 minutes applies to all SaaS tiers, and the cost factor calculation is:
-- `Monthly minute quota / 300,000 job duration minutes = Cost factor`
+- `Monthly compute quota / 300,000 job duration minutes = Cost factor`
-For example, with the 10,000 CI/CD minutes per month in the Premium tier:
+For example, with a monthly compute quota of 10,000 in the Premium tier:
- 10,000 / 300,000 = 0.03333333333 cost factor.
@@ -261,41 +259,41 @@ GitLab administrators can add a namespace to the reduced cost factor
GitLab SaaS runners have different cost factors, depending on the runner type (Linux, Windows, macOS) and the virtual machine configuration.
-| GitLab SaaS runner type | Machine Size | CI/CD minutes cost factor |
-| :--------- | :------------------- | :--------- |
-| Linux OS amd64 | small |1|
-| Linux OS amd64 | medium |2|
-| Linux OS amd64 | large |3|
-| Linux OS amd64 + GPU-enabled | medium, GPU standard |7|
-| macOS M1 | Medium |6|
+| GitLab SaaS runner type | Machine Size | Cost factor |
+|:-----------------------------|:---------------------|:------------|
+| Linux OS amd64 | small | 1 |
+| Linux OS amd64 | medium | 2 |
+| Linux OS amd64 | large | 3 |
+| Linux OS amd64 + GPU-enabled | medium, GPU standard | 7 |
+| macOS M1 | Medium | 6 |
-### Monthly reset of CI/CD minutes
+### Monthly reset of compute usage
-On the first day of each calendar month, the accumulated usage of CI/CD minutes is reset to `0`
+On the first day of each calendar month, the accumulated compute usage is reset to `0`
for all namespaces that use shared runners. This means your full quota is available, and
calculations start again from `0`.
-For example, if you have a monthly quota of `10,000` CI/CD minutes:
+For example, if you have a monthly quota of `10,000` units of compute:
-- On **April 1**, you have `10,000` minutes.
-- During April, you use only `6,000` of the `10,000` minutes.
-- On **May 1**, the accumulated usage of minutes resets to `0`, and you have `10,000` minutes to use again
+- On **April 1**, you have `10,000` units of compute.
+- During April, you use only `6,000` of the `10,000` units of compute.
+- On **May 1**, the accumulated compute usage resets to `0`, and you have `10,000` units of compute to use again
during May.
Usage data for the previous month is kept to show historical view of the consumption over time.
-### Monthly rollover of purchased CI/CD minutes
+### Monthly rollover of purchased units of compute
-If you purchase additional CI/CD minutes and don't use the full amount, the remaining amount rolls over to
+If you purchase additional units of compute and don't use the full amount, the remaining amount rolls over to
the next month.
For example:
-- On **April 1**, you purchase `5,000` additional CI/CD minutes.
-- During April, you use only `3,000` of the `5,000` additional minutes.
-- On **May 1**, the unused minute roll over, so you have `2,000` additional minutes available for May.
+- On **April 1**, you purchase `5,000` additional units of compute.
+- During April, you use only `3,000` of the `5,000` additional units of compute.
+- On **May 1**, the unused units of compute roll over, so you have `2,000` additional units of compute available for May.
-Additional CI/CD minutes are a one-time purchase and do not renew or refresh each month.
+Additional units of compute are a one-time purchase and do not renew or refresh each month.
## What happens when you exceed the quota
diff --git a/doc/ci/pipelines/img/group_cicd_minutes_quota.png b/doc/ci/pipelines/img/group_cicd_minutes_quota.png
deleted file mode 100644
index 318527426bd..00000000000
--- a/doc/ci/pipelines/img/group_cicd_minutes_quota.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index 67430da0739..1656b2147d5 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -110,7 +110,7 @@ as it can cause the pipeline to behave unexpectedly.
| `CI_REGISTRY_PASSWORD` | 9.0 | all | The password to push containers to the project's GitLab Container Registry. Only available if the Container Registry is enabled for the project. This password value is the same as the `CI_JOB_TOKEN` and is valid only as long as the job is running. Use the `CI_DEPLOY_PASSWORD` for long-lived access to the registry |
| `CI_REGISTRY_USER` | 9.0 | all | The username to push containers to the project's GitLab Container Registry. Only available if the Container Registry is enabled for the project. |
| `CI_REGISTRY` | 8.10 | 0.5 | The address of the GitLab Container Registry. Only available if the Container Registry is enabled for the project. This variable includes a `:port` value if one is specified in the registry configuration. |
-| `CI_REPOSITORY_URL` | 9.0 | all | The URL to clone the Git repository. |
+| `CI_REPOSITORY_URL` | 9.0 | all | The full path to Git clone (HTTP) the repository with a [CI/CD job token](../jobs/ci_job_token.md), in the format `https://gitlab-ci-token:$CI_JOB_TOKEN@gitlab.example.com/my-group/my-project.git`. |
| `CI_RUNNER_DESCRIPTION` | 8.10 | 0.5 | The description of the runner. |
| `CI_RUNNER_EXECUTABLE_ARCH` | all | 10.6 | The OS/architecture of the GitLab Runner executable. Might not be the same as the environment of the executor. |
| `CI_RUNNER_ID` | 8.10 | 0.5 | The unique ID of the runner being used. |
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 95b4baadf46..3634d963636 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -900,7 +900,7 @@ msgstr ""
msgid "%{milliseconds}ms"
msgstr ""
-msgid "%{minutesUsed} minutes"
+msgid "%{minutesUsed} units"
msgstr ""
msgid "%{model_name} not found"
@@ -2921,9 +2921,6 @@ msgstr ""
msgid "Additional diagram formats"
msgstr ""
-msgid "Additional minutes"
-msgstr ""
-
msgid "Additional minutes:"
msgstr ""
@@ -2942,6 +2939,9 @@ msgstr ""
msgid "Additional text to show on the sign-in page"
msgstr ""
+msgid "Additional units"
+msgstr ""
+
msgid "Address"
msgstr ""
@@ -8107,18 +8107,12 @@ msgstr ""
msgid "Branches|Filter by branch name"
msgstr ""
-msgid "Branches|Merged into %{default_branch}"
-msgstr ""
-
msgid "Branches|New branch"
msgstr ""
msgid "Branches|No branches to show"
msgstr ""
-msgid "Branches|Only a project maintainer or owner can delete a protected branch"
-msgstr ""
-
msgid "Branches|Overview"
msgstr ""
@@ -8158,9 +8152,6 @@ msgstr ""
msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
msgstr ""
-msgid "Branches|The default branch cannot be deleted"
-msgstr ""
-
msgid "Branches|This branch hasn't been merged into %{defaultBranchName}. To avoid data loss, consider merging this branch before deleting it."
msgstr ""
@@ -8200,9 +8191,6 @@ msgstr ""
msgid "Branches|diverged from upstream"
msgstr ""
-msgid "Branches|merged"
-msgstr ""
-
msgid "Branches|protected"
msgstr ""
@@ -8563,9 +8551,6 @@ msgstr ""
msgid "CI/CD limits"
msgstr ""
-msgid "CI/CD minutes"
-msgstr ""
-
msgid "CI/CD|No projects have been added to the scope"
msgstr ""
@@ -48362,6 +48347,9 @@ msgstr ""
msgid "Unhappy?"
msgstr ""
+msgid "Units of compute"
+msgstr ""
+
msgid "Units|d"
msgstr ""
@@ -48725,25 +48713,25 @@ msgstr ""
msgid "UsageQuota|Audio samples, videos, datasets, and graphics."
msgstr ""
-msgid "UsageQuota|Buy additional minutes"
+msgid "UsageQuota|Buy additional units of compute"
msgstr ""
msgid "UsageQuota|Buy storage"
msgstr ""
-msgid "UsageQuota|CI minutes usage by month"
+msgid "UsageQuota|Code packages and container images."
msgstr ""
-msgid "UsageQuota|CI minutes usage by project"
+msgid "UsageQuota|Compute usage"
msgstr ""
-msgid "UsageQuota|CI/CD minutes usage"
+msgid "UsageQuota|Compute usage by month"
msgstr ""
-msgid "UsageQuota|CI/CD minutes usage since %{usageSince}"
+msgid "UsageQuota|Compute usage by project"
msgstr ""
-msgid "UsageQuota|Code packages and container images."
+msgid "UsageQuota|Compute usage since %{usageSince}"
msgstr ""
msgid "UsageQuota|Container Registry"
@@ -48800,7 +48788,7 @@ msgstr ""
msgid "UsageQuota|Namespace storage used"
msgstr ""
-msgid "UsageQuota|No CI minutes usage data available."
+msgid "UsageQuota|No compute usage data available."
msgstr ""
msgid "UsageQuota|No projects to display."
@@ -48881,7 +48869,7 @@ msgstr ""
msgid "UsageQuota|This namespace has no projects which used shared runners in the current period"
msgstr ""
-msgid "UsageQuota|This table omits projects that used 0 CI/CD minutes or 0 shared runners duration"
+msgid "UsageQuota|This table omits projects that used 0 units of compute or 0 shared runners duration"
msgstr ""
msgid "UsageQuota|Transfer"
diff --git a/qa/qa/page/project/branches/show.rb b/qa/qa/page/project/branches/show.rb
index a97d0afd160..af328f876f7 100644
--- a/qa/qa/page/project/branches/show.rb
+++ b/qa/qa/page/project/branches/show.rb
@@ -5,7 +5,7 @@ module QA
module Project
module Branches
class Show < Page::Base
- view 'app/assets/javascripts/branches/components/delete_branch_button.vue' do
+ view 'app/assets/javascripts/branches/components/branch_more_actions.vue' do
element :delete_branch_button
end
diff --git a/spec/controllers/every_controller_spec.rb b/spec/controllers/every_controller_spec.rb
index b76da85ad72..76de522e5be 100644
--- a/spec/controllers/every_controller_spec.rb
+++ b/spec/controllers/every_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe "Every controller" do
+RSpec.describe "Every controller", feature_category: :scalability do
context "feature categories" do
let_it_be(:feature_categories) do
Gitlab::FeatureCategories.default.categories.map(&:to_sym).to_set
diff --git a/spec/features/projects/branches/user_deletes_branch_spec.rb b/spec/features/projects/branches/user_deletes_branch_spec.rb
index d120ea36a55..7e7ab4b2a47 100644
--- a/spec/features/projects/branches/user_deletes_branch_spec.rb
+++ b/spec/features/projects/branches/user_deletes_branch_spec.rb
@@ -23,7 +23,8 @@ RSpec.describe "User deletes branch", :js, feature_category: :groups_and_project
branch_search.native.send_keys(:enter)
page.within(".js-branch-improve\\/awesome") do
- find('.js-delete-branch-button').click
+ click_button 'More actions'
+ find('[data-testid="delete-branch-button"]').click
end
accept_gl_confirm(button_text: 'Yes, delete branch')
diff --git a/spec/features/projects/branches/user_views_branches_spec.rb b/spec/features/projects/branches/user_views_branches_spec.rb
index 322e1fa0ac1..52327cc6543 100644
--- a/spec/features/projects/branches/user_views_branches_spec.rb
+++ b/spec/features/projects/branches/user_views_branches_spec.rb
@@ -10,22 +10,41 @@ RSpec.describe "User views branches", :js, feature_category: :groups_and_project
sign_in(user)
end
- context "all branches", :js do
+ context "all branches" do
before do
visit(project_branches_path(project))
- branch_search = find('input[data-testid="branch-search"]')
- branch_search.set('master')
- branch_search.native.send_keys(:enter)
end
- it "shows branches" do
- expect(page).to have_content("Branches").and have_content("master")
+ describe 'default branch' do
+ before do
+ search_branches('master')
+ end
- expect(page.all(".graph-side")).to all(have_content(/\d+/))
+ it "shows the default branch" do
+ expect(page).to have_content("Branches").and have_content("master")
+
+ expect(page.all(".graph-side")).to all(have_content(/\d+/))
+ end
+
+ it "does not show the \"More actions\" dropdown" do
+ expect(page).not_to have_selector('[data-testid="branch-more-actions"]')
+ end
end
- it "displays a disabled button with a tooltip for the default branch that cannot be deleted", :js do
- expect(page).to have_button('The default branch cannot be deleted', disabled: true)
+ describe 'non-default branch' do
+ before do
+ search_branches('feature')
+ end
+
+ it "shows the branches" do
+ expect(page).to have_content("Branches").and have_content("feature")
+
+ expect(page.all(".graph-side")).to all(have_content(/\d+/))
+ end
+
+ it "shows the \"More actions\" dropdown" do
+ expect(page).to have_button('More actions')
+ end
end
end
@@ -42,4 +61,10 @@ RSpec.describe "User views branches", :js, feature_category: :groups_and_project
end
end
end
+
+ def search_branches(query)
+ branch_search = find('input[data-testid="branch-search"]')
+ branch_search.set(query)
+ branch_search.native.send_keys(:enter)
+ end
end
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index 7cfb7240db6..6a13d5637af 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -231,7 +231,7 @@ RSpec.describe 'Branches', feature_category: :groups_and_projects do
visit project_branches_path(project)
page.within first('.all-branches li') do
- expect(page).to have_content 'Merge request'
+ expect(page).to have_content 'New'
end
end
@@ -242,7 +242,7 @@ RSpec.describe 'Branches', feature_category: :groups_and_projects do
visit project_branches_path(project)
page.within first('.all-branches li') do
- expect(page).not_to have_content 'Merge request'
+ expect(page).not_to have_content 'New'
end
end
@@ -266,7 +266,7 @@ RSpec.describe 'Branches', feature_category: :groups_and_projects do
it 'does not show merge request button' do
page.within first('.all-branches li') do
- expect(page).not_to have_content 'Merge request'
+ expect(page).not_to have_content 'New'
end
end
end
@@ -294,7 +294,7 @@ RSpec.describe 'Branches', feature_category: :groups_and_projects do
it 'displays a placeholder when not available' do
page.all('.all-branches li') do |li|
- expect(li).to have_css 'svg.s24'
+ expect(li).to have_css '.pipeline-status svg.s16'
end
end
end
@@ -306,7 +306,7 @@ RSpec.describe 'Branches', feature_category: :groups_and_projects do
it 'does not show placeholder or pipeline status' do
page.all('.all-branches') do |branches|
- expect(branches).not_to have_css 'svg.s24'
+ expect(branches).not_to have_css '.pipeline-status svg.s16'
end
end
end
@@ -322,6 +322,8 @@ RSpec.describe 'Branches', feature_category: :groups_and_projects do
visit project_branches_path(project)
page.within first('.all-branches li') do
+ wait_for_requests
+ find('[data-testid="branch-more-actions"] .gl-new-dropdown-toggle').click
click_link 'Compare'
end
@@ -329,7 +331,7 @@ RSpec.describe 'Branches', feature_category: :groups_and_projects do
end
end
- context 'on a read-only instance' do
+ context 'on a read-only instance', :js do
before do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
end
@@ -337,7 +339,7 @@ RSpec.describe 'Branches', feature_category: :groups_and_projects do
it_behaves_like 'compares branches'
end
- context 'on a read-write instance' do
+ context 'on a read-write instance', :js do
it_behaves_like 'compares branches'
end
end
@@ -364,7 +366,9 @@ RSpec.describe 'Branches', feature_category: :groups_and_projects do
end
def delete_branch_and_confirm
- find('.js-delete-branch-button', match: :first).click
+ wait_for_requests
+ find('[data-testid="branch-more-actions"] .gl-new-dropdown-toggle', match: :first).click
+ find('[data-testid="delete-branch-button"]').click
within '.modal-footer' do
click_button 'Yes, delete branch'
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 346268af972..0f903901984 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -479,7 +479,10 @@ RSpec.describe 'Environment', feature_category: :groups_and_projects do
visit project_branches_filtered_path(project, state: 'all', search: 'feature')
remove_branch_with_hooks(project, user, 'feature') do
- page.within('.js-branch-feature') { find('.js-delete-branch-button').click }
+ page.within('.js-branch-feature') do
+ find('[data-testid="branch-more-actions"] .gl-new-dropdown-toggle').click
+ find('[data-testid="delete-branch-button"]').click
+ end
end
visit_environment(environment)
diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb
index e5c927af96c..6d6d850342a 100644
--- a/spec/features/projects/merge_request_button_spec.rb
+++ b/spec/features/projects/merge_request_button_spec.rb
@@ -50,10 +50,17 @@ RSpec.describe 'Merge Request button', feature_category: :groups_and_projects do
end
it 'does not show Create merge request button' do
+ href = project_new_merge_request_path(
+ project,
+ merge_request: {
+ source_branch: 'feature'
+ }.merge(extra_mr_params)
+ )
+
visit url
within('#content-body') do
- expect(page).not_to have_link(label)
+ expect(page).not_to have_link(label, href: href)
end
end
end
@@ -105,7 +112,7 @@ RSpec.describe 'Merge Request button', feature_category: :groups_and_projects do
context 'on branches page' do
it_behaves_like 'Merge request button only shown when allowed' do
- let(:label) { 'Merge request' }
+ let(:label) { 'New' }
let(:url) { project_branches_filtered_path(project, state: 'all', search: 'feature') }
let(:fork_url) { project_branches_filtered_path(forked_project, state: 'all', search: 'feature') }
end
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index e4a64d391b0..9244cafbc0b 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem
find('input[data-testid="branch-search"]').set('fix')
find('input[data-testid="branch-search"]').native.send_keys(:enter)
- expect(page).to have_button('Only a project maintainer or owner can delete a protected branch', disabled: true)
+ expect(page).not_to have_button('Delete protected branch')
end
end
end
@@ -64,9 +64,11 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem
expect(page).to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 1)
+ find('[data-testid="branch-more-actions"] button').click
+ wait_for_requests
expect(page).to have_button('Delete protected branch', disabled: false)
- page.find('.js-delete-branch-button').click
+ find('[data-testid="delete-branch-button"]').click
fill_in 'delete_branch_input', with: 'fix'
click_button 'Yes, delete protected branch'
diff --git a/spec/frontend/branches/components/__snapshots__/delete_merged_branches_spec.js.snap b/spec/frontend/branches/components/__snapshots__/delete_merged_branches_spec.js.snap
index 300b6f4a39a..0254f814827 100644
--- a/spec/frontend/branches/components/__snapshots__/delete_merged_branches_spec.js.snap
+++ b/spec/frontend/branches/components/__snapshots__/delete_merged_branches_spec.js.snap
@@ -4,7 +4,7 @@ exports[`Delete merged branches component Delete merged branches confirmation mo
<div>
<gl-base-dropdown-stub
category="tertiary"
- class="gl-disclosure-dropdown"
+ class="gl-disclosure-dropdown gl-display-none gl-md-display-block!"
data-qa-selector="delete_merged_branches_dropdown_button"
icon="ellipsis_v"
nocaret="true"
@@ -31,6 +31,27 @@ exports[`Delete merged branches component Delete merged branches confirmation mo
</gl-base-dropdown-stub>
+ <b-button-stub
+ class="gl-display-block gl-md-display-none! gl-button btn-danger-secondary"
+ data-qa-selector="delete_merged_branches_button"
+ size="md"
+ tag="button"
+ type="button"
+ variant="danger"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+
+ Delete merged branches
+
+ </span>
+ </b-button-stub>
+
<div>
<form
action="/namespace/project/-/merged_branches"
diff --git a/spec/frontend/branches/components/branch_more_actions_spec.js b/spec/frontend/branches/components/branch_more_actions_spec.js
new file mode 100644
index 00000000000..32b850a62a0
--- /dev/null
+++ b/spec/frontend/branches/components/branch_more_actions_spec.js
@@ -0,0 +1,70 @@
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import BranchMoreDropdown from '~/branches/components/branch_more_actions.vue';
+import eventHub from '~/branches/event_hub';
+
+describe('Delete branch button', () => {
+ let wrapper;
+ let eventHubSpy;
+
+ const findCompareButton = () => wrapper.findByTestId('compare-branch-button');
+ const findDeleteButton = () => wrapper.findByTestId('delete-branch-button');
+
+ const createComponent = (props = {}) => {
+ wrapper = mountExtended(BranchMoreDropdown, {
+ propsData: {
+ branchName: 'test',
+ defaultBranchName: 'main',
+ canDeleteBranch: true,
+ isProtectedBranch: false,
+ merged: false,
+ comparePath: '/path/to/branch',
+ deletePath: '/path/to/branch',
+ ...props,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ eventHubSpy = jest.spyOn(eventHub, '$emit');
+ });
+
+ it('renders the compare action', () => {
+ createComponent();
+
+ expect(findCompareButton().exists()).toBe(true);
+ expect(findCompareButton().text()).toBe('Compare');
+ });
+
+ it('renders the delete action', () => {
+ createComponent();
+
+ expect(findDeleteButton().exists()).toBe(true);
+ expect(findDeleteButton().text()).toBe('Delete branch');
+ });
+
+ it('renders a different text for a protected branch', () => {
+ createComponent({ isProtectedBranch: true });
+
+ expect(findDeleteButton().text()).toBe('Delete protected branch');
+ });
+
+ it('emits the data to eventHub when button is clicked', async () => {
+ createComponent({ merged: true });
+
+ await findDeleteButton().trigger('click');
+
+ expect(eventHubSpy).toHaveBeenCalledWith('openModal', {
+ branchName: 'test',
+ defaultBranchName: 'main',
+ deletePath: '/path/to/branch',
+ isProtectedBranch: false,
+ merged: true,
+ });
+ });
+
+ it('doesn`t render the delete action when user cannot delete branch', () => {
+ createComponent({ canDeleteBranch: false });
+
+ expect(findDeleteButton().exists()).toBe(false);
+ });
+});
diff --git a/spec/frontend/branches/components/delete_branch_button_spec.js b/spec/frontend/branches/components/delete_branch_button_spec.js
deleted file mode 100644
index 5b2ec443c59..00000000000
--- a/spec/frontend/branches/components/delete_branch_button_spec.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import { GlButton } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import DeleteBranchButton from '~/branches/components/delete_branch_button.vue';
-import eventHub from '~/branches/event_hub';
-
-let wrapper;
-let findDeleteButton;
-
-const createComponent = (props = {}) => {
- wrapper = shallowMount(DeleteBranchButton, {
- propsData: {
- branchName: 'test',
- deletePath: '/path/to/branch',
- defaultBranchName: 'main',
- ...props,
- },
- });
-};
-
-describe('Delete branch button', () => {
- let eventHubSpy;
-
- beforeEach(() => {
- findDeleteButton = () => wrapper.findComponent(GlButton);
- eventHubSpy = jest.spyOn(eventHub, '$emit');
- });
-
- it('renders the button with default tooltip, style, and icon', () => {
- createComponent();
-
- expect(findDeleteButton().attributes()).toMatchObject({
- title: 'Delete branch',
- variant: 'default',
- icon: 'remove',
- });
- });
-
- it('renders a different tooltip for a protected branch', () => {
- createComponent({ isProtectedBranch: true });
-
- expect(findDeleteButton().attributes()).toMatchObject({
- title: 'Delete protected branch',
- variant: 'default',
- icon: 'remove',
- });
- });
-
- it('renders a different protected tooltip when it is both protected and disabled', () => {
- createComponent({ isProtectedBranch: true, disabled: true });
-
- expect(findDeleteButton().attributes()).toMatchObject({
- title: 'Only a project maintainer or owner can delete a protected branch',
- variant: 'default',
- });
- });
-
- it('emits the data to eventHub when button is clicked', () => {
- createComponent({ merged: true });
-
- findDeleteButton().vm.$emit('click');
-
- expect(eventHubSpy).toHaveBeenCalledWith('openModal', {
- branchName: 'test',
- defaultBranchName: 'main',
- deletePath: '/path/to/branch',
- isProtectedBranch: false,
- merged: true,
- });
- });
-
- describe('#disabled', () => {
- it('does not disable the button by default when mounted', () => {
- createComponent();
-
- expect(findDeleteButton().attributes()).toMatchObject({
- title: 'Delete branch',
- variant: 'default',
- });
- });
-
- // Used for unallowed users and for the default branch.
- it('disables the button when mounted for a disabled modal', () => {
- createComponent({ disabled: true, tooltip: 'The default branch cannot be deleted' });
-
- expect(findDeleteButton().attributes()).toMatchObject({
- title: 'The default branch cannot be deleted',
- disabled: 'true',
- variant: 'default',
- });
- });
- });
-});
diff --git a/spec/graphql/resolvers/releases_resolver_spec.rb b/spec/graphql/resolvers/releases_resolver_spec.rb
index 1e9608ab780..58f6257c946 100644
--- a/spec/graphql/resolvers/releases_resolver_spec.rb
+++ b/spec/graphql/resolvers/releases_resolver_spec.rb
@@ -54,9 +54,7 @@ RSpec.describe Resolvers::ReleasesResolver, feature_category: :release_orchestra
private
def resolve_releases
- batch_sync do
- context = { current_user: current_user }
- resolve(described_class, obj: project, args: args, ctx: context, arg_style: :internal)
- end
+ context = { current_user: current_user }
+ resolve(described_class, obj: project, args: args, ctx: context, arg_style: :internal)
end
end
diff --git a/spec/graphql/types/ci/catalog/resource_type_spec.rb b/spec/graphql/types/ci/catalog/resource_type_spec.rb
deleted file mode 100644
index dee790a0c93..00000000000
--- a/spec/graphql/types/ci/catalog/resource_type_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Types::Ci::Catalog::ResourceType, feature_category: :pipeline_composition do
- specify { expect(described_class.graphql_name).to eq('CiCatalogResource') }
-
- it 'exposes the expected fields' do
- expected_fields = %i[
- id
- name
- description
- icon
- web_path
- versions
- star_count
- forks_count
- root_namespace
- readme_html
- ]
-
- expect(described_class).to have_graphql_fields(*expected_fields)
- end
-end
diff --git a/spec/helpers/branches_helper_spec.rb b/spec/helpers/branches_helper_spec.rb
index 2ad15adff59..33756867653 100644
--- a/spec/helpers/branches_helper_spec.rb
+++ b/spec/helpers/branches_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe BranchesHelper do
+RSpec.describe BranchesHelper, feature_category: :source_code_management do
describe '#access_levels_data' do
subject { helper.access_levels_data(access_levels) }
@@ -47,4 +47,53 @@ RSpec.describe BranchesHelper do
end
end
end
+
+ describe '#merge_request_status' do
+ subject { helper.merge_request_status(merge_request) }
+
+ let(:merge_request) { build(:merge_request, title: title) }
+ let(:title) { 'Test MR' }
+
+ context 'when merge request is missing' do
+ let(:merge_request) { nil }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when merge request is closed' do
+ before do
+ merge_request.close
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when merge request is open' do
+ it { is_expected.to eq(icon: 'merge-request-open', title: "Open - #{title}", variant: :success) }
+ end
+
+ context 'when merge request is locked' do
+ let(:merge_request) { build(:merge_request, :locked, title: title) }
+
+ it { is_expected.to eq(icon: 'merge-request-open', title: "Open - #{title}", variant: :success) }
+ end
+
+ context 'when merge request is draft' do
+ let(:title) { 'Draft: Test MR' }
+
+ it { is_expected.to eq(icon: 'merge-request-open', title: "Open - #{title}", variant: :warning) }
+ end
+
+ context 'when merge request is merged' do
+ let(:merge_request) { build(:merge_request, :merged, title: title) }
+
+ it { is_expected.to eq(icon: 'merge', title: "Merged - #{title}", variant: :info) }
+ end
+
+ context 'when merge request status is unsupported' do
+ let(:merge_request) { build(:merge_request, state_id: -1) }
+
+ it { is_expected.to be_nil }
+ end
+ end
end
diff --git a/spec/lib/api/every_api_endpoint_spec.rb b/spec/lib/api/every_api_endpoint_spec.rb
index c45ff9eb628..f01fe5a2c9a 100644
--- a/spec/lib/api/every_api_endpoint_spec.rb
+++ b/spec/lib/api/every_api_endpoint_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'Every API endpoint' do
+RSpec.describe 'Every API endpoint', feature_category: :scalability do
context 'feature categories' do
let_it_be(:feature_categories) do
Gitlab::FeatureCategories.default.categories.map(&:to_sym).to_set
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index a5e68829c5d..f0ea8db9e81 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -180,6 +180,26 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
end
end
+ describe '.by_sorted_source_branches' do
+ let(:fork_for_project) { fork_project(project) }
+
+ let!(:merge_request_to_master) { create(:merge_request, :closed, target_project: project, source_branch: 'a-feature') }
+ let!(:merge_request_to_other_branch) { create(:merge_request, target_project: project, source_branch: 'b-feature') }
+ let!(:merge_request_to_master2) { create(:merge_request, target_project: project, source_branch: 'a-feature') }
+ let!(:merge_request_from_fork_to_master) { create(:merge_request, source_project: fork_for_project, target_project: project, source_branch: 'b-feature') }
+
+ it 'returns merge requests sorted by name and id' do
+ expect(described_class.by_sorted_source_branches(%w[a-feature b-feature non-existing-feature])).to eq(
+ [
+ merge_request_to_master2,
+ merge_request_to_master,
+ merge_request_from_fork_to_master,
+ merge_request_to_other_branch
+ ]
+ )
+ 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) }
diff --git a/spec/support/shared_examples/requests/api/graphql/releases_and_group_releases_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/releases_and_group_releases_shared_examples.rb
index b40cf6daea9..fd7a530fcd6 100644
--- a/spec/support/shared_examples/requests/api/graphql/releases_and_group_releases_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/releases_and_group_releases_shared_examples.rb
@@ -14,6 +14,20 @@ RSpec.shared_examples 'correct total count' do
end
end
+RSpec.shared_examples 'when there are no releases' do
+ let(:data) { graphql_data.dig(resource_type.to_s, 'releases') }
+
+ before do
+ project.releases.delete_all
+
+ post_query
+ end
+
+ it 'returns an empty array' do
+ expect(data['nodes']).to eq([])
+ end
+end
+
RSpec.shared_examples 'full access to all repository-related fields' do
describe 'repository-related fields' do
before do
@@ -57,6 +71,7 @@ RSpec.shared_examples 'full access to all repository-related fields' do
end
it_behaves_like 'correct total count'
+ it_behaves_like 'when there are no releases'
end
RSpec.shared_examples 'no access to any repository-related fields' do
diff --git a/spec/views/projects/branches/index.html.haml_spec.rb b/spec/views/projects/branches/index.html.haml_spec.rb
index 9954d9ecaec..b2b96866904 100644
--- a/spec/views/projects/branches/index.html.haml_spec.rb
+++ b/spec/views/projects/branches/index.html.haml_spec.rb
@@ -16,6 +16,7 @@ RSpec.describe 'projects/branches/index.html.haml' do
assign(:mode, 'overview')
assign(:active_branches, [active_branch])
assign(:stale_branches, [stale_branch])
+ assign(:related_merge_requests, {})
assign(:overview_max_branches, 5)
assign(:branch_pipeline_statuses, {})
assign(:refs_pipelines, {})