summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-01-21 00:11:07 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-01-21 00:11:07 +0000
commit9f021745a60369298705393103f621ff73a2b286 (patch)
treea6e9f85a8f35245e4ccef5f532c6ca6857f08d9e
parentef8c47e97e1c178291e4857314a3f53875d75062 (diff)
downloadgitlab-ce-9f021745a60369298705393103f621ff73a2b286.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml8
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/design_management/components/list/item.vue2
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js14
-rw-r--r--app/assets/javascripts/search/topbar/components/project_filter.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestions.vue12
-rw-r--r--app/assets/stylesheets/components/design_management/design_list_item.scss5
-rw-r--r--app/controllers/concerns/integrations_actions.rb4
-rw-r--r--app/finders/terraform/states_finder.rb28
-rw-r--r--app/graphql/mutations/security/ci_configuration/configure_sast.rb50
-rw-r--r--app/graphql/resolvers/terraform/states_resolver.rb18
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/graphql/types/project_type.rb8
-rw-r--r--app/helpers/groups_helper.rb23
-rw-r--r--app/models/terraform/state.rb1
-rw-r--r--app/services/groups/open_issues_count_service.rb64
-rw-r--r--app/services/security/ci_configuration/sast_create_service.rb66
-rw-r--r--app/views/groups/_import_group_from_another_instance_panel.html.haml13
-rw-r--r--app/views/groups/runners/_runner.html.haml5
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml6
-rw-r--r--app/views/projects/branches/_branch.html.haml16
-rw-r--r--app/views/projects/buttons/_fork.html.haml10
-rw-r--r--app/views/projects/notes/_actions.html.haml14
-rw-r--r--app/views/projects/notes/_more_actions_dropdown.html.haml7
-rw-r--r--app/views/projects/pages/_use.html.haml7
-rw-r--r--app/views/shared/access_tokens/_form.html.haml2
-rw-r--r--app/views/shared/access_tokens/_table.html.haml2
-rw-r--r--changelogs/unreleased/231204-projects-notes.yml5
-rw-r--r--changelogs/unreleased/290231-warning-message-for-group-migration.yml5
-rw-r--r--changelogs/unreleased/291140-query-single-state.yml5
-rw-r--r--changelogs/unreleased/293477-update-pages-help-text.yml5
-rw-r--r--changelogs/unreleased/299213-project-filter-regression.yml5
-rw-r--r--changelogs/unreleased/id-bump-doorkeeper-to-5-4-0.yml5
-rw-r--r--changelogs/unreleased/move_mutations_to_ce_for_sast_config.yml5
-rw-r--r--changelogs/unreleased/ph-235741-fixSuggestionsUpdatingIncorrectly.yml5
-rw-r--r--changelogs/unreleased/yo-gl-button-revoke-button.yml5
-rw-r--r--config/feature_flags/development/cached_sidebar_open_issues_count.yml8
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql12
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json29
-rw-r--r--doc/api/graphql/reference/index.md3
-rw-r--r--doc/api/groups.md13
-rw-r--r--doc/ci/README.md2
-rw-r--r--doc/ci/examples/README.md22
-rw-r--r--doc/ci/examples/artifactory_and_gitlab/index.md289
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.pngbin11852 -> 0 bytes
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/create_from_template.pngbin82121 -> 0 bytes
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md145
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/aws_config_window.pngbin9300 -> 0 bytes
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/gitlab_config.pngbin15160 -> 0 bytes
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/test_pipeline_pass.pngbin9985 -> 0 bytes
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md534
-rw-r--r--doc/ci/examples/test-phoenix-application.md3
-rw-r--r--doc/ci/examples/test-scala-application.md81
-rw-r--r--doc/ci/yaml/README.md21
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/directory_structure.md36
-rw-r--r--doc/development/usage_ping.md71
-rw-r--r--doc/user/packages/maven_repository/index.md33
-rw-r--r--doc/user/project/issues/index.md38
-rw-r--r--lib/gitlab/experimentation/controller_concern.rb2
-rw-r--r--lib/tasks/benchmark.rake11
-rw-r--r--lib/tasks/gitlab/pages.rake13
-rw-r--r--locale/gitlab.pot12
-rw-r--r--package.json2
-rw-r--r--qa/Dockerfile4
-rw-r--r--spec/benchmarks/banzai_benchmark.rb114
-rw-r--r--spec/features/groups/import_export/connect_instance_spec.rb1
-rw-r--r--spec/finders/terraform/states_finder_spec.rb45
-rw-r--r--spec/frontend/notes/stores/mutation_spec.js13
-rw-r--r--spec/frontend/search/mock_data.js6
-rw-r--r--spec/frontend/search/topbar/components/project_filter_spec.js2
-rw-r--r--spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb120
-rw-r--r--spec/graphql/resolvers/terraform/states_resolver_spec.rb20
-rw-r--r--spec/graphql/types/project_type_spec.rb7
-rw-r--r--spec/helpers/groups_helper_spec.rb78
-rw-r--r--spec/helpers/invite_members_helper_spec.rb5
-rw-r--r--spec/models/terraform/state_spec.rb9
-rw-r--r--spec/requests/api/graphql/project/terraform/state_spec.rb83
-rw-r--r--spec/requests/api/groups_spec.rb7
-rw-r--r--spec/services/groups/open_issues_count_service_spec.rb106
-rw-r--r--spec/services/security/ci_configuration/sast_create_service_spec.rb69
-rw-r--r--[-rwxr-xr-x]vendor/gitignore/C++.gitignore0
-rw-r--r--[-rwxr-xr-x]vendor/gitignore/Java.gitignore0
-rw-r--r--yarn.lock8
86 files changed, 1349 insertions, 1170 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 38426bae943..15bec7530ee 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -17,7 +17,7 @@ stages:
# in cases where jobs require Docker-in-Docker, the job
# definition must be extended with `.use-docker-in-docker`
default:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
tags:
- gitlab-org
# All jobs are interruptible by default
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 355607c17ac..45bff293cd4 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -71,7 +71,7 @@
policy: pull
.use-pg11:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
services:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -80,7 +80,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg12:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34"
services:
- name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -89,7 +89,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11-ee:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
services:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -100,7 +100,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg12-ee:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34"
services:
- name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
diff --git a/Gemfile b/Gemfile
index a2aff0f4b1e..949802ec510 100644
--- a/Gemfile
+++ b/Gemfile
@@ -25,7 +25,7 @@ gem 'marginalia', '~> 1.10.0'
gem 'devise', '~> 4.7.2'
# TODO: verify ARM compile issue on 3.1.13+ version (see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18828)
gem 'bcrypt', '3.1.12'
-gem 'doorkeeper', '~> 5.3.0'
+gem 'doorkeeper', '~> 5.4.0'
gem 'doorkeeper-openid_connect', '~> 1.7.4'
gem 'omniauth', '~> 1.8'
gem 'omniauth-auth0', '~> 2.0.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 74d88417541..974bc4181f1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -260,7 +260,7 @@ GEM
docile (1.3.2)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
- doorkeeper (5.3.3)
+ doorkeeper (5.4.0)
railties (>= 5)
doorkeeper-openid_connect (1.7.4)
doorkeeper (>= 5.2, < 5.5)
@@ -1325,7 +1325,7 @@ DEPENDENCIES
diff_match_patch (~> 0.1.0)
diffy (~> 3.3)
discordrb-webhooks-blackst0ne (~> 3.3)
- doorkeeper (~> 5.3.0)
+ doorkeeper (~> 5.4.0)
doorkeeper-openid_connect (~> 1.7.4)
ed25519 (~> 1.2)
elasticsearch-api (~> 6.8.2)
diff --git a/app/assets/javascripts/design_management/components/list/item.vue b/app/assets/javascripts/design_management/components/list/item.vue
index 5402fed0a9a..67110265b5f 100644
--- a/app/assets/javascripts/design_management/components/list/item.vue
+++ b/app/assets/javascripts/design_management/components/list/item.vue
@@ -133,7 +133,7 @@ export default {
<div
class="card-body gl-p-0 gl-display-flex gl-align-items-center gl-justify-content-center gl-overflow-hidden gl-relative"
>
- <div v-if="icon.name" data-testid="design-event" class="design-event gl-absolute">
+ <div v-if="icon.name" data-testid="design-event" class="gl-top-5 gl-right-5 gl-absolute">
<span :title="icon.tooltip" :aria-label="icon.tooltip">
<gl-icon
:name="icon.name"
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 2c51ce0d970..1a3c15bedb8 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -1,3 +1,4 @@
+import { isEqual } from 'lodash';
import * as utils from './utils';
import * as types from './mutation_types';
import * as constants from '../constants';
@@ -31,7 +32,8 @@ export default {
}
}
- note.base_discussion = undefined; // No point keeping a reference to this
+ // note.base_discussion = undefined; // No point keeping a reference to this
+ delete note.base_discussion;
discussion.notes = [note];
state.discussions.push(discussion);
@@ -220,6 +222,11 @@ export default {
[types.UPDATE_NOTE](state, note) {
const noteObj = utils.findNoteObjectById(state.discussions, note.discussion_id);
+ // Disable eslint here so we can delete the property that we no longer need
+ // in the note object
+ // eslint-disable-next-line no-param-reassign
+ delete note.base_discussion;
+
if (noteObj.individual_note) {
if (note.type === constants.DISCUSSION_NOTE) {
noteObj.individual_note = false;
@@ -228,7 +235,10 @@ export default {
noteObj.notes.splice(0, 1, note);
} else {
const comment = utils.findNoteObjectById(noteObj.notes, note.id);
- noteObj.notes.splice(noteObj.notes.indexOf(comment), 1, note);
+
+ if (!isEqual(comment, note)) {
+ noteObj.notes.splice(noteObj.notes.indexOf(comment), 1, note);
+ }
}
},
diff --git a/app/assets/javascripts/search/topbar/components/project_filter.vue b/app/assets/javascripts/search/topbar/components/project_filter.vue
index 3f1f3848ac7..effaafb72c1 100644
--- a/app/assets/javascripts/search/topbar/components/project_filter.vue
+++ b/app/assets/javascripts/search/topbar/components/project_filter.vue
@@ -27,7 +27,7 @@ export default {
handleProjectChange(project) {
// This determines if we need to update the group filter or not
const queryParams = {
- ...(project.namespace_id && { [GROUP_DATA.queryParam]: project.namespace_id }),
+ ...(project.namespace?.id && { [GROUP_DATA.queryParam]: project.namespace.id }),
[PROJECT_DATA.queryParam]: project.id,
};
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
index 5ee51764555..94a216596a8 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -64,6 +64,11 @@ export default {
mounted() {
this.renderSuggestions();
},
+ beforeDestroy() {
+ if (this.suggestionsWatch) {
+ this.suggestionsWatch();
+ }
+ },
methods: {
renderSuggestions() {
// swaps out suggestion(s) markdown with rich diff components
@@ -108,6 +113,13 @@ export default {
},
});
+ // We're using `$watch` as `suggestionsCount` updates do not
+ // propagate to this component for some unknown reason while
+ // using a traditional prop watcher.
+ this.suggestionsWatch = this.$watch('suggestionsCount', () => {
+ suggestionDiff.suggestionsCount = this.suggestionsCount;
+ });
+
suggestionDiff.$on('apply', ({ suggestionId, callback, message }) => {
this.$emit('apply', { suggestionId, callback, flashContainer: this.$el, message });
});
diff --git a/app/assets/stylesheets/components/design_management/design_list_item.scss b/app/assets/stylesheets/components/design_management/design_list_item.scss
index b7f6b2026fe..09af4da37e9 100644
--- a/app/assets/stylesheets/components/design_management/design_list_item.scss
+++ b/app/assets/stylesheets/components/design_management/design_list_item.scss
@@ -8,11 +8,6 @@
top: 10px;
}
- .design-event {
- top: $gl-padding;
- right: $gl-padding;
- }
-
.card-body {
height: 230px;
}
diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb
index baebedb8e5d..a3ea39d9c3d 100644
--- a/app/controllers/concerns/integrations_actions.rb
+++ b/app/controllers/concerns/integrations_actions.rb
@@ -34,10 +34,6 @@ module IntegrationsActions
end
end
- def custom_integration_projects
- Project.with_custom_integration_compared_to(integration).page(params[:page]).per(20)
- end
-
def test
render json: {}, status: :ok
end
diff --git a/app/finders/terraform/states_finder.rb b/app/finders/terraform/states_finder.rb
new file mode 100644
index 00000000000..bbe90fead2b
--- /dev/null
+++ b/app/finders/terraform/states_finder.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Terraform
+ class StatesFinder
+ def initialize(project, current_user, params: {})
+ @project = project
+ @current_user = current_user
+ @params = params
+ end
+
+ def execute
+ return ::Terraform::State.none unless can_read_terraform_states?
+
+ states = project.terraform_states
+ states = states.with_name(params[:name]) if params[:name].present?
+
+ states.ordered_by_name
+ end
+
+ private
+
+ attr_reader :project, :current_user, :params
+
+ def can_read_terraform_states?
+ current_user.can?(:read_terraform_state, project)
+ end
+ end
+end
diff --git a/app/graphql/mutations/security/ci_configuration/configure_sast.rb b/app/graphql/mutations/security/ci_configuration/configure_sast.rb
new file mode 100644
index 00000000000..6cb3704a19b
--- /dev/null
+++ b/app/graphql/mutations/security/ci_configuration/configure_sast.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Security
+ module CiConfiguration
+ class ConfigureSast < BaseMutation
+ include ResolvesProject
+
+ graphql_name 'ConfigureSast'
+
+ argument :project_path, GraphQL::ID_TYPE,
+ required: true,
+ description: 'Full path of the project.'
+
+ argument :configuration, ::Types::CiConfiguration::Sast::InputType,
+ required: true,
+ description: 'SAST CI configuration for the project.'
+
+ field :status, GraphQL::STRING_TYPE, null: false,
+ description: 'Status of creating the commit for the supplied SAST CI configuration.'
+
+ field :success_path, GraphQL::STRING_TYPE, null: true,
+ description: 'Redirect path to use when the response is successful.'
+
+ authorize :push_code
+
+ def resolve(project_path:, configuration:)
+ project = authorized_find!(full_path: project_path)
+
+ result = ::Security::CiConfiguration::SastCreateService.new(project, current_user, configuration).execute
+ prepare_response(result)
+ end
+
+ private
+
+ def find_object(full_path:)
+ resolve_project(full_path: full_path)
+ end
+
+ def prepare_response(result)
+ {
+ status: result[:status],
+ success_path: result[:success_path],
+ errors: Array(result[:errors])
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/terraform/states_resolver.rb b/app/graphql/resolvers/terraform/states_resolver.rb
index 38b26a948b1..f543eb182e8 100644
--- a/app/graphql/resolvers/terraform/states_resolver.rb
+++ b/app/graphql/resolvers/terraform/states_resolver.rb
@@ -3,20 +3,20 @@
module Resolvers
module Terraform
class StatesResolver < BaseResolver
- type Types::Terraform::StateType, null: true
+ type Types::Terraform::StateType.connection_type, null: true
alias_method :project, :object
- def resolve(**args)
- return ::Terraform::State.none unless can_read_terraform_states?
-
- project.terraform_states.ordered_by_name
+ when_single do
+ argument :name, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Name of the Terraform state.'
end
- private
-
- def can_read_terraform_states?
- current_user.can?(:read_terraform_state, project)
+ def resolve(**args)
+ ::Terraform::StatesFinder
+ .new(project, current_user, params: args)
+ .execute
end
end
end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index f9dd11cbe37..11980e009f4 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -15,6 +15,7 @@ module Types
mount_mutation Mutations::AlertManagement::HttpIntegration::Update
mount_mutation Mutations::AlertManagement::HttpIntegration::ResetToken
mount_mutation Mutations::AlertManagement::HttpIntegration::Destroy
+ mount_mutation Mutations::Security::CiConfiguration::ConfigureSast
mount_mutation Mutations::AlertManagement::PrometheusIntegration::Create
mount_mutation Mutations::AlertManagement::PrometheusIntegration::Update
mount_mutation Mutations::AlertManagement::PrometheusIntegration::ResetToken
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 41a4edd20a5..257c77e2ede 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -309,10 +309,16 @@ module Types
description: 'Title of the label'
end
+ field :terraform_state,
+ Types::Terraform::StateType,
+ null: true,
+ description: 'Find a single Terraform state by name.',
+ resolver: Resolvers::Terraform::StatesResolver.single
+
field :terraform_states,
Types::Terraform::StateType.connection_type,
null: true,
- description: 'Terraform states associated with the project',
+ description: 'Terraform states associated with the project.',
resolver: Resolvers::Terraform::StatesResolver
field :pipeline_analytics, Types::Ci::AnalyticsType, null: true,
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 133d9d21a14..eeeffb7b3ae 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -62,6 +62,14 @@ module GroupsHelper
can?(current_user, :set_emails_disabled, group) && !group.parent&.emails_disabled?
end
+ def group_open_issues_count(group)
+ if Feature.enabled?(:cached_sidebar_open_issues_count, group)
+ cached_open_group_issues_count(group)
+ else
+ number_with_delimiter(group_issues_count(state: 'opened'))
+ end
+ end
+
def group_issues_count(state:)
IssuesFinder
.new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true)
@@ -69,6 +77,21 @@ module GroupsHelper
.count
end
+ def cached_open_group_issues_count(group)
+ count_service = Groups::OpenIssuesCountService
+ issues_count = count_service.new(group, current_user).count
+
+ if issues_count > count_service::CACHED_COUNT_THRESHOLD
+ ActiveSupport::NumberHelper
+ .number_to_human(
+ issues_count,
+ units: { thousand: 'k', million: 'm' }, precision: 1, significant: false, format: '%n%u'
+ )
+ else
+ number_with_delimiter(issues_count)
+ end
+ end
+
def group_merge_requests_count(state:)
MergeRequestsFinder
.new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true)
diff --git a/app/models/terraform/state.rb b/app/models/terraform/state.rb
index efbbd86ae4a..a301c4a9689 100644
--- a/app/models/terraform/state.rb
+++ b/app/models/terraform/state.rb
@@ -22,6 +22,7 @@ module Terraform
scope :versioning_not_enabled, -> { where(versioning_enabled: false) }
scope :ordered_by_name, -> { order(:name) }
+ scope :with_name, -> (name) { where(name: name) }
validates :project_id, presence: true
validates :uuid, presence: true, uniqueness: true, length: { is: UUID_LENGTH },
diff --git a/app/services/groups/open_issues_count_service.rb b/app/services/groups/open_issues_count_service.rb
new file mode 100644
index 00000000000..db1ca09212a
--- /dev/null
+++ b/app/services/groups/open_issues_count_service.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module Groups
+ # Service class for counting and caching the number of open issues of a group.
+ class OpenIssuesCountService < BaseCountService
+ include Gitlab::Utils::StrongMemoize
+
+ VERSION = 1
+ PUBLIC_COUNT_KEY = 'group_public_open_issues_count'
+ TOTAL_COUNT_KEY = 'group_total_open_issues_count'
+ CACHED_COUNT_THRESHOLD = 1000
+ EXPIRATION_TIME = 24.hours
+
+ attr_reader :group, :user
+
+ def initialize(group, user = nil)
+ @group = group
+ @user = user
+ end
+
+ # Reads count value from cache and return it if present.
+ # If empty or expired, #uncached_count will calculate the issues count for the group and
+ # compare it with the threshold. If it is greater, it will be written to the cache and returned.
+ # If below, it will be returned without being cached.
+ # This results in only caching large counts and calculating the rest with every call to maintain
+ # accuracy.
+ def count
+ cached_count = Rails.cache.read(cache_key)
+ return cached_count unless cached_count.blank?
+
+ refreshed_count = uncached_count
+ update_cache_for_key(cache_key) { refreshed_count } if refreshed_count > CACHED_COUNT_THRESHOLD
+ refreshed_count
+ end
+
+ def cache_key(key = nil)
+ ['groups', 'open_issues_count_service', VERSION, group.id, cache_key_name]
+ end
+
+ private
+
+ def cache_options
+ super.merge({ expires_in: EXPIRATION_TIME })
+ end
+
+ def cache_key_name
+ public_only? ? PUBLIC_COUNT_KEY : TOTAL_COUNT_KEY
+ end
+
+ def public_only?
+ !user_is_at_least_reporter?
+ end
+
+ def user_is_at_least_reporter?
+ strong_memoize(:user_is_at_least_reporter) do
+ group.member?(user, Gitlab::Access::REPORTER)
+ end
+ end
+
+ def relation_for_count
+ IssuesFinder.new(user, group_id: group.id, state: 'opened', non_archived: true, include_subgroups: true, public_only: public_only?).execute
+ end
+ end
+end
diff --git a/app/services/security/ci_configuration/sast_create_service.rb b/app/services/security/ci_configuration/sast_create_service.rb
new file mode 100644
index 00000000000..8fc3b8d078c
--- /dev/null
+++ b/app/services/security/ci_configuration/sast_create_service.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Security
+ module CiConfiguration
+ class SastCreateService < ::BaseService
+ def initialize(project, current_user, params)
+ @project = project
+ @current_user = current_user
+ @params = params
+ @branch_name = @project.repository.next_branch('set-sast-config')
+ end
+
+ def execute
+ attributes_for_commit = attributes
+ result = ::Files::MultiService.new(@project, @current_user, attributes_for_commit).execute
+
+ if result[:status] == :success
+ result[:success_path] = successful_change_path
+ track_event(attributes_for_commit)
+ else
+ result[:errors] = result[:message]
+ end
+
+ result
+
+ rescue Gitlab::Git::PreReceiveError => e
+ { status: :error, errors: e.message }
+ end
+
+ private
+
+ def attributes
+ actions = Security::CiConfiguration::SastBuildActions.new(@project.auto_devops_enabled?, @params, existing_gitlab_ci_content).generate
+
+ @project.repository.add_branch(@current_user, @branch_name, @project.default_branch)
+ message = _('Set .gitlab-ci.yml to enable or configure SAST')
+
+ {
+ commit_message: message,
+ branch_name: @branch_name,
+ start_branch: @branch_name,
+ actions: actions
+ }
+ end
+
+ def existing_gitlab_ci_content
+ gitlab_ci_yml = @project.repository.gitlab_ci_yml_for(@project.repository.root_ref_sha)
+ YAML.safe_load(gitlab_ci_yml) if gitlab_ci_yml
+ end
+
+ def successful_change_path
+ description = _('Set .gitlab-ci.yml to enable or configure SAST security scanning using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings.')
+ merge_request_params = { source_branch: @branch_name, description: description }
+ Gitlab::Routing.url_helpers.project_new_merge_request_url(@project, merge_request: merge_request_params)
+ end
+
+ def track_event(attributes_for_commit)
+ action = attributes_for_commit[:actions].first
+
+ Gitlab::Tracking.event(
+ self.class.to_s, action[:action], label: action[:default_values_overwritten].to_s
+ )
+ end
+ end
+ end
+end
diff --git a/app/views/groups/_import_group_from_another_instance_panel.html.haml b/app/views/groups/_import_group_from_another_instance_panel.html.haml
index c95e7c16161..83d2e13d345 100644
--- a/app/views/groups/_import_group_from_another_instance_panel.html.haml
+++ b/app/views/groups/_import_group_from_another_instance_panel.html.haml
@@ -2,9 +2,18 @@
= form_errors(@group)
.gl-border-l-solid.gl-border-r-solid.gl-border-gray-100.gl-border-1.gl-p-5
- %h4
+ %h4.gl-display-flex
= s_('GroupsNew|Import groups from another instance of GitLab')
- %p
+ %span.badge.badge-info.badge-pill.gl-badge.md.gl-ml-3
+ = _('Beta')
+ .gl-alert.gl-alert-warning{ role: 'alert' }
+ = sprite_icon('warning', css_class: 'gl-icon s16 gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-body
+ - docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/group/import/index.md') }
+ - feedback_link_start = '<a href="https://gitlab.com/gitlab-org/gitlab/-/issues/284495" target="_blank" rel="noopener noreferrer">'.html_safe
+ - link_end = '</a>'.html_safe
+ = s_('GroupsNew|Not all related objects are migrated, as %{docs_link_start}described here%{docs_link_end}. Please %{feedback_link_start}leave feedback%{feedback_link_end} on this feature.').html_safe % { docs_link_start: docs_link_start, docs_link_end: link_end, feedback_link_start: feedback_link_start, feedback_link_end: link_end }
+ %p.gl-mt-3
= s_('GroupsNew|Provide credentials for another instance of GitLab to import your groups directly.')
.form-group.gl-display-flex.gl-flex-direction-column
= f.label :bulk_import_gitlab_url, s_('GroupsNew|GitLab source URL'), for: 'import_gitlab_url'
diff --git a/app/views/groups/runners/_runner.html.haml b/app/views/groups/runners/_runner.html.haml
index 3fc50cc86d2..80739395713 100644
--- a/app/views/groups/runners/_runner.html.haml
+++ b/app/views/groups/runners/_runner.html.haml
@@ -77,8 +77,9 @@
= link_to resume_group_runner_path(@group, runner), method: :post, class: 'btn btn-default has-tooltip', title: _('Resume'), ref: 'tooltip', aria: { label: _('Resume') }, data: { placement: 'top', container: 'body'} do
= sprite_icon('play')
- if runner.belongs_to_more_than_one_project?
- .btn-group
- .btn.btn-danger.has-tooltip{ 'aria-label' => 'Remove', 'data-container' => 'body', 'data-original-title' => _('Multi-project Runners cannot be removed'), 'data-placement' => 'top', disabled: 'disabled' }
+ - delete_runner_tooltip = _('Multi-project Runners cannot be removed')
+ .btn-group.has-tooltip{ data: { container: 'body', placement: 'top' }, title: delete_runner_tooltip }
+ .btn.btn-danger{ 'aria-label' => delete_runner_tooltip, disabled: 'disabled' }
= sprite_icon('close')
- else
.btn-group
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 5569a69222f..52d3c8d133a 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -1,4 +1,4 @@
-- issues_count = group_issues_count(state: 'opened')
+- issues_count = group_open_issues_count(@group)
- merge_requests_count = group_merge_requests_count(state: 'opened')
.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('groups_side_navigation', 'render', 'groups_side_navigation') }
@@ -54,14 +54,14 @@
= sprite_icon('issues')
%span.nav-item-name
= _('Issues')
- %span.badge.badge-pill.count= number_with_delimiter(issues_count)
+ %span.badge.badge-pill.count= issues_count
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_issues_sidebar_submenu'} }
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index', 'iterations#index'], html_options: { class: "fly-out-top-item" } ) do
= link_to issues_group_path(@group) do
%strong.fly-out-top-item-name
= _('Issues')
- %span.badge.badge-pill.count.issue_counter.fly-out-badge= number_with_delimiter(issues_count)
+ %span.badge.badge-pill.count.issue_counter.fly-out-badge= issues_count
%li.divider.fly-out-top-item
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index dc4172e2f09..9ea4d3df631 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -50,10 +50,10 @@
- if can?(current_user, :push_code, @project)
- if branch.name == @project.repository.root_ref
- %button{ class: "gl-button btn btn-danger remove-row has-tooltip disabled",
- disabled: true,
- title: s_('Branches|The default branch cannot be deleted') }
- = sprite_icon("remove")
+ - delete_default_branch_tooltip = s_('Branches|The default branch cannot be deleted')
+ %span.has-tooltip{ title: delete_default_branch_tooltip }
+ %button{ class: "gl-button btn btn-danger remove-row disabled", disabled: true, 'aria-label' => delete_default_branch_tooltip }
+ = sprite_icon("remove")
- elsif protected_branch?(@project, branch)
- if can?(current_user, :push_to_delete_protected_branch, @project)
%button{ class: "gl-button btn btn-danger remove-row has-tooltip",
@@ -65,10 +65,10 @@
is_merged: ("true" if merged) } }
= sprite_icon("remove")
- else
- %button{ class: "gl-button btn btn-danger remove-row has-tooltip disabled",
- disabled: true,
- title: s_('Branches|Only a project maintainer or owner can delete a protected branch') }
- = sprite_icon("remove")
+ - delete_protected_branch_tooltip = s_('Branches|Only a project maintainer or owner can delete a protected branch')
+ %span.has-tooltip{ title: delete_protected_branch_tooltip }
+ %button{ class: "gl-button btn btn-danger remove-row disabled", disabled: true, 'aria-label' => delete_protected_branch_tooltip }
+ = sprite_icon("remove")
- else
= link_to project_branch_path(@project, branch.name),
class: "gl-button btn btn-danger remove-row qa-remove-btn js-ajax-loading-spinner has-tooltip",
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index dbe0bf35b98..fad168da71e 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -7,11 +7,11 @@
%span= s_('ProjectOverview|Fork')
- else
- can_create_fork = current_user.can?(:create_fork)
- = link_to new_project_fork_path(@project),
- class: "btn btn-default btn-xs has-tooltip count-badge-button d-flex align-items-center fork-btn #{'has-tooltip disabled' unless can_create_fork}",
- title: (s_('ProjectOverview|You have reached your project limit') unless can_create_fork) do
- = sprite_icon('fork', css_class: 'icon')
- %span= s_('ProjectOverview|Fork')
+ - disabled_fork_tooltip = s_('ProjectOverview|You have reached your project limit')
+ %span.has-tooltip{ title: (disabled_fork_tooltip unless can_create_fork) }
+ = link_to new_project_fork_path(@project), class: "btn btn-default btn-xs count-badge-button d-flex align-items-center fork-btn #{' disabled' unless can_create_fork }", 'aria-label' => (disabled_fork_tooltip unless can_create_fork) do
+ = sprite_icon('fork', css_class: 'icon')
+ %span= s_('ProjectOverview|Fork')
%span.fork-count.count-badge-count.d-flex.align-items-center
= link_to project_forks_path(@project), title: n_(s_('ProjectOverview|Fork'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count' do
= @project.forks_count
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index a785e36fad5..d11b61466e2 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -39,15 +39,15 @@
- if can?(current_user, :award_emoji, note)
- if note.emoji_awardable?
.note-actions-item
- = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do
- %span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile')
- %span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley')
- %span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile')
+ = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip btn gl-button btn-icon btn-default-tertiary btn-transparent", data: { position: 'right', container: 'body' } do
+ = sprite_icon('slight-smile', css_class: 'link-highlight award-control-icon-neutral gl-button-icon gl-icon gl-text-gray-400')
+ = sprite_icon('smiley', css_class: 'link-highlight award-control-icon-positive gl-button-icon gl-icon gl-left-3!')
+ = sprite_icon('smile', css_class: 'link-highlight award-control-icon-super-positive gl-button-icon gl-icon gl-left-3! ')
- if note_editable
- .note-actions-item
- = button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body', qa_selector: 'edit_comment_button' } do
+ .note-actions-item.gl-ml-0
+ = button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn gl-button btn-default-tertiary btn-transparent gl-px-2!', data: { container: 'body', qa_selector: 'edit_comment_button' } do
%span.link-highlight
- = custom_icon('icon_pencil')
+ = sprite_icon('pencil', css_class: 'gl-button-icon gl-icon gl-text-gray-400 s16')
= render 'projects/notes/more_actions_dropdown', note: note, note_editable: note_editable
diff --git a/app/views/projects/notes/_more_actions_dropdown.html.haml b/app/views/projects/notes/_more_actions_dropdown.html.haml
index 8cf1b6b9294..c81a3683e90 100644
--- a/app/views/projects/notes/_more_actions_dropdown.html.haml
+++ b/app/views/projects/notes/_more_actions_dropdown.html.haml
@@ -1,10 +1,9 @@
- is_current_user = current_user == note.author
- if note_editable || !is_current_user
- .dropdown.more-actions.note-actions-item
- = button_tag title: 'More actions', class: 'note-action-button more-actions-toggle has-tooltip btn btn-transparent', data: { toggle: 'dropdown', container: 'body', qa_selector: 'more_actions_dropdown' } do
- %span.icon
- = custom_icon('ellipsis_v')
+ %div{ class: "dropdown more-actions note-actions-item gl-ml-0!" }
+ = button_tag title: 'More actions', class: 'note-action-button more-actions-toggle has-tooltip btn gl-button btn-default-tertiary btn-transparent gl-pl-2! gl-pr-0!', data: { toggle: 'dropdown', container: 'body', qa_selector: 'more_actions_dropdown' } do
+ = sprite_icon('ellipsis_v', css_class: 'gl-button-icon gl-icon gl-text-gray-400')
%ul.dropdown-menu.more-actions-dropdown.dropdown-open-left
%li
= clipboard_button(text: noteable_note_url(note), title: _('Copy reference'), button_text: _('Copy link'), class: 'btn-clipboard', hide_tooltip: true, hide_button_icon: true)
diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml
index e9ace8c72f1..ec3fc27dc20 100644
--- a/app/views/projects/pages/_use.html.haml
+++ b/app/views/projects/pages/_use.html.haml
@@ -4,7 +4,8 @@
= s_('GitLabPages|Configure pages')
.card-body
%p.gl-mb-0
- - link_start = "<a href='#{help_page_path('user/project/pages/index.md')}' target='_blank' rel='noopener noreferrer'>".html_safe
+ - docs_link_start = "<a href='#{help_page_path('user/project/pages/index.md')}' target='_blank' rel='noopener noreferrer'>".html_safe
+ - samples_link_start = "<a href='https://gitlab.com/pages' target='_blank' rel='noopener noreferrer'>".html_safe
+ - templates_link_start = "<a href='https://gitlab.com/gitlab-org/project-templates' target='_blank' rel='noopener noreferrer'>".html_safe
- link_end = '</a>'.html_safe
- = s_('GitLabPages|Learn how to upload your static site and have it served by GitLab by following the %{link_start}documentation on GitLab Pages%{link_end}.').html_safe % { link_start: link_start,
- link_end: link_end }
+ = s_('GitLabPages|See the %{docs_link_start}GitLab Pages documentation%{link_end} to learn how to upload your static site and have GitLab serve it. You can also follow a %{samples_link_start}sample project%{link_end} or use a %{templates_link_start}GitLab CI template%{link_end}.').html_safe % { docs_link_start: docs_link_start, samples_link_start: samples_link_start, templates_link_start: templates_link_start, link_end: link_end }
diff --git a/app/views/shared/access_tokens/_form.html.haml b/app/views/shared/access_tokens/_form.html.haml
index f206a2152c2..089643f4748 100644
--- a/app/views/shared/access_tokens/_form.html.haml
+++ b/app/views/shared/access_tokens/_form.html.haml
@@ -30,4 +30,4 @@
= render 'shared/tokens/scopes_form', prefix: prefix, token: token, scopes: scopes
.gl-mt-3
- = f.submit _('Create %{type}') % { type: type }, class: 'btn btn-success', data: { qa_selector: 'create_token_button' }
+ = f.submit _('Create %{type}') % { type: type }, class: 'gl-button btn btn-success', data: { qa_selector: 'create_token_button' }
diff --git a/app/views/shared/access_tokens/_table.html.haml b/app/views/shared/access_tokens/_table.html.haml
index 50daa400e6c..d7c74255578 100644
--- a/app/views/shared/access_tokens/_table.html.haml
+++ b/app/views/shared/access_tokens/_table.html.haml
@@ -43,7 +43,7 @@
- else
%span.token-never-expires-label= _('Never')
%td= token.scopes.present? ? token.scopes.join(', ') : _('no scopes selected')
- %td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: 'btn btn-danger float-right qa-revoke-button', data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } }
+ %td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: 'gl-button btn btn-danger float-right qa-revoke-button', data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } }
- else
.settings-message.text-center
= no_active_tokens_message
diff --git a/changelogs/unreleased/231204-projects-notes.yml b/changelogs/unreleased/231204-projects-notes.yml
new file mode 100644
index 00000000000..e1952e214a3
--- /dev/null
+++ b/changelogs/unreleased/231204-projects-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Apply GitLab UI button styles to buttons in app/views/projects/notes directory
+merge_request: 44107
+author: Lakshit
+type: other
diff --git a/changelogs/unreleased/290231-warning-message-for-group-migration.yml b/changelogs/unreleased/290231-warning-message-for-group-migration.yml
new file mode 100644
index 00000000000..bb51802e835
--- /dev/null
+++ b/changelogs/unreleased/290231-warning-message-for-group-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Add warning message for GitLab group migration
+merge_request: 51214
+author:
+type: changed
diff --git a/changelogs/unreleased/291140-query-single-state.yml b/changelogs/unreleased/291140-query-single-state.yml
new file mode 100644
index 00000000000..ba7e586a493
--- /dev/null
+++ b/changelogs/unreleased/291140-query-single-state.yml
@@ -0,0 +1,5 @@
+---
+title: Add GraphQL query for single Terraform state
+merge_request: 51145
+author:
+type: added
diff --git a/changelogs/unreleased/293477-update-pages-help-text.yml b/changelogs/unreleased/293477-update-pages-help-text.yml
new file mode 100644
index 00000000000..5117aec4fcc
--- /dev/null
+++ b/changelogs/unreleased/293477-update-pages-help-text.yml
@@ -0,0 +1,5 @@
+---
+title: Update links in Pages settings
+merge_request: 51847
+author:
+type: other
diff --git a/changelogs/unreleased/299213-project-filter-regression.yml b/changelogs/unreleased/299213-project-filter-regression.yml
new file mode 100644
index 00000000000..243b408ebf8
--- /dev/null
+++ b/changelogs/unreleased/299213-project-filter-regression.yml
@@ -0,0 +1,5 @@
+---
+title: Global Search - Project Filter sets Group
+merge_request: 52015
+author:
+type: fixed
diff --git a/changelogs/unreleased/id-bump-doorkeeper-to-5-4-0.yml b/changelogs/unreleased/id-bump-doorkeeper-to-5-4-0.yml
new file mode 100644
index 00000000000..46790583400
--- /dev/null
+++ b/changelogs/unreleased/id-bump-doorkeeper-to-5-4-0.yml
@@ -0,0 +1,5 @@
+---
+title: Bump doorkeeper to 5.4.0
+merge_request: 51559
+author:
+type: other
diff --git a/changelogs/unreleased/move_mutations_to_ce_for_sast_config.yml b/changelogs/unreleased/move_mutations_to_ce_for_sast_config.yml
new file mode 100644
index 00000000000..edaf88df906
--- /dev/null
+++ b/changelogs/unreleased/move_mutations_to_ce_for_sast_config.yml
@@ -0,0 +1,5 @@
+---
+title: 'Move to CE: mutation to create MR for SAST Configuration'
+merge_request: 51634
+author:
+type: changed
diff --git a/changelogs/unreleased/ph-235741-fixSuggestionsUpdatingIncorrectly.yml b/changelogs/unreleased/ph-235741-fixSuggestionsUpdatingIncorrectly.yml
new file mode 100644
index 00000000000..2b7bbef0223
--- /dev/null
+++ b/changelogs/unreleased/ph-235741-fixSuggestionsUpdatingIncorrectly.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed notes polling incorrectly overwriting suggestions in the DOM
+merge_request: 51988
+author:
+type: fixed
diff --git a/changelogs/unreleased/yo-gl-button-revoke-button.yml b/changelogs/unreleased/yo-gl-button-revoke-button.yml
new file mode 100644
index 00000000000..9d5e8c61e54
--- /dev/null
+++ b/changelogs/unreleased/yo-gl-button-revoke-button.yml
@@ -0,0 +1,5 @@
+---
+title: Add gl-button to personal access token page
+merge_request: 51294
+author: Yogi (@yo)
+type: other
diff --git a/config/feature_flags/development/cached_sidebar_open_issues_count.yml b/config/feature_flags/development/cached_sidebar_open_issues_count.yml
new file mode 100644
index 00000000000..e94566057fc
--- /dev/null
+++ b/config/feature_flags/development/cached_sidebar_open_issues_count.yml
@@ -0,0 +1,8 @@
+---
+name: cached_sidebar_open_issues_count
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49739
+rollout_issue_url:
+milestone: '13.8'
+type: development
+group: group::product planning
+default_enabled: false
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 64dd074a912..ac24ab0bc49 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -19316,7 +19316,17 @@ type Project {
tagList: String
"""
- Terraform states associated with the project
+ Find a single Terraform state by name.
+ """
+ terraformState(
+ """
+ Name of the Terraform state.
+ """
+ name: String!
+ ): TerraformState
+
+ """
+ Terraform states associated with the project.
"""
terraformStates(
"""
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 790c641a6c0..3c751925558 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -56123,8 +56123,35 @@
"deprecationReason": null
},
{
+ "name": "terraformState",
+ "description": "Find a single Terraform state by name.",
+ "args": [
+ {
+ "name": "name",
+ "description": "Name of the Terraform state.",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "TerraformState",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "terraformStates",
- "description": "Terraform states associated with the project",
+ "description": "Terraform states associated with the project.",
"args": [
{
"name": "after",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 31e9e46b6be..1497e8c2aca 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -2787,7 +2787,8 @@ Autogenerated return type of PipelineRetry.
| `statistics` | ProjectStatistics | Statistics of the project |
| `suggestionCommitMessage` | String | The commit message used to apply merge request suggestions |
| `tagList` | String | List of project topics (not Git tags) |
-| `terraformStates` | TerraformStateConnection | Terraform states associated with the project |
+| `terraformState` | TerraformState | Find a single Terraform state by name. |
+| `terraformStates` | TerraformStateConnection | Terraform states associated with the project. |
| `userPermissions` | ProjectPermissions! | Permissions for the current user on the resource |
| `visibility` | String | Visibility of the project |
| `vulnerabilities` | VulnerabilityConnection | Vulnerabilities reported on the project |
diff --git a/doc/api/groups.md b/doc/api/groups.md
index eb255f8de00..958e876ba01 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -941,6 +941,19 @@ The `shared_runners_setting` attribute determines whether shared runners are ena
| `disabled_with_override` | Disables shared runners for all projects and subgroups in this group, but allows subgroups to override this setting. |
| `disabled_and_unoverridable` | Disables shared runners for all projects and subgroups in this group, and prevents subgroups from overriding this setting. |
+### Upload a group avatar
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36681) in GitLab 12.9.
+
+To upload an avatar file from your file system, use the `--form` argument. This causes
+curl to post data using the header `Content-Type: multipart/form-data`. The
+`file=` parameter must point to a file on your file system and be preceded by
+`@`. For example:
+
+```shell
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/22" --form "avatar=@/tmp/example.png"
+```
+
## Remove group
Only available to group owners and administrators.
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 740be7d1dbd..391d32edfa7 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -160,8 +160,6 @@ Its feature set is listed on the table below according to DevOps stages.
Find example project code and tutorials for using GitLab CI/CD with a variety of app frameworks, languages, and platforms
on the [CI Examples](examples/README.md) page.
-GitLab also provides [example projects](https://gitlab.com/gitlab-examples) pre-configured to use GitLab CI/CD.
-
## Administration **(CORE ONLY)**
As a GitLab administrator, you can change the default behavior
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 9fa0bb080ac..3384f76b686 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -23,31 +23,35 @@ Examples are available in several forms. As a collection of:
The following table lists examples with step-by-step tutorials that are contained in this section:
| Use case | Resource |
-|:------------------------------|:---------|
+|-------------------------------|----------|
| Browser performance testing | [Browser Performance Testing with the Sitespeed.io container](../../user/project/merge_requests/browser_performance_testing.md). |
| Clojure | [Test a Clojure application with GitLab CI/CD](test-clojure-application.md). |
| Deployment with Dpl | [Using `dpl` as deployment tool](deployment/README.md). |
| GitLab Pages | See the [GitLab Pages](../../user/project/pages/index.md) documentation for a complete example of deploying a static site. |
| End-to-end testing | [End-to-end testing with GitLab CI/CD and WebdriverIO](end_to_end_testing_webdriverio/index.md). |
-| Game development | [DevOps and Game Dev with GitLab CI/CD](devops_and_game_dev_with_gitlab_ci_cd/index.md). |
-| Java with Maven | [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md). |
-| Java with Spring Boot | [Deploy a Spring Boot application to Cloud Foundry with GitLab CI/CD](deploy_spring_boot_to_cloud_foundry/index.md). |
| Load performance testing | [Load Performance Testing with the k6 container](../../user/project/merge_requests/load_performance_testing.md). |
| Multi project pipeline | [Build, test deploy using multi project pipeline](https://gitlab.com/gitlab-examples/upstream-project). |
| NPM with semantic-release | [Publish NPM packages to the GitLab Package Registry using semantic-release](semantic-release.md). |
| PHP with Laravel, Envoy | [Test and deploy Laravel applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md). |
| PHP with NPM, SCP | [Running Composer and NPM scripts with deployment via SCP in GitLab CI/CD](deployment/composer-npm-deploy.md). |
| PHP with PHPunit, atoum | [Testing PHP projects](php.md). |
-| Parallel testing Ruby & JS | [GitLab CI/CD parallel jobs testing for Ruby & JavaScript projects](https://docs.knapsackpro.com/2019/how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing). |
| Python on Heroku | [Test and deploy a Python application with GitLab CI/CD](test-and-deploy-python-application-to-heroku.md). |
| Ruby on Heroku | [Test and deploy a Ruby application with GitLab CI/CD](test-and-deploy-ruby-application-to-heroku.md). |
-| Scala on Heroku | [Test and deploy a Scala application to Heroku](test-scala-application.md). |
| Secrets management with Vault | [Authenticating and Reading Secrets With Hashicorp Vault](authenticating-with-hashicorp-vault/index.md). |
-### How to contributing examples
+### Contributed examples
+
+You can help people that use your favorite programming language by submitting a link
+to a guide for that language. These contributed guides are hosted externally or in
+separate example projects:
-Contributions are welcome! You can help your favorite programming
-language users and GitLab by sending a merge request with a guide for that language.
+| Use case | Resource |
+|-------------------------------|----------|
+| Game development | [DevOps and Game Dev with GitLab CI/CD](https://gitlab.com/gitlab-examples/gitlab-game-demo/). |
+| Java with Maven | [How to deploy Maven projects to Artifactory with GitLab CI/CD](https://gitlab.com/gitlab-examples/maven/simple-maven-example). |
+| Java with Spring Boot | [Deploy a Spring Boot application to Cloud Foundry with GitLab CI/CD](https://gitlab.com/gitlab-examples/spring-gitlab-cf-deploy-demo). |
+| Parallel testing Ruby & JS | [GitLab CI/CD parallel jobs testing for Ruby & JavaScript projects](https://docs.knapsackpro.com/2019/how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing). |
+| Scala on Heroku | [Test and deploy a Scala application to Heroku](https://gitlab.com/gitlab-examples/scala-sbt). |
## CI/CD templates
diff --git a/doc/ci/examples/artifactory_and_gitlab/index.md b/doc/ci/examples/artifactory_and_gitlab/index.md
index c1df21297e3..a1a7de26cf2 100644
--- a/doc/ci/examples/artifactory_and_gitlab/index.md
+++ b/doc/ci/examples/artifactory_and_gitlab/index.md
@@ -1,289 +1,8 @@
---
-stage: Verify
-group: Continuous Integration
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
-disqus_identifier: 'https://docs.gitlab.com/ee/articles/artifactory_and_gitlab/index.html'
-author: Fabio Busatto
-author_gitlab: bikebilly
-type: tutorial
-date: 2017-08-15
+redirect_to: '../README.md#contributed-examples'
---
-<!-- vale off -->
+This document was moved to [another location](../README.md#contributed-examples).
-# How to deploy Maven projects to Artifactory with GitLab CI/CD
-
-## Introduction
-
-In this article, we show how you can leverage the power of [GitLab CI/CD](https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/)
-to build a [Maven](https://maven.apache.org/) project, deploy it to [Artifactory](https://jfrog.com/artifactory/), and then use it from another Maven application as a dependency.
-
-You'll create two different projects:
-
-- `simple-maven-dep`: the app built and deployed to Artifactory (see the [simple-maven-dep](https://gitlab.com/gitlab-examples/maven/simple-maven-dep) example project)
-- `simple-maven-app`: the app using the previous one as a dependency (see the [simple-maven-app](https://gitlab.com/gitlab-examples/maven/simple-maven-app) example project)
-
-We assume that you already have a GitLab account on [GitLab.com](https://gitlab.com/), and that you know the basic usage of Git and [GitLab CI/CD](https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/).
-We also assume that an Artifactory instance is available and reachable from the internet, and that you have valid credentials to deploy on it.
-
-## Create the simple Maven dependency
-
-First, you need an application to work with: in this specific case we'll use a
-simple one, but it could be any Maven application. This will be the dependency
-you want to package and deploy to Artifactory, to be available to other
-projects.
-
-### Prepare the dependency application
-
-For this article you'll use a Maven app that can be cloned from our example
-project:
-
-1. Log in to your GitLab account
-1. Create a new project by selecting **Import project from > Repo by URL**
-1. Add the following URL:
-
- ```plaintext
- https://gitlab.com/gitlab-examples/maven/simple-maven-dep.git
- ```
-
-1. Click **Create project**
-
-This application is nothing more than a basic class with a stub for a JUnit based test suite.
-It exposes a method called `hello` that accepts a string as input, and prints a hello message on the screen.
-
-The project structure is really simple, and you should consider these two resources:
-
-- `pom.xml`: project object model (POM) configuration file
-- `src/main/java/com/example/dep/Dep.java`: source of our application
-
-### Configure the Artifactory deployment
-
-The application is ready to use, but you need some additional steps to deploy it to Artifactory:
-
-1. Log in to Artifactory with your user's credentials.
-1. From the main screen, click on the `libs-release-local` item in the **Set Me Up** panel.
-1. Copy to clipboard the configuration snippet under the **Deploy** paragraph.
-1. Change the `url` value to have it configurable by using variables.
-1. Copy the snippet in the `pom.xml` file for your project, just after the
- `dependencies` section. The snippet should look like this:
-
- ```xml
- <distributionManagement>
- <repository>
- <id>central</id>
- <name>83d43b5afeb5-releases</name>
- <url>${env.MAVEN_REPO_URL}/libs-release-local</url>
- </repository>
- </distributionManagement>
- ```
-
-Another step you need to do before you can deploy the dependency to Artifactory
-is to configure the authentication data. It is a simple task, but Maven requires
-it to stay in a file called `settings.xml` that has to be in the `.m2` subdirectory
-in the user's homedir.
-
-Since you want to use a runner to automatically deploy the application, you
-should create the file in the project's home directory and set a command line
-parameter in `.gitlab-ci.yml` to use the custom location instead of the default one:
-
-1. Create a folder called `.m2` in the root of your repository
-1. Create a file called `settings.xml` in the `.m2` folder
-1. Copy the following content into a `settings.xml` file:
-
- ```xml
- <settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd"
- xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
- <servers>
- <server>
- <id>central</id>
- <username>${env.MAVEN_REPO_USER}</username>
- <password>${env.MAVEN_REPO_PASS}</password>
- </server>
- </servers>
- </settings>
- ```
-
- Username and password will be replaced by the correct values using variables.
-
-### Configure GitLab CI/CD for `simple-maven-dep`
-
-Now it's time we set up [GitLab CI/CD](https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/) to automatically build, test and deploy the dependency!
-
-GitLab CI/CD uses a file in the root of the repository, named `.gitlab-ci.yml`, to read the definitions for jobs
-that will be executed by the configured runners. You can read more about this file in the [GitLab Documentation](../../yaml/README.md).
-
-First of all, remember to set up variables for your deployment. Navigate to your project's **Settings > CI/CD > Environment variables** page
-and add the following ones (replace them with your current values, of course):
-
-- **MAVEN_REPO_URL**: `http://artifactory.example.com:8081/artifactory` (your Artifactory URL)
-- **MAVEN_REPO_USER**: `gitlab` (your Artifactory username)
-- **MAVEN_REPO_PASS**: `AKCp2WXr3G61Xjz1PLmYa3arm3yfBozPxSta4taP3SeNu2HPXYa7FhNYosnndFNNgoEds8BCS` (your Artifactory Encrypted Password)
-
-Now it's time to define jobs in `.gitlab-ci.yml` and push it to the repository:
-
-```yaml
-image: maven:latest
-
-variables:
- MAVEN_CLI_OPTS: "-s .m2/settings.xml --batch-mode"
- MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
-
-cache:
- paths:
- - .m2/repository/
- - target/
-
-build:
- stage: build
- script:
- - mvn $MAVEN_CLI_OPTS compile
-
-test:
- stage: test
- script:
- - mvn $MAVEN_CLI_OPTS test
-
-deploy:
- stage: deploy
- script:
- - mvn $MAVEN_CLI_OPTS deploy
- only:
- - master
-```
-
-The runner uses the latest [Maven Docker image](https://hub.docker.com/_/maven/),
-which contains all of the tools and dependencies needed to manage the project
-and to run the jobs.
-
-Environment variables are set to instruct Maven to use the `homedir` of the repository instead of the user's home when searching for configuration and dependencies.
-
-Caching the `.m2/repository folder` (where all the Maven files are stored), and the `target` folder (where our application will be created), is useful for speeding up the process
-by running all Maven phases in a sequential order, therefore, executing `mvn test` will automatically run `mvn compile` if necessary.
-
-Both `build` and `test` jobs leverage the `mvn` command to compile the application and to test it as defined in the test suite that is part of the application.
-
-Deploy to Artifactory is done as defined by the variables we have just set up.
-The deployment occurs only if we're pushing or merging to `master` branch, so that the development versions are tested but not published.
-
-Done! Now you have all the changes in the GitLab repository, and a pipeline has already been started for this commit. In the **Pipelines** tab you can see what's happening.
-If the deployment has been successful, the deploy job log will output:
-
-```plaintext
-[INFO] ------------------------------------------------------------------------
-[INFO] BUILD SUCCESS
-[INFO] ------------------------------------------------------------------------
-[INFO] Total time: 1.983 s
-```
-
->**Note**:
-the `mvn` command downloads a lot of files from the internet, so you'll see a lot of extra activity in the log the first time you run it.
-
-Yay! You did it! Checking in Artifactory will confirm that you have a new artifact available in the `libs-release-local` repository.
-
-## Create the main Maven application
-
-Now that you have the dependency available on Artifactory, it's time to use it!
-Let's see how we can have it as a dependency to our main application.
-
-### Prepare the main application
-
-We'll use again a Maven app that can be cloned from our example project:
-
-1. Create a new project by selecting **Import project from âž” Repo by URL**
-1. Add the following URL:
-
- ```plaintext
- https://gitlab.com/gitlab-examples/maven/simple-maven-app.git
- ```
-
-1. Click **Create project**
-
-This one is a simple app as well. If you look at the `src/main/java/com/example/app/App.java`
-file you can see that it imports the `com.example.dep.Dep` class and calls the `hello` method passing `GitLab` as a parameter.
-
-Since Maven doesn't know how to resolve the dependency, you need to modify the configuration:
-
-1. Go back to Artifactory
-1. Browse the `libs-release-local` repository
-1. Select the `simple-maven-dep-1.0.jar` file
-1. Find the configuration snippet from the **Dependency Declaration** section of the main panel
-1. Copy the snippet in the `dependencies` section of the `pom.xml` file.
- The snippet should look like this:
-
- ```xml
- <dependency>
- <groupId>com.example.dep</groupId>
- <artifactId>simple-maven-dep</artifactId>
- <version>1.0</version>
- </dependency>
- ```
-
-### Configure the Artifactory repository location
-
-At this point you defined the dependency for the application, but you still miss where you can find the required files.
-You need to create a `.m2/settings.xml` file as you did for the dependency project, and let Maven know the location using environment variables.
-
-Here is how you can get the content of the file directly from Artifactory:
-
-1. From the main screen, click on the `libs-release-local` item in the **Set Me Up** panel
-1. Click on **Generate Maven Settings**
-1. Click on **Generate Settings**
-1. Copy to clipboard the configuration file
-1. Save the file as `.m2/settings.xml` in your repository
-
-Now you are ready to use the Artifactory repository to resolve dependencies and use `simple-maven-dep` in your main application!
-
-### Configure GitLab CI/CD for `simple-maven-app`
-
-You need a last step to have everything in place: configure the `.gitlab-ci.yml` file for this project, as you already did for `simple-maven-dep`.
-
-You want to leverage [GitLab CI/CD](https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/) to automatically build, test and run your awesome application,
-and see if you can get the greeting as expected!
-
-All you need to do is to add the following `.gitlab-ci.yml` to the repository:
-
-```yaml
-image: maven:latest
-
-stages:
- - build
- - test
- - run
-
-variables:
- MAVEN_CLI_OPTS: "-s .m2/settings.xml --batch-mode"
- MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
-
-cache:
- paths:
- - .m2/repository/
- - target/
-
-build:
- stage: build
- script:
- - mvn $MAVEN_CLI_OPTS compile
-
-test:
- stage: test
- script:
- - mvn $MAVEN_CLI_OPTS test
-
-run:
- stage: run
- script:
- - mvn $MAVEN_CLI_OPTS package
- - mvn $MAVEN_CLI_OPTS exec:java -Dexec.mainClass="com.example.app.App"
-```
-
-It is very similar to the configuration used for `simple-maven-dep`, but instead of the `deploy` job there is a `run` job.
-Probably something that you don't want to use in real projects, but here it is useful to see the application executed automatically.
-
-And that's it! In the `run` job output log you will find a friendly hello to GitLab!
-
-## Conclusion
-
-In this article we covered the basic steps to use an Artifactory Maven repository to automatically publish and consume artifacts.
-
-A similar approach could be used to interact with any other Maven compatible Binary Repository Manager.
-Obviously, you can improve these examples, optimizing the `.gitlab-ci.yml` file to better suit your needs, and adapting to your workflow.
+<!-- This redirect file can be deleted after 2021-04-18. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png
deleted file mode 100644
index e76767741ce..00000000000
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/create_from_template.png b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/create_from_template.png
deleted file mode 100644
index f3761632556..00000000000
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/create_from_template.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
index 9c145677f6e..a1a7de26cf2 100644
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
+++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
@@ -1,145 +1,8 @@
---
-stage: Release
-group: Release
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
-author: Dylan Griffith
-author_gitlab: DylanGriffith
-type: tutorial
-date: 2018-06-07
-description: "Continuous Deployment of a Spring Boot application to Cloud Foundry with GitLab CI/CD"
+redirect_to: '../README.md#contributed-examples'
---
-<!-- vale off -->
+This document was moved to [another location](../README.md#contributed-examples).
-# Deploy a Spring Boot application to Cloud Foundry with GitLab CI/CD
-
-## Introduction
-
-This article demonstrates how to use the [Continuous Deployment](https://about.gitlab.com/blog/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#continuous-deployment)
-method to deploy a [Spring Boot](https://projects.spring.io/spring-boot/) application to
-[Cloud Foundry (CF)](https://www.cloudfoundry.org/)
-with GitLab CI/CD.
-
-All the code for this project can be found in this [GitLab
-repository](https://gitlab.com/gitlab-examples/spring-gitlab-cf-deploy-demo).
-
-In case you're interested in deploying Spring Boot applications to Kubernetes
-using GitLab CI/CD, read through the blog post [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/blog/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/).
-
-## Requirements
-
-This tutorial assumes you are familiar with Java, GitLab, Cloud Foundry, and GitLab CI/CD.
-
-To follow along, you need:
-
-- An account on [Pivotal Web Services (PWS)](https://run.pivotal.io/) or any
- other Cloud Foundry (CF) instance.
-- An account on GitLab.
-
-NOTE:
-If you're not deploying to PWS, you must replace the `api.run.pivotal.io` URL in all the below
-commands with the [API URL](https://docs.cloudfoundry.org/running/cf-api-endpoint.html)
-of your CF instance.
-
-## Create your project
-
-To create your Spring Boot application you can use the Spring template in
-GitLab when creating a new project:
-
-![New Project From Template](img/create_from_template.png)
-
-## Configure the deployment to Cloud Foundry
-
-To deploy to Cloud Foundry you must add a `manifest.yml` file. This
-is the configuration for the CF CLI you must use to deploy the application.
-Create this in the root directory of your project with the following
-content:
-
-```yaml
----
-applications:
- - name: gitlab-hello-world
- random-route: true
- memory: 1G
- path: target/demo-0.0.1-SNAPSHOT.jar
-```
-
-## Configure GitLab CI/CD to deploy your application
-
-Now you must add the GitLab CI/CD configuration file
-([`.gitlab-ci.yml`](../../yaml/README.md))
-to your project's root. This is how GitLab figures out what commands must run whenever
-code is pushed to your repository. Add the following `.gitlab-ci.yml`
-file to the root directory of the repository. GitLab detects it
-automatically and runs the defined steps once you push your code:
-
-```yaml
-image: java:8
-
-stages:
- - build
- - deploy
-
-before_script:
- - chmod +x mvnw
-
-build:
- stage: build
- script: ./mvnw package
- artifacts:
- paths:
- - target/demo-0.0.1-SNAPSHOT.jar
-
-production:
- stage: deploy
- script:
- - curl --location "https://cli.run.pivotal.io/stable?release=linux64-binary&source=github" | tar zx
- - ./cf login -u $CF_USERNAME -p $CF_PASSWORD -a api.run.pivotal.io
- - ./cf push
- only:
- - master
-```
-
-This uses the `java:8` [Docker image](../../docker/using_docker_images.md)
-to build your application, as it provides the up-to-date Java 8 JDK on [Docker Hub](https://hub.docker.com/).
-You also added the [`only` clause](../../yaml/README.md#onlyexcept-basic)
-to ensure your deployments only happen when you push to the master branch.
-
-Because the steps defined in `.gitlab-ci.yml` require credentials to sign in to
-CF, you must add your CF credentials as
-[environment variables](../../variables/README.md#predefined-environment-variables)
-in GitLab CI/CD. To set the environment variables, navigate to your project's
-**Settings > CI/CD**, and then expand **Variables**. Name the variables
-`CF_USERNAME` and `CF_PASSWORD` and set them to the correct values.
-
-![Variable Settings in GitLab](img/cloud_foundry_variables.png)
-
-After set up, GitLab CI/CD deploys your app to CF at every push to your
-repository's default branch. To review the build logs or watch your builds
-running live, navigate to **CI/CD > Pipelines**.
-
-WARNING:
-It's considered best practice for security to create a separate deploy user for
-your application and add its credentials to GitLab instead of using a
-developer's credentials.
-
-To start a manual deployment in GitLab go to **CI/CD > Pipelines** then click
-**Run Pipeline**. After the app is finished deploying, it displays the
-URL of your application in the logs for the `production` job:
-
-```shell
-requested state: started
-instances: 1/1
-usage: 1G x 1 instances
-urls: gitlab-hello-world-undissembling-hotchpot.cfapps.io
-last uploaded: Mon Nov 6 10:02:25 UTC 2017
-stack: cflinuxfs2
-buildpack: client-certificate-mapper=1.2.0_RELEASE container-security-provider=1.8.0_RELEASE java-buildpack=v4.5-offline-https://github.com/cloudfoundry/java-buildpack.git#ffeefb9 java-main java-opts jvmkill-agent=1.10.0_RELEASE open-jdk-like-jre=1.8.0_1...
-
- state since cpu memory disk details
-#0 running 2017-11-06 09:03:22 PM 120.4% 291.9M of 1G 137.6M of 1G
-```
-
-You can then visit your deployed application (for this example,
-`https://gitlab-hello-world-undissembling-hotchpot.cfapps.io/`) and you should
-see the "Spring is here!" message.
+<!-- This redirect file can be deleted after 2021-04-18. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/aws_config_window.png b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/aws_config_window.png
deleted file mode 100644
index 09eef98202f..00000000000
--- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/aws_config_window.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/gitlab_config.png b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/gitlab_config.png
deleted file mode 100644
index 71ffcdea289..00000000000
--- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/gitlab_config.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/test_pipeline_pass.png b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/test_pipeline_pass.png
deleted file mode 100644
index a9452577a42..00000000000
--- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/test_pipeline_pass.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
index 298ffff568a..0dc412bf002 100644
--- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
@@ -1,534 +1,8 @@
---
-stage: Verify
-group: Continuous Integration
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
-author: Ryan Hall
-author_gitlab: blitzgren
-type: tutorial
-date: 2018-03-07
+redirect_to: '../README.md#ci-cd-examples'
---
-<!-- vale off -->
+This document was moved to [another location](../README.md#contributed-examples).
-# DevOps and Game Dev with GitLab CI/CD
-
-With advances in WebGL and WebSockets, browsers are extremely viable as game development
-platforms without the use of plugins like Adobe Flash. Furthermore, by using GitLab and [AWS](https://aws.amazon.com/),
-single game developers, as well as game dev teams, can easily host browser-based games online.
-
-In this tutorial, we'll focus on DevOps, as well as testing and hosting games with Continuous
-Integration/Deployment methods using [GitLab CI/CD](../../README.md). We assume you are familiar with GitLab, JavaScript,
-and the basics of game development.
-
-## The game
-
-Our [demo game](http://gitlab-game-demo.s3-website-us-east-1.amazonaws.com/) consists of a simple spaceship traveling in space that shoots by clicking the mouse in a given direction.
-
-Creating a strong CI/CD pipeline at the beginning of developing another game, [Dark Nova](https://www.darknova.io),
-was essential for the fast pace the team worked at. This tutorial will build upon my
-[previous introductory article](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) and go through the following steps:
-
-1. Using code from the previous article to start with a bare-bones [Phaser](https://phaser.io) game built by a gulp file
-1. Adding and running unit tests
-1. Creating a `Weapon` class that can be triggered to spawn a `Bullet` in a given direction
-1. Adding a `Player` class that uses this weapon and moves around the screen
-1. Adding the sprites we will use for the `Player` and `Weapon`
-1. Testing and deploying with Continuous Integration and Continuous Deployment methods
-
-By the end, we'll have the core of a [playable game](http://gitlab-game-demo.s3-website-us-east-1.amazonaws.com/)
-that's tested and deployed on every push to the `master` branch of the [codebase](https://gitlab.com/blitzgren/gitlab-game-demo).
-This will also provide
-boilerplate code for starting a browser-based game with the following components:
-
-- Written in [TypeScript](https://www.typescriptlang.org/) and [PhaserJs](https://phaser.io)
-- Building, running, and testing with [Gulp](https://gulpjs.com)
-- Unit tests with [Chai](https://www.chaijs.com) and [Mocha](https://mochajs.org/)
-- CI/CD with GitLab
-- Hosting the codebase on GitLab.com
-- Hosting the game on AWS
-- Deploying to AWS
-
-## Requirements and setup
-
-Please refer to my previous article [DevOps and Game Dev](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) to learn the foundational
-development tools, running a Hello World-like game, and building this game using GitLab
-CI/CD from every new push to master. The `master` branch for this game's [repository](https://gitlab.com/blitzgren/gitlab-game-demo)
-contains a completed version with all configurations. If you would like to follow along
-with this article, you can clone and work from the `devops-article` branch:
-
-```shell
-git clone git@gitlab.com:blitzgren/gitlab-game-demo.git
-git checkout devops-article
-```
-
-Next, we'll create a small subset of tests that exemplify most of the states I expect
-this `Weapon` class to go through. To get started, create a folder called `lib/tests`
-and add the following code to a new file `weaponTests.ts`:
-
-```typescript
-import { expect } from 'chai';
-import { Weapon, BulletFactory } from '../lib/weapon';
-
-describe('Weapon', () => {
- var subject: Weapon;
- var shotsFired: number = 0;
- // Mocked bullet factory
- var bulletFactory: BulletFactory = <BulletFactory>{
- generate: function(px, py, vx, vy, rot) {
- shotsFired++;
- }
- };
- var parent: any = { x: 0, y: 0 };
-
- beforeEach(() => {
- shotsFired = 0;
- subject = new Weapon(bulletFactory, parent, 0.25, 1);
- });
-
- it('should shoot if not in cooldown', () => {
- subject.trigger(true);
- subject.update(0.1);
- expect(shotsFired).to.equal(1);
- });
-
- it('should not shoot during cooldown', () => {
- subject.trigger(true);
- subject.update(0.1);
- subject.update(0.1);
- expect(shotsFired).to.equal(1);
- });
-
- it('should shoot after cooldown ends', () => {
- subject.trigger(true);
- subject.update(0.1);
- subject.update(0.3); // longer than timeout
- expect(shotsFired).to.equal(2);
- });
-
- it('should not shoot if not triggered', () => {
- subject.update(0.1);
- subject.update(0.1);
- expect(shotsFired).to.equal(0);
- });
-});
-```
-
-To build and run these tests using gulp, let's also add the following gulp functions
-to the existing `gulpfile.js` file:
-
-```typescript
-gulp.task('build-test', function () {
- return gulp.src('src/tests/**/*.ts', { read: false })
- .pipe(tap(function (file) {
- // replace file contents with browserify's bundle stream
- file.contents = browserify(file.path, { debug: true })
- .plugin(tsify, { project: "./tsconfig.test.json" })
- .bundle();
- }))
- .pipe(buffer())
- .pipe(sourcemaps.init({loadMaps: true}) )
- .pipe(gulp.dest('built/tests'));
-});
-
-gulp.task('run-test', function() {
- gulp.src(['./built/tests/**/*.ts']).pipe(mocha());
-});
-```
-
-We will start implementing the first part of our game and get these `Weapon` tests to pass.
-The `Weapon` class will expose a method to trigger the generation of a bullet at a given
-direction and speed. Later we will implement a `Player` class that ties together the user input
-to trigger the weapon. In the `src/lib` folder create a `weapon.ts` file. We'll add two classes
-to it: `Weapon` and `BulletFactory` which will encapsulate Phaser's **sprite** and
-**group** objects, and the logic specific to our game.
-
-```typescript
-export class Weapon {
- private isTriggered: boolean = false;
- private currentTimer: number = 0;
-
- constructor(private bulletFactory: BulletFactory, private parent: Phaser.Sprite, private cooldown: number, private bulletSpeed: number) {
- }
-
- public trigger(on: boolean): void {
- this.isTriggered = on;
- }
-
- public update(delta: number): void {
- this.currentTimer -= delta;
-
- if (this.isTriggered && this.currentTimer <= 0) {
- this.shoot();
- }
- }
-
- private shoot(): void {
- // Reset timer
- this.currentTimer = this.cooldown;
-
- // Get velocity direction from player rotation
- var parentRotation = this.parent.rotation + Math.PI / 2;
- var velx = Math.cos(parentRotation);
- var vely = Math.sin(parentRotation);
-
- // Apply a small forward offset so bullet shoots from head of ship instead of the middle
- var posx = this.parent.x - velx * 10
- var posy = this.parent.y - vely * 10;
-
- this.bulletFactory.generate(posx, posy, -velx * this.bulletSpeed, -vely * this.bulletSpeed, this.parent.rotation);
- }
-}
-
-export class BulletFactory {
-
- constructor(private bullets: Phaser.Group, private poolSize: number) {
- // Set all the defaults for this BulletFactory's bullet object
- this.bullets.enableBody = true;
- this.bullets.physicsBodyType = Phaser.Physics.ARCADE;
- this.bullets.createMultiple(30, 'bullet');
- this.bullets.setAll('anchor.x', 0.5);
- this.bullets.setAll('anchor.y', 0.5);
- this.bullets.setAll('outOfBoundsKill', true);
- this.bullets.setAll('checkWorldBounds', true);
- }
-
- public generate(posx: number, posy: number, velx: number, vely: number, rot: number): Phaser.Sprite {
- // Pull a bullet from Phaser's Group pool
- var bullet = this.bullets.getFirstExists(false);
-
- // Set the few unique properties about this bullet: rotation, position, and velocity
- if (bullet) {
- bullet.reset(posx, posy);
- bullet.rotation = rot;
- bullet.body.velocity.x = velx;
- bullet.body.velocity.y = vely;
- }
-
- return bullet;
- }
-}
-```
-
-Lastly, we'll redo our entry point, `game.ts`, to tie together both `Player` and `Weapon` objects
-as well as add them to the update loop. Here is what the updated `game.ts` file looks like:
-
-```typescript
-import { Player } from "./player";
-import { Weapon, BulletFactory } from "./weapon";
-
-window.onload = function() {
- var game = new Phaser.Game(800, 600, Phaser.AUTO, 'gameCanvas', { preload: preload, create: create, update: update });
- var player: Player;
- var weapon: Weapon;
-
- // Import all assets prior to loading the game
- function preload () {
- game.load.image('player', 'assets/player.png');
- game.load.image('bullet', 'assets/bullet.png');
- }
-
- // Create all entities in the game, after Phaser loads
- function create () {
- // Create and position the player
- var playerSprite = game.add.sprite(400, 550, 'player');
- playerSprite.anchor.setTo(0.5);
- player = new Player(game.input, playerSprite, 150);
-
- var bulletFactory = new BulletFactory(game.add.group(), 30);
- weapon = new Weapon(bulletFactory, player.sprite, 0.25, 1000);
-
- player.loadWeapon(weapon);
- }
-
- // This function is called once every tick, default is 60fps
- function update() {
- var deltaSeconds = game.time.elapsedMS / 1000; // convert to seconds
- player.update(deltaSeconds);
- weapon.update(deltaSeconds);
- }
-}
-```
-
-Run `gulp serve` and you can run around and shoot. Wonderful! Let's update our CI
-pipeline to include running the tests along with the existing build job.
-
-## Continuous Integration
-
-To ensure our changes don't break the build and all tests still pass, we use
-Continuous Integration (CI) to run these checks automatically for every push.
-Read through this article to understand [Continuous Integration, Continuous Delivery, and Continuous Deployment](https://about.gitlab.com/blog/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/),
-and how these methods are leveraged by GitLab.
-From the [last tutorial](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) we already have a `.gitlab-ci.yml` file set up for building our app from
-every push. We need to set up a new CI job for testing, which GitLab CI/CD will run after the build job using our generated artifacts from gulp.
-
-Please read through the [documentation on CI/CD configuration](../../../ci/yaml/README.md) file to explore its contents and adjust it to your needs.
-
-### Build your game with GitLab CI/CD
-
-We need to update our build job to ensure tests get run as well. Add `gulp build-test`
-to the end of the `script` array for the existing `build` job. After these commands run,
-we know we will need to access everything in the `built` folder, given by GitLab CI/CD's `artifacts`.
-We'll also cache `node_modules` to avoid having to do a full re-pull of those dependencies:
-just pack them up in the cache. Here is the full `build` job:
-
-```yaml
-build:
- stage: build
- script:
- - npm i gulp -g
- - npm i
- - gulp
- - gulp build-test
- cache:
- policy: push
- paths:
- - node_modules
- artifacts:
- paths:
- - built
-```
-
-### Test your game with GitLab CI/CD
-
-For testing locally, we simply run `gulp run-tests`, which requires gulp to be installed
-globally like in the `build` job. We pull `node_modules` from the cache, so the `npm i`
-command won't have to do much. In preparation for deployment, we know we will still need
-the `built` folder in the artifacts, which will be brought over as default behavior from
-the previous job. Lastly, by convention, we let GitLab CI/CD know this needs to be run after
-the `build` job by giving it a `test` [stage](../../../ci/yaml/README.md#stages).
-Following the YAML structure, the `test` job should look like this:
-
-```yaml
-test:
- stage: test
- script:
- - npm i gulp -g
- - npm i
- - gulp run-test
- cache:
- policy: push
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
-```
-
-We have added unit tests for a `Weapon` class that shoots on a specified interval.
-The `Player` class implements `Weapon` along with the ability to move around and shoot. Also,
-we've added test artifacts and a test stage to our GitLab CI/CD pipeline using `.gitlab-ci.yml`,
-allowing us to run our tests by every push.
-Our entire `.gitlab-ci.yml` file should now look like this:
-
-```yaml
-image: node:10
-
-build:
- stage: build
- script:
- - npm i gulp -g
- - npm i
- - gulp
- - gulp build-test
- cache:
- policy: push
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
-
-test:
- stage: test
- script:
- - npm i gulp -g
- - npm i
- - gulp run-test
- cache:
- policy: pull
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
-```
-
-### Run your CI/CD pipeline
-
-That's it! Add all your new files, commit, and push. For a reference of what our repository should
-look like at this point, please refer to the [final commit related to this article on my sample repository](https://gitlab.com/blitzgren/gitlab-game-demo/commit/8b36ef0ecebcf569aeb251be4ee13743337fcfe2).
-By applying both build and test stages, GitLab will run them sequentially at every push to
-our repository. If all goes well you'll end up with a green check mark on each job for the pipeline:
-
-![Passing Pipeline](img/test_pipeline_pass.png)
-
-You can confirm that the tests passed by clicking on the `test` job to enter the full build logs.
-Scroll to the bottom and observe, in all its passing glory:
-
-```shell
-$ gulp run-test
-[18:37:24] Using gulpfile /builds/blitzgren/gitlab-game-demo/gulpfile.js
-[18:37:24] Starting 'run-test'...
-[18:37:24] Finished 'run-test' after 21 ms
-
-
- Weapon
- ✓ should shoot if not in cooldown
- ✓ should not shoot during cooldown
- ✓ should shoot after cooldown ends
- ✓ should not shoot if not triggered
-
-
- 4 passing (18ms)
-
-Uploading artifacts...
-built/: found 17 matching files
-Uploading artifacts to coordinator... ok id=17095874 responseStatus=201 Created token=aaaaaaaa Job succeeded
-```
-
-## Continuous Deployment
-
-We have our codebase built and tested on every push. To complete the full pipeline with Continuous Deployment,
-let's set up [free web hosting with AWS S3](https://aws.amazon.com/free/) and a job through which our build artifacts get
-deployed. GitLab also has a free static site hosting service we can use, [GitLab Pages](https://about.gitlab.com/stages-devops-lifecycle/pages/),
-however Dark Nova specifically uses other AWS tools that necessitates using `AWS S3`.
-Read through this article that describes [deploying to both S3 and GitLab Pages](https://about.gitlab.com/blog/2016/08/26/ci-deployment-and-environments/)
-and further delves into the principles of GitLab CI/CD than discussed in this article.
-
-### Set up S3 Bucket
-
-1. Log into your AWS account and go to [S3](https://console.aws.amazon.com/s3/home)
-1. Click the **Create Bucket** link at the top
-1. Enter a name of your choosing and click next
-1. Keep the default **Properties** and click next
-1. Click the **Manage group permissions** and allow **Read** for the **Everyone** group, click next
-1. Create the bucket, and select it in your S3 bucket list
-1. On the right side, click **Properties** and enable the **Static website hosting** category
-1. Update the radio button to the **Use this bucket to host a website** selection. Fill in `index.html` and `error.html` respectively
-
-### Set up AWS Secrets
-
-We need to be able to deploy to AWS with our AWS account credentials, but we certainly
-don't want to put secrets into source code. Luckily GitLab provides a solution for this
-with [Variables](../../../ci/variables/README.md). This can get complicated
-due to [IAM](https://aws.amazon.com/iam/) management. As a best practice, you shouldn't
-use root security credentials. Proper IAM credential management is beyond the scope of this
-article, but AWS will remind you that using root credentials is unadvised and against their
-best practices, as they should. Feel free to follow best practices and use a custom IAM user's
-credentials, which will be the same two credentials (Key ID and Secret). It's a good idea to
-fully understand [IAM Best Practices in AWS](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html). We need to add these credentials to GitLab:
-
-1. Log into your AWS account and go to the [Security Credentials page](https://console.aws.amazon.com/iam/home#/security_credential)
-1. Click the **Access Keys** section and **Create New Access Key**. Create the key and keep the ID and secret around, you'll need them later
-
- ![AWS Access Key Configuration](img/aws_config_window.png)
-
-1. Go to your GitLab project, click **Settings > CI/CD** on the left sidebar
-1. Expand the **Variables** section
-
- ![GitLab Secret Configuration](img/gitlab_config.png)
-
-1. Add a key named `AWS_KEY_ID` and copy the key ID from Step 2 into the **Value** field
-1. Add a key named `AWS_KEY_SECRET` and copy the key secret from Step 2 into the **Value** field
-
-### Deploy your game with GitLab CI/CD
-
-To deploy our build artifacts, we need to install the [AWS CLI](https://aws.amazon.com/cli/) on
-the shared runner. The shared runner also needs to be able to authenticate with your AWS
-account to deploy the artifacts. By convention, AWS CLI will look for `AWS_ACCESS_KEY_ID`
-and `AWS_SECRET_ACCESS_KEY`. GitLab CI/CD gives us a way to pass the variables we
-set up in the prior section using the `variables` portion of the `deploy` job. At the end,
-we add directives to ensure deployment `only` happens on pushes to `master`. This way, every
-single branch still runs through CI, and only merging (or committing directly) to master will
-trigger the `deploy` job of our pipeline. Put these together to get the following:
-
-```yaml
-deploy:
- stage: deploy
- variables:
- AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
- AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
- script:
- - apt-get update
- - apt-get install -y python3-dev python3-pip
- - easy_install3 -U pip
- - pip3 install --upgrade awscli
- - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
- only:
- - master
-```
-
-Be sure to update the region and S3 URL in that last script command to fit your setup.
-Our final configuration file `.gitlab-ci.yml` looks like:
-
-```yaml
-image: node:10
-
-build:
- stage: build
- script:
- - npm i gulp -g
- - npm i
- - gulp
- - gulp build-test
- cache:
- policy: push
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
-
-test:
- stage: test
- script:
- - npm i gulp -g
- - gulp run-test
- cache:
- policy: pull
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
-
-deploy:
- stage: deploy
- variables:
- AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
- AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
- script:
- - apt-get update
- - apt-get install -y python3-dev python3-pip
- - easy_install3 -U pip
- - pip3 install --upgrade awscli
- - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
- only:
- - master
-```
-
-## Conclusion
-
-Within the [demo repository](https://gitlab.com/blitzgren/gitlab-game-demo) you can also find a handful of boilerplate code to get
-[TypeScript](https://www.typescriptlang.org/), [Mocha](https://mochajs.org/), [Gulp](https://gulpjs.com/) and [Phaser](https://phaser.io) all playing
-together nicely with GitLab CI/CD, which is the result of lessons learned while making [Dark Nova](https://www.darknova.io).
-Using a combination of free and open source software, we have a full CI/CD pipeline, a game foundation,
-and unit tests, all running and deployed at every push to master - with shockingly little code.
-Errors can be easily debugged through GitLab build logs, and within minutes of a successful commit,
-you can see the changes live on your game.
-
-Setting up Continuous Integration and Continuous Deployment from the start with Dark Nova enables
-rapid but stable development. We can easily test changes in a separate [environment](../../environments/index.md),
-or multiple environments if needed. Balancing and updating a multiplayer game can be ongoing
-and tedious, but having faith in a stable deployment with GitLab CI/CD allows
-a lot of breathing room in quickly getting changes to players.
-
-## Further settings
-
-Here are some ideas to further investigate that can speed up or improve your pipeline:
-
-- [Yarn](https://yarnpkg.com) instead of npm
-- Set up a custom [Docker](../../../ci/docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) image that can pre-load dependencies and tools (like AWS CLI)
-- Forward a [custom domain](https://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) to your game's S3 static website
-- Combine jobs if you find it unnecessary for a small project
-- Avoid the queues and set up your own [custom GitLab CI/CD runner](https://about.gitlab.com/blog/2016/03/01/gitlab-runner-with-docker/)
+<!-- This redirect file can be deleted after 2021-04-19. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/doc/ci/examples/test-phoenix-application.md b/doc/ci/examples/test-phoenix-application.md
index 52db5740c34..42b7681bd10 100644
--- a/doc/ci/examples/test-phoenix-application.md
+++ b/doc/ci/examples/test-phoenix-application.md
@@ -3,3 +3,6 @@ redirect_to: '../../ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md'
---
The content of this page was incorporated in [this document](../../ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md).
+
+<!-- This redirect file can be deleted after February 1, 2021. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/doc/ci/examples/test-scala-application.md b/doc/ci/examples/test-scala-application.md
index 5c499d6a855..a1a7de26cf2 100644
--- a/doc/ci/examples/test-scala-application.md
+++ b/doc/ci/examples/test-scala-application.md
@@ -1,81 +1,8 @@
---
-stage: Verify
-group: Continuous Integration
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
-type: tutorial
+redirect_to: '../README.md#contributed-examples'
---
-# Test and deploy a Scala application to Heroku
+This document was moved to [another location](../README.md#contributed-examples).
-This example demonstrates the integration of GitLab CI/CD with Scala
-applications using SBT. You can view or fork the [example project](https://gitlab.com/gitlab-examples/scala-sbt)
-and view the logs of its past [CI jobs](https://gitlab.com/gitlab-examples/scala-sbt/-/jobs?scope=finished).
-
-## Add `.gitlab-ci.yml` file to project
-
-The following `.gitlab-ci.yml` should be added in the root of your
-repository to trigger CI:
-
-``` yaml
-image: openjdk:8
-
-stages:
- - test
- - deploy
-
-before_script:
- - apt-get update -y
- - apt-get install apt-transport-https -y
- ## Install SBT
- - echo "deb http://dl.bintray.com/sbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list
- - apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 642AC823
- - apt-get update -y
- - apt-get install sbt -y
- - sbt sbtVersion
-
-test:
- stage: test
- script:
- - sbt clean coverage test coverageReport
-
-deploy:
- stage: deploy
- script:
- - apt-get update -yq
- - apt-get install rubygems ruby-dev -y
- - gem install dpl
- - dpl --provider=heroku --app=gitlab-play-sample-app --api-key=$HEROKU_API_KEY
-```
-
-In the above configuration:
-
-- The `before_script` installs [SBT](https://www.scala-sbt.org/) and
- displays the version that is being used.
-- The `test` stage executes SBT to compile and test the project.
- - [sbt-scoverage](https://github.com/scoverage/sbt-scoverage) is used as an SBT
- plugin to measure test coverage.
-- The `deploy` stage automatically deploys the project to Heroku using dpl.
-
-You can use other versions of Scala and SBT by defining them in
-`build.sbt`.
-
-## Display test coverage in job
-
-Add the `Coverage was \[\d+.\d+\%\]` regular expression in the
-**Settings > Pipelines > Coverage report** project setting to
-retrieve the [test coverage](../pipelines/settings.md#test-coverage-report-badge)
-rate from the build trace and have it displayed with your jobs.
-
-**Pipelines** must be enabled for this option to appear.
-
-## Heroku application
-
-A Heroku application is required. You can create one through the
-[Dashboard](https://dashboard.heroku.com/). Substitute `gitlab-play-sample-app`
-in the `.gitlab-ci.yml` file with your application's name.
-
-## Heroku API key
-
-You can look up your Heroku API key in your
-[account](https://dashboard.heroku.com/account). Add a [protected variable](../variables/README.md#protect-a-custom-variable) with
-this value in **Project âž” Variables** with key `HEROKU_API_KEY`.
+<!-- This redirect file can be deleted after 2021-04-18. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 96bdad0cf82..56594103362 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -4450,22 +4450,31 @@ You can use [YAML anchors](#anchors) with [script](#script), [`before_script`](#
and [`after_script`](#after_script) to use predefined commands in multiple jobs:
```yaml
-.some-script: &some-script
- - echo "Execute this script in `before_script` sections"
-
.some-script-before: &some-script-before
- - echo "Execute this script in `script` sections"
+ - echo "Execute this script first"
+
+.some-script: &some-script
+ - echo "Execute this script second"
+ - echo "Execute this script too"
.some-script-after: &some-script-after
- - echo "Execute this script in `after_script` sections"
+ - echo "Execute this script last"
-job_name:
+job1:
before_script:
- *some-script-before
script:
- *some-script
+ - echo "Execute something, for this job only"
after_script:
- *some-script-after
+
+job2:
+ script:
+ - *some-script-before
+ - *some-script
+ - echo "Execute something else, for this job only"
+ - *some-script-after
```
#### YAML anchors for variables
diff --git a/doc/development/README.md b/doc/development/README.md
index 0d3c1b3cbe9..087b2b2e7eb 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -151,6 +151,7 @@ In these cases, use the following workflow:
## Backend guides
+- [Directory structure](directory_structure.md)
- [GitLab utilities](utilities.md)
- [Issuable-like Rails models](issuable-like-models.md)
- [Logging](logging.md)
diff --git a/doc/development/directory_structure.md b/doc/development/directory_structure.md
new file mode 100644
index 00000000000..c2329feb941
--- /dev/null
+++ b/doc/development/directory_structure.md
@@ -0,0 +1,36 @@
+---
+stage: none
+group: unassigned
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Backend directory structure
+
+## Use namespaces to define bounded contexts
+
+A healthy application is divided into macro and sub components that represent the contexts at play,
+whether they are related to business domain or infrastructure code.
+
+As GitLab code has so many features and components it's hard to see what contexts are involved.
+We should expect any class to be defined inside a module/namespace that represents the contexts where it operates.
+
+When we namespace classes inside their domain:
+
+- Similar terminology becomes unambiguous as the domain clarifies the meaning:
+ For example, `MergeRequests::Diff` and `Notes::Diff`.
+- Top-level namespaces could be associated to one or more groups identified as domain experts.
+- We can better identify the interactions and coupling between components.
+ For example, several classes inside `MergeRequests::` domain interact more with `Ci::`
+ domain and less with `ImportExport::`.
+
+```ruby
+# bad
+class MyClass
+end
+
+# good
+module MyDomain
+ class MyClass
+ end
+end
+```
diff --git a/doc/development/usage_ping.md b/doc/development/usage_ping.md
index 10c3de2f0a1..f3203379994 100644
--- a/doc/development/usage_ping.md
+++ b/doc/development/usage_ping.md
@@ -122,6 +122,60 @@ sequenceDiagram
the hostname is `version.gitlab.com`, the protocol is `TCP`, and the port number is `443`,
the required URL is <https://version.gitlab.com/>.
+## Usage Ping Metric Life cycle
+
+### 1. New metrics addition
+
+Please follow the [Implementing Usage Ping](#implementing-usage-ping) guide.
+
+### 2. Existing metric change
+
+Because we do not control when customers update their self-managed instances of GitLab,
+we **STRONGLY DISCOURAGE** changes to the logic used to calculate any metric.
+Any such changes lead to inconsistent reports from multiple GitLab instances.
+If there is a problem with an existing metric, it's best to deprecate the existing metric,
+and use it, side by side, with the desired new metric.
+
+Example:
+Consider following change. Before GitLab 12.6, the `example_metric` was implemented as:
+
+```ruby
+{
+ ...
+ example_metric: distinct_count(Project, :creator_id)
+}
+```
+
+For GitLab 12.6, the metric was changed to filter out archived projects:
+
+```ruby
+{
+ ...
+ example_metric: distinct_count(Project.non_archived, :creator_id)
+}
+```
+
+In this scenario all instances running up to GitLab 12.5 continue to report `example_metric`,
+including all archived projects, while all instances running GitLab 12.6 and higher filters
+out such projects. As Usage Ping data is collected from all reporting instances, the
+resulting dataset includes mixed data, which distorts any following business analysis.
+
+The correct approach is to add a new metric for GitLab 12.6 release with updated logic:
+
+```ruby
+{
+ ...
+ example_metric_without_archived: distinct_count(Project.non_archived, :creator_id)
+}
+```
+
+and update existing business analysis artefacts to use `example_metric_without_archived` instead of `example_metric`
+
+### 3. Metrics deprecation and removal
+
+The process for deprecating and removing metrics is currently under development. For
+more information, see the following [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/284637).
+
## Implementing Usage Ping
Usage Ping consists of two kinds of data, counters and observations. Counters track how often a certain event
@@ -446,6 +500,8 @@ Implemented using Redis methods [PFADD](https://redis.io/commands/pfadd) and [PF
Aggregation on a `daily` basis does not pull more fine grained data.
- `feature_flag`: optional. For details, see our [GitLab internal Feature flags](feature_flags/) documentation.
+Use one of the following methods to track events:
+
1. Track event in controller using `RedisTracking` module with `track_redis_hll_event(*controller_actions, name:, feature:, feature_default_enabled: false)`.
Arguments:
@@ -562,21 +618,6 @@ Implemented using Redis methods [PFADD](https://redis.io/commands/pfadd) and [PF
api.trackRedisHllUserEvent('my_already_defined_event_name'),
```
-1. Track event using base module `Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event_name, values:)`.
-
- Arguments:
-
- - `event_name`: event name.
- - `values`: One value or array of values we count. For example: user_id, visitor_id, user_ids.
-
-1. Track event on context level using base module `Gitlab::UsageDataCounters::HLLRedisCounter.track_event_in_context(event_name, values:, context:)`.
-
- Arguments:
-
- - `event_name`: event name.
- - `values`: values we count. For example: user_id, visitor_id.
- - `context`: context value. Allowed values are `default`, `free`, `bronze`, `silver`, `gold`, `starter`, `premium`, `ultimate`
-
1. Get event data using `Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names:, start_date:, end_date:, context: '')`.
Arguments:
diff --git a/doc/user/packages/maven_repository/index.md b/doc/user/packages/maven_repository/index.md
index e0f5a400977..58730cd6682 100644
--- a/doc/user/packages/maven_repository/index.md
+++ b/doc/user/packages/maven_repository/index.md
@@ -186,10 +186,11 @@ published to the GitLab Package Registry.
## Authenticate to the Package Registry with Maven
-To authenticate to the Package Registry, you need either a personal access token or deploy token.
+To authenticate to the Package Registry, you need one of the following:
-- If you use a [personal access token](../../../user/profile/personal_access_tokens.md), set the scope to `api`.
-- If you use a [deploy token](../../project/deploy_tokens/index.md), set the scope to `read_package_registry`, `write_package_registry`, or both.
+- A [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `api`.
+- A [deploy token](../../project/deploy_tokens/index.md) with the scope set to `read_package_registry`, `write_package_registry`, or both.
+- A [CI_JOB_TOKEN](#authenticate-with-a-ci-job-token-in-maven).
### Authenticate with a personal access token in Maven
@@ -354,12 +355,13 @@ repositories {
To use the GitLab endpoint for Maven packages, choose an option:
-- **Project-level**: Use when you have few Maven packages and they are not in
- the same GitLab group.
-- **Group-level**: Use when you have many Maven packages in the same GitLab
- group.
-- **Instance-level**: Use when you have many Maven packages in different
- GitLab groups or in their own namespace.
+- **Project-level**: To publish Maven packages to a project, use a project-level endpoint.
+ To install Maven packages, use a project-level endpoint when you have few Maven packages
+ and they are not in the same GitLab group.
+- **Group-level**: Use a group-level endpoint when you want to install packages from
+ many different projects in the same GitLab group.
+- **Instance-level**: Use an instance-level endpoint when you want to install many
+ packages from different GitLab groups or in their own namespace.
The option you choose determines the settings you add to your `pom.xml` file.
@@ -533,7 +535,7 @@ repositories {
After you have set up the [remote and authentication](#authenticate-to-the-package-registry-with-maven)
and [configured your project](#use-the-gitlab-endpoint-for-maven-packages),
-publish a Maven artifact from your project.
+publish a Maven package to your project.
### Publish by using Maven
@@ -604,11 +606,20 @@ To publish a package by using Gradle:
Now navigate to your project's **Packages & Registries** page and view the published artifacts.
+### Publishing a package with the same name or version
+
+When you publish a package with the same name or version as an existing package,
+the existing package is overwritten.
+
## Install a package
To install a package from the GitLab Package Registry, you must configure
the [remote and authenticate](#authenticate-to-the-package-registry-with-maven).
-When this is completed, there are two ways to install a package.
+When this is completed, you can install a package from a project,
+group, or namespace.
+
+If multiple packages have the same name and version, when you install
+a package, the most recently-published package is retrieved.
### Use Maven with `mvn install`
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index 74311eefd83..d07905c0ead 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -119,7 +119,7 @@ and modify them if you have the necessary [permissions](../../permissions.md).
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17589) in GitLab 13.3.
Assignees in the sidebar are updated in real time. This feature is **disabled by default**.
-To enable, you need to enable [ActionCable in-app mode](https://docs.gitlab.com/omnibus/settings/actioncable.html).
+To enable it, you need to enable [ActionCable in-app mode](https://docs.gitlab.com/omnibus/settings/actioncable.html).
### Issues List
@@ -137,7 +137,22 @@ view, you can also make certain changes [in bulk](../bulk_editing.md) to the dis
For more information, see the [Issue Data and Actions](issue_data_and_actions.md) page
for a rundown of all the fields and information in an issue.
-You can sort a list of issues in several ways, for example by issue creation date, milestone due date. For more information, see the [Sorting and Ordering Issue Lists](sorting_issue_lists.md) page.
+You can sort a list of issues in several ways, for example by issue creation date, milestone due date.
+For more information, see the [Sorting and ordering issue lists](sorting_issue_lists.md) page.
+
+#### Cached issue count
+
+> - [Introduced]([link-to-issue](https://gitlab.com/gitlab-org/gitlab/-/issues/243753)) in GitLab 13.9.
+> - It's [deployed behind a feature flag](../../feature_flags.md), disabled by default.
+> - It's disabled on GitLab.com.
+> - It's not recommended for production use.
+> - To use this feature in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-cached-issue-count) **(CORE ONLY)**
+
+WARNING:
+This feature might not be available to you. Check the **version history** note above for details.
+
+In a group, the sidebar displays the total count of open issues and this value is cached if higher
+than 1000. The cached value is rounded to thousands (or millions) and updated every 24 hours.
### Issue boards
@@ -226,3 +241,22 @@ You can then see issue statuses in the [issue list](#issues-list) and the
- [Issues API](../../../api/issues.md)
- Configure an [external issue tracker](../../../integration/external-issue-tracker.md)
such as Jira, Redmine, Bugzilla, or EWM.
+
+## Enable or disable cached issue count **(CORE ONLY)**
+
+Cached issue count in the left sidebar is under development and not ready for production use. It is
+deployed behind a feature flag that is **disabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
+can enable it.
+
+To enable it:
+
+```ruby
+Feature.enable(:cached_sidebar_open_issues_count)
+```
+
+To disable it:
+
+```ruby
+Feature.disable(:cached_sidebar_open_issues_count)
+```
diff --git a/lib/gitlab/experimentation/controller_concern.rb b/lib/gitlab/experimentation/controller_concern.rb
index 4b1d3663095..2b38b12c914 100644
--- a/lib/gitlab/experimentation/controller_concern.rb
+++ b/lib/gitlab/experimentation/controller_concern.rb
@@ -140,7 +140,7 @@ module Gitlab
cookies[:force_experiment].to_s.split(',').any? { |experiment| experiment.strip == experiment_key.to_s }
end
- def tracking_label(subject)
+ def tracking_label(subject = nil)
return experimentation_subject_id if subject.blank?
if subject.respond_to?(:to_global_id)
diff --git a/lib/tasks/benchmark.rake b/lib/tasks/benchmark.rake
new file mode 100644
index 00000000000..6deafb2c351
--- /dev/null
+++ b/lib/tasks/benchmark.rake
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+return if Rails.env.production?
+
+namespace :benchmark do
+ desc 'Benchmark | Banzai pipeline/filters'
+ RSpec::Core::RakeTask.new(:banzai) do |t|
+ t.pattern = 'spec/benchmarks/banzai_benchmark.rb'
+ ENV['BENCHMARK'] = '1'
+ end
+end
diff --git a/lib/tasks/gitlab/pages.rake b/lib/tasks/gitlab/pages.rake
index e15cbb4e32e..107e0d08b70 100644
--- a/lib/tasks/gitlab/pages.rake
+++ b/lib/tasks/gitlab/pages.rake
@@ -6,7 +6,8 @@ namespace :gitlab do
task migrate_legacy_storage: :gitlab_environment do
logger = Logger.new(STDOUT)
logger.info('Starting to migrate legacy pages storage to zip deployments')
- processed_projects = 0
+ projects_migrated = 0
+ projects_errored = 0
ProjectPagesMetadatum.only_on_legacy_storage.each_batch(of: 10) do |batch|
batch.preload(project: [:namespace, :route, pages_metadatum: :pages_deployment]).each do |metadatum|
@@ -16,20 +17,26 @@ namespace :gitlab do
time = Benchmark.realtime do
result = ::Pages::MigrateLegacyStorageToDeploymentService.new(project).execute
end
- processed_projects += 1
if result[:status] == :success
logger.info("project_id: #{project.id} #{project.pages_path} has been migrated in #{time} seconds")
+ projects_migrated += 1
else
logger.error("project_id: #{project.id} #{project.pages_path} failed to be migrated in #{time} seconds: #{result[:message]}")
+ projects_errored += 1
end
rescue => e
+ projects_errored += 1
logger.error("#{e.message} project_id: #{project&.id}")
Gitlab::ErrorTracking.track_exception(e, project_id: project&.id)
end
- logger.info("#{processed_projects} pages projects are processed")
+ logger.info("#{projects_migrated} projects are migrated successfully, #{projects_errored} projects failed to be migrated")
end
+
+ logger.info("A total of #{projects_migrated + projects_errored} projects were processed.")
+ logger.info("- The #{projects_migrated} projects migrated successfully")
+ logger.info("- The #{projects_errored} projects failed to be migrated")
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 317678714e3..b3fecb8f883 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4436,6 +4436,9 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
+msgid "Beta"
+msgstr ""
+
msgid "Bi-weekly code coverage"
msgstr ""
@@ -13376,9 +13379,6 @@ msgstr ""
msgid "GitLabPages|It may take up to 30 minutes before the site is available after the first deployment."
msgstr ""
-msgid "GitLabPages|Learn how to upload your static site and have it served by GitLab by following the %{link_start}documentation on GitLab Pages%{link_end}."
-msgstr ""
-
msgid "GitLabPages|Learn more."
msgstr ""
@@ -13406,6 +13406,9 @@ msgstr ""
msgid "GitLabPages|Save"
msgstr ""
+msgid "GitLabPages|See the %{docs_link_start}GitLab Pages documentation%{link_end} to learn how to upload your static site and have GitLab serve it. You can also follow a %{samples_link_start}sample project%{link_end} or use a %{templates_link_start}GitLab CI template%{link_end}."
+msgstr ""
+
msgid "GitLabPages|Something went wrong while obtaining the Let's Encrypt certificate for %{domain}. To retry visit your %{link_start}domain details%{link_end}."
msgstr ""
@@ -14255,6 +14258,9 @@ msgstr ""
msgid "GroupsNew|No import options available"
msgstr ""
+msgid "GroupsNew|Not all related objects are migrated, as %{docs_link_start}described here%{docs_link_end}. Please %{feedback_link_start}leave feedback%{feedback_link_end} on this feature."
+msgstr ""
+
msgid "GroupsNew|Personal access token"
msgstr ""
diff --git a/package.json b/package.json
index a5ca4851efa..202c63a63ea 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "1.178.0",
"@gitlab/tributejs": "1.0.0",
- "@gitlab/ui": "25.11.3",
+ "@gitlab/ui": "25.12.2",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-4",
"@rails/ujs": "^6.0.3-4",
diff --git a/qa/Dockerfile b/qa/Dockerfile
index d040bddfc7f..76c81d03071 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -3,8 +3,8 @@ LABEL maintainer="GitLab Quality Department <quality@gitlab.com>"
ENV DEBIAN_FRONTEND="noninteractive"
ENV DOCKER_VERSION="17.09.0-ce"
-ENV CHROME_VERSION="84.0.4147.89-1"
-ENV CHROME_DRIVER_VERSION="84.0.4147.30"
+ENV CHROME_VERSION="87.0.4280.141-1"
+ENV CHROME_DRIVER_VERSION="87.0.4280.88"
ENV CHROME_DEB="google-chrome-stable_${CHROME_VERSION}_amd64.deb"
ENV CHROME_URL="https://s3.amazonaws.com/gitlab-google-chrome-stable/${CHROME_DEB}"
diff --git a/spec/benchmarks/banzai_benchmark.rb b/spec/benchmarks/banzai_benchmark.rb
new file mode 100644
index 00000000000..e489237a2f2
--- /dev/null
+++ b/spec/benchmarks/banzai_benchmark.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+if ENV.key?('BENCHMARK')
+ require 'spec_helper'
+ require 'erb'
+ require 'benchmark/ips'
+
+ # This benchmarks some of the Banzai pipelines and filters.
+ # They are not definitive, but can be used by a developer to
+ # get a rough idea how the changing or addition of a new filter
+ # will effect performance.
+ #
+ # Run by:
+ # BENCHMARK=1 rspec spec/benchmarks/banzai_benchmark.rb
+ # or
+ # rake benchmark:banzai
+ #
+ RSpec.describe 'GitLab Markdown Benchmark', :aggregate_failures do
+ include MarkupHelper
+
+ let_it_be(:feature) { MarkdownFeature.new }
+ let_it_be(:project) { feature.project }
+ let_it_be(:group) { feature.group }
+ let_it_be(:wiki) { feature.wiki }
+ let_it_be(:wiki_page) { feature.wiki_page }
+ let_it_be(:markdown_text) { feature.raw_markdown }
+
+ let!(:render_context) { Banzai::RenderContext.new(project, current_user) }
+
+ before do
+ stub_application_setting(asset_proxy_enabled: true)
+ stub_application_setting(asset_proxy_secret_key: 'shared-secret')
+ stub_application_setting(asset_proxy_url: 'https://assets.example.com')
+ stub_application_setting(asset_proxy_whitelist: %w(gitlab.com *.mydomain.com))
+
+ Banzai::Filter::AssetProxyFilter.initialize_settings
+ end
+
+ context 'pipelines' do
+ it 'benchmarks several pipelines' do
+ path = 'images/example.jpg'
+ gitaly_wiki_file = Gitlab::GitalyClient::WikiFile.new(path: path)
+ allow(wiki).to receive(:find_file).with(path).and_return(Gitlab::Git::WikiFile.new(gitaly_wiki_file))
+ allow(wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' }
+
+ puts "\n--> Benchmarking Full, Wiki, and Plain pipelines\n"
+
+ Benchmark.ips do |x|
+ x.config(time: 10, warmup: 2)
+
+ x.report('Full pipeline') { markdown(markdown_text, { pipeline: :full }) }
+ x.report('Wiki pipeline') { markdown(markdown_text, { pipeline: :wiki, wiki: wiki, page_slug: wiki_page.slug }) }
+ x.report('Plain pipeline') { markdown(markdown_text, { pipeline: :plain_markdown }) }
+
+ x.compare!
+ end
+ end
+ end
+
+ context 'filters' do
+ let(:context) do
+ tmp = { project: project, current_user: current_user, render_context: render_context }
+ Banzai::Filter::AssetProxyFilter.transform_context(tmp)
+ end
+
+ it 'benchmarks all filters in the FullPipeline' do
+ benchmark_pipeline_filters(:full)
+ end
+
+ it 'benchmarks all filters in the PlainMarkdownPipeline' do
+ benchmark_pipeline_filters(:plain_markdown)
+ end
+ end
+
+ # build up the source text for each filter
+ def build_filter_text(pipeline, initial_text)
+ filter_source = {}
+ input_text = initial_text
+
+ pipeline.filters.each do |filter_klass|
+ filter_source[filter_klass] = input_text
+
+ output = filter_klass.call(input_text, context)
+ input_text = output
+ end
+
+ filter_source
+ end
+
+ def benchmark_pipeline_filters(pipeline_type)
+ pipeline = Banzai::Pipeline[pipeline_type]
+ filter_source = build_filter_text(pipeline, markdown_text)
+
+ puts "\n--> Benchmarking #{pipeline.name.demodulize} filters\n"
+
+ Benchmark.ips do |x|
+ x.config(time: 10, warmup: 2)
+
+ pipeline.filters.each do |filter_klass|
+ label = filter_klass.name.demodulize.delete_suffix('Filter').truncate(20)
+
+ x.report(label) { filter_klass.call(filter_source[filter_klass], context) }
+ end
+
+ x.compare!
+ end
+ end
+
+ # Fake a `current_user` helper
+ def current_user
+ feature.user
+ end
+ end
+end
diff --git a/spec/features/groups/import_export/connect_instance_spec.rb b/spec/features/groups/import_export/connect_instance_spec.rb
index 2e1bf27ba8b..98212df0c01 100644
--- a/spec/features/groups/import_export/connect_instance_spec.rb
+++ b/spec/features/groups/import_export/connect_instance_spec.rb
@@ -37,6 +37,7 @@ RSpec.describe 'Import/Export - Connect to another instance', :js do
)
expect(page).to have_content 'Import groups from another instance of GitLab'
+ expect(page).to have_content 'Not all related objects are migrated'
fill_in :bulk_import_gitlab_url, with: source_url
fill_in :bulk_import_gitlab_access_token, with: pat
diff --git a/spec/finders/terraform/states_finder_spec.rb b/spec/finders/terraform/states_finder_spec.rb
new file mode 100644
index 00000000000..260e5f4818f
--- /dev/null
+++ b/spec/finders/terraform/states_finder_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Terraform::StatesFinder do
+ describe '#execute' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:state_1) { create(:terraform_state, project: project) }
+ let_it_be(:state_2) { create(:terraform_state, project: project) }
+
+ let(:user) { project.creator }
+
+ subject { described_class.new(project, user).execute }
+
+ it { is_expected.to contain_exactly(state_1, state_2) }
+
+ context 'user does not have permission' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_guest(user)
+ end
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'filtering by name' do
+ let(:params) { { name: name_param } }
+
+ subject { described_class.new(project, user, params: params).execute }
+
+ context 'name does not match' do
+ let(:name_param) { 'other-name' }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'name does match' do
+ let(:name_param) { state_1.name }
+
+ it { is_expected.to contain_exactly(state_1) }
+ end
+ end
+ end
+end
diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js
index 66fc74525ad..c4301f63470 100644
--- a/spec/frontend/notes/stores/mutation_spec.js
+++ b/spec/frontend/notes/stores/mutation_spec.js
@@ -400,6 +400,19 @@ describe('Notes Store mutations', () => {
expect(state.discussions[0].notes[0].note).toEqual('Foo');
});
+ it('does not update existing note if it matches', () => {
+ const state = {
+ discussions: [{ ...individualNote, individual_note: false }],
+ };
+ jest.spyOn(state.discussions[0].notes, 'splice');
+
+ const updated = individualNote.notes[0];
+
+ mutations.UPDATE_NOTE(state, updated);
+
+ expect(state.discussions[0].notes.splice).not.toHaveBeenCalled();
+ });
+
it('transforms an individual note to discussion', () => {
const state = {
discussions: [individualNote],
diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js
index ee509eaad8d..01d5a99c037 100644
--- a/spec/frontend/search/mock_data.js
+++ b/spec/frontend/search/mock_data.js
@@ -26,7 +26,7 @@ export const MOCK_GROUPS = [
export const MOCK_PROJECT = {
name: 'test project',
- namespace_id: MOCK_GROUP.id,
+ namespace: MOCK_GROUP,
nameWithNamespace: 'test group test project',
id: 'test_1',
};
@@ -34,13 +34,13 @@ export const MOCK_PROJECT = {
export const MOCK_PROJECTS = [
{
name: 'test project',
- namespace_id: MOCK_GROUP.id,
+ namespace: MOCK_GROUP,
name_with_namespace: 'test group test project',
id: 'test_1',
},
{
name: 'test project 2',
- namespace_id: MOCK_GROUP.id,
+ namespace: MOCK_GROUP,
name_with_namespace: 'test group test project 2',
id: 'test_2',
},
diff --git a/spec/frontend/search/topbar/components/project_filter_spec.js b/spec/frontend/search/topbar/components/project_filter_spec.js
index c1fc61d7e89..f2ac8f2689d 100644
--- a/spec/frontend/search/topbar/components/project_filter_spec.js
+++ b/spec/frontend/search/topbar/components/project_filter_spec.js
@@ -99,7 +99,7 @@ describe('ProjectFilter', () => {
it('calls setUrlParams with project id, group id, then calls visitUrl', () => {
expect(setUrlParams).toHaveBeenCalledWith({
- [GROUP_DATA.queryParam]: MOCK_PROJECT.namespace_id,
+ [GROUP_DATA.queryParam]: MOCK_PROJECT.namespace.id,
[PROJECT_DATA.queryParam]: MOCK_PROJECT.id,
});
expect(visitUrl).toHaveBeenCalled();
diff --git a/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb b/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb
new file mode 100644
index 00000000000..ed03a1cb906
--- /dev/null
+++ b/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Security::CiConfiguration::ConfigureSast do
+ subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
+
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ let_it_be(:service_result_json) do
+ {
+ status: "success",
+ success_path: "http://127.0.0.1:3000/root/demo-historic-secrets/-/merge_requests/new?",
+ errors: nil
+ }
+ end
+
+ let_it_be(:service_error_result_json) do
+ {
+ status: "error",
+ success_path: nil,
+ errors: %w(error1 error2)
+ }
+ end
+
+ let(:context) do
+ GraphQL::Query::Context.new(
+ query: OpenStruct.new(schema: nil),
+ values: { current_user: user },
+ object: nil
+ )
+ end
+
+ specify { expect(described_class).to require_graphql_authorizations(:push_code) }
+
+ describe '#resolve' do
+ subject { mutation.resolve(project_path: project.full_path, configuration: {}) }
+
+ let(:result) { subject }
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'when user does not have enough permissions' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when user is a maintainer of a different project' do
+ before do
+ create(:project_empty_repo).add_maintainer(user)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when the user does not have permission to create a new branch' do
+ before_all do
+ project.add_developer(user)
+ end
+
+ let(:error_message) { 'You are not allowed to create protected branches on this project.' }
+
+ it 'returns an array of errors' do
+ allow_next_instance_of(::Files::MultiService) do |multi_service|
+ allow(multi_service).to receive(:execute).and_raise(Gitlab::Git::PreReceiveError.new("GitLab: #{error_message}"))
+ end
+
+ expect(result).to match(
+ status: :error,
+ success_path: nil,
+ errors: match_array([error_message])
+ )
+ end
+ end
+
+ context 'when the user can create a merge request' do
+ before_all do
+ project.add_developer(user)
+ end
+
+ context 'when service successfully generates a path to create a new merge request' do
+ it 'returns a success path' do
+ allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service|
+ allow(service).to receive(:execute).and_return(service_result_json)
+ end
+
+ expect(result).to match(
+ status: 'success',
+ success_path: service_result_json[:success_path],
+ errors: []
+ )
+ end
+ end
+
+ context 'when service can not generate any path to create a new merge request' do
+ it 'returns an array of errors' do
+ allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service|
+ allow(service).to receive(:execute).and_return(service_error_result_json)
+ end
+
+ expect(result).to match(
+ status: 'error',
+ success_path: be_nil,
+ errors: match_array(service_error_result_json[:errors])
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/terraform/states_resolver_spec.rb b/spec/graphql/resolvers/terraform/states_resolver_spec.rb
index 64b515528cd..91d48cd782b 100644
--- a/spec/graphql/resolvers/terraform/states_resolver_spec.rb
+++ b/spec/graphql/resolvers/terraform/states_resolver_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Resolvers::Terraform::StatesResolver do
include GraphqlHelpers
- it { expect(described_class.type).to eq(Types::Terraform::StateType) }
+ it { expect(described_class).to have_nullable_graphql_type(Types::Terraform::StateType.connection_type) }
it { expect(described_class.null).to be_truthy }
describe '#resolve' do
@@ -31,3 +31,21 @@ RSpec.describe Resolvers::Terraform::StatesResolver do
end
end
end
+
+RSpec.describe Resolvers::Terraform::StatesResolver.single do
+ it { expect(described_class).to be < Resolvers::Terraform::StatesResolver }
+
+ describe 'arguments' do
+ subject { described_class.arguments[argument] }
+
+ describe 'name' do
+ let(:argument) { 'name' }
+
+ it do
+ expect(subject).to be_present
+ expect(subject.type.to_s).to eq('String!')
+ expect(subject.description).to be_present
+ end
+ end
+ end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 8f6a120110f..95c835773e1 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -318,6 +318,13 @@ RSpec.describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) }
end
+ describe 'terraform state field' do
+ subject { described_class.fields['terraformState'] }
+
+ it { is_expected.to have_graphql_type(Types::Terraform::StateType) }
+ it { is_expected.to have_graphql_resolver(Resolvers::Terraform::StatesResolver.single) }
+ end
+
describe 'terraform states field' do
subject { described_class.fields['terraformStates'] }
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 8eb1b7b3b3d..61aaa618c45 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -444,4 +444,82 @@ RSpec.describe GroupsHelper do
end
end
end
+
+ describe '#group_open_issues_count' do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:count_service) { Groups::OpenIssuesCountService }
+
+ before do
+ allow(helper).to receive(:current_user) { current_user }
+ end
+
+ context 'when cached_sidebar_open_issues_count feature flag is enabled' do
+ before do
+ stub_feature_flags(cached_sidebar_open_issues_count: true)
+ end
+
+ it 'returns count value from cache' do
+ allow_next_instance_of(count_service) do |service|
+ allow(service).to receive(:count).and_return(2500)
+ end
+
+ expect(helper.group_open_issues_count(group)).to eq('2.5k')
+ end
+ end
+
+ context 'when cached_sidebar_open_issues_count feature flag is disabled' do
+ before do
+ stub_feature_flags(cached_sidebar_open_issues_count: false)
+ end
+
+ it 'returns not cached issues count' do
+ allow(helper).to receive(:group_issues_count).and_return(2500)
+
+ expect(helper.group_open_issues_count(group)).to eq('2,500')
+ end
+ end
+ end
+
+ describe '#cached_open_group_issues_count' do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:group) { create(:group, name: 'group') }
+ let_it_be(:count_service) { Groups::OpenIssuesCountService }
+
+ before do
+ allow(helper).to receive(:current_user) { current_user }
+ end
+
+ it 'returns all digits for count value under 1000' do
+ allow_next_instance_of(count_service) do |service|
+ allow(service).to receive(:count).and_return(999)
+ end
+
+ expect(helper.cached_open_group_issues_count(group)).to eq('999')
+ end
+
+ it 'returns truncated digits for count value over 1000' do
+ allow_next_instance_of(count_service) do |service|
+ allow(service).to receive(:count).and_return(2300)
+ end
+
+ expect(helper.cached_open_group_issues_count(group)).to eq('2.3k')
+ end
+
+ it 'returns truncated digits for count value over 10000' do
+ allow_next_instance_of(count_service) do |service|
+ allow(service).to receive(:count).and_return(12560)
+ end
+
+ expect(helper.cached_open_group_issues_count(group)).to eq('12.6k')
+ end
+
+ it 'returns truncated digits for count value over 100000' do
+ allow_next_instance_of(count_service) do |service|
+ allow(service).to receive(:count).and_return(112560)
+ end
+
+ expect(helper.cached_open_group_issues_count(group)).to eq('112.6k')
+ end
+ end
end
diff --git a/spec/helpers/invite_members_helper_spec.rb b/spec/helpers/invite_members_helper_spec.rb
index ee7a30cd4cb..576021b37b3 100644
--- a/spec/helpers/invite_members_helper_spec.rb
+++ b/spec/helpers/invite_members_helper_spec.rb
@@ -7,6 +7,10 @@ RSpec.describe InviteMembersHelper do
let_it_be(:developer) { create(:user, developer_projects: [project]) }
let(:owner) { project.owner }
+ before do
+ helper.extend(Gitlab::Experimentation::ControllerConcern)
+ end
+
context 'with project' do
before do
assign(:project, project)
@@ -202,7 +206,6 @@ RSpec.describe InviteMembersHelper do
before do
allow(helper).to receive(:experiment_tracking_category_and_group) { '_track_property_' }
- allow(helper).to receive(:tracking_label)
allow(helper).to receive(:current_user) { owner }
end
diff --git a/spec/models/terraform/state_spec.rb b/spec/models/terraform/state_spec.rb
index ed311314086..4eb03554378 100644
--- a/spec/models/terraform/state_spec.rb
+++ b/spec/models/terraform/state_spec.rb
@@ -25,6 +25,15 @@ RSpec.describe Terraform::State do
it { expect(subject.map(&:name)).to eq(names.sort) }
end
+
+ describe '.with_name' do
+ let_it_be(:matching_name) { create(:terraform_state, name: 'matching-name') }
+ let_it_be(:other_name) { create(:terraform_state, name: 'other-name') }
+
+ subject { described_class.with_name(matching_name.name) }
+
+ it { is_expected.to contain_exactly(matching_name) }
+ end
end
describe '#destroy' do
diff --git a/spec/requests/api/graphql/project/terraform/state_spec.rb b/spec/requests/api/graphql/project/terraform/state_spec.rb
new file mode 100644
index 00000000000..9f1d9ab204a
--- /dev/null
+++ b/spec/requests/api/graphql/project/terraform/state_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'query a single terraform state' do
+ include GraphqlHelpers
+ include ::API::Helpers::RelatedResourcesHelpers
+
+ let_it_be(:terraform_state) { create(:terraform_state, :with_version, :locked) }
+
+ let(:latest_version) { terraform_state.latest_version }
+ let(:project) { terraform_state.project }
+ let(:current_user) { project.creator }
+ let(:data) { graphql_data.dig('project', 'terraformState') }
+
+ let(:query) do
+ graphql_query_for(
+ :project,
+ { fullPath: project.full_path },
+ query_graphql_field(
+ :terraformState,
+ { name: terraform_state.name },
+ %{
+ id
+ name
+ lockedAt
+ createdAt
+ updatedAt
+
+ latestVersion {
+ id
+ serial
+ createdAt
+ updatedAt
+
+ createdByUser {
+ id
+ }
+
+ job {
+ name
+ }
+ }
+
+ lockedByUser {
+ id
+ }
+ }
+ )
+ )
+ end
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns terraform state data' do
+ expect(data).to match(a_hash_including({
+ 'id' => global_id_of(terraform_state),
+ 'name' => terraform_state.name,
+ 'lockedAt' => terraform_state.locked_at.iso8601,
+ 'createdAt' => terraform_state.created_at.iso8601,
+ 'updatedAt' => terraform_state.updated_at.iso8601,
+ 'lockedByUser' => { 'id' => global_id_of(terraform_state.locked_by_user) },
+ 'latestVersion' => {
+ 'id' => eq(global_id_of(latest_version)),
+ 'serial' => eq(latest_version.version),
+ 'createdAt' => eq(latest_version.created_at.iso8601),
+ 'updatedAt' => eq(latest_version.updated_at.iso8601),
+ 'createdByUser' => { 'id' => eq(global_id_of(latest_version.created_by_user)) },
+ 'job' => { 'name' => eq(latest_version.build.name) }
+ }
+ }))
+ end
+
+ context 'unauthorized users' do
+ let(:current_user) { nil }
+
+ it { expect(data).to be_nil }
+ end
+end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index c7756a4fae5..1c359b6e50f 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -54,7 +54,7 @@ RSpec.describe API::Groups do
it_behaves_like 'invalid file upload request'
end
- context 'when file format is not supported' do
+ context 'when file is too large' do
let(:file_path) { 'spec/fixtures/big-image.png' }
let(:message) { 'is too big' }
@@ -661,6 +661,7 @@ RSpec.describe API::Groups do
describe 'PUT /groups/:id' do
let(:new_group_name) { 'New Group'}
+ let(:file_path) { 'spec/fixtures/dk.png' }
it_behaves_like 'group avatar upload' do
def make_upload_request
@@ -678,7 +679,8 @@ RSpec.describe API::Groups do
request_access_enabled: true,
project_creation_level: "noone",
subgroup_creation_level: "maintainer",
- default_branch_protection: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS
+ default_branch_protection: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS,
+ avatar: fixture_file_upload(file_path)
}
expect(response).to have_gitlab_http_status(:ok)
@@ -701,6 +703,7 @@ RSpec.describe API::Groups do
expect(json_response['shared_projects']).to be_an Array
expect(json_response['shared_projects'].length).to eq(0)
expect(json_response['default_branch_protection']).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
+ expect(json_response['avatar_url']).to end_with('dk.png')
end
context 'updating the `default_branch_protection` attribute' do
diff --git a/spec/services/groups/open_issues_count_service_spec.rb b/spec/services/groups/open_issues_count_service_spec.rb
new file mode 100644
index 00000000000..8bbb1c90c6b
--- /dev/null
+++ b/spec/services/groups/open_issues_count_service_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::OpenIssuesCountService, :use_clean_rails_memory_store_caching do
+ let_it_be(:group) { create(:group, :public)}
+ let_it_be(:project) { create(:project, :public, namespace: group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:issue) { create(:issue, :opened, project: project) }
+ let_it_be(:confidential) { create(:issue, :opened, confidential: true, project: project) }
+ let_it_be(:closed) { create(:issue, :closed, project: project) }
+
+ subject { described_class.new(group, user) }
+
+ describe '#relation_for_count' do
+ before do
+ allow(IssuesFinder).to receive(:new).and_call_original
+ end
+
+ it 'uses the IssuesFinder to scope issues' do
+ expect(IssuesFinder)
+ .to receive(:new)
+ .with(user, group_id: group.id, state: 'opened', non_archived: true, include_subgroups: true, public_only: true)
+
+ subject.count
+ end
+ end
+
+ describe '#count' do
+ context 'when user is nil' do
+ it 'does not include confidential issues in the issue count' do
+ expect(described_class.new(group).count).to eq(1)
+ end
+ end
+
+ context 'when user is provided' do
+ context 'when user can read confidential issues' do
+ before do
+ group.add_reporter(user)
+ end
+
+ it 'returns the right count with confidential issues' do
+ expect(subject.count).to eq(2)
+ end
+ end
+
+ context 'when user cannot read confidential issues' do
+ before do
+ group.add_guest(user)
+ end
+
+ it 'does not include confidential issues' do
+ expect(subject.count).to eq(1)
+ end
+ end
+
+ context 'with different cache values' do
+ let(:public_count_key) { subject.cache_key(described_class::PUBLIC_COUNT_KEY) }
+ let(:under_threshold) { described_class::CACHED_COUNT_THRESHOLD - 1 }
+ let(:over_threshold) { described_class::CACHED_COUNT_THRESHOLD + 1 }
+
+ context 'when cache is empty' do
+ before do
+ Rails.cache.delete(public_count_key)
+ end
+
+ it 'refreshes cache if value over threshold' do
+ allow(subject).to receive(:uncached_count).and_return(over_threshold)
+
+ expect(subject.count).to eq(over_threshold)
+ expect(Rails.cache.read(public_count_key)).to eq(over_threshold)
+ end
+
+ it 'does not refresh cache if value under threshold' do
+ allow(subject).to receive(:uncached_count).and_return(under_threshold)
+
+ expect(subject.count).to eq(under_threshold)
+ expect(Rails.cache.read(public_count_key)).to be_nil
+ end
+ end
+
+ context 'when cached count is under the threshold value' do
+ before do
+ Rails.cache.write(public_count_key, under_threshold)
+ end
+
+ it 'does not refresh cache' do
+ expect(Rails.cache).not_to receive(:write)
+ expect(subject.count).to eq(under_threshold)
+ end
+ end
+
+ context 'when cached count is over the threshold value' do
+ before do
+ Rails.cache.write(public_count_key, over_threshold)
+ end
+
+ it 'does not refresh cache' do
+ expect(Rails.cache).not_to receive(:write)
+ expect(subject.count).to eq(over_threshold)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/security/ci_configuration/sast_create_service_spec.rb b/spec/services/security/ci_configuration/sast_create_service_spec.rb
new file mode 100644
index 00000000000..ff7ab614e08
--- /dev/null
+++ b/spec/services/security/ci_configuration/sast_create_service_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Security::CiConfiguration::SastCreateService, :snowplow do
+ describe '#execute' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ let(:params) { {} }
+
+ subject(:result) { described_class.new(project, user, params).execute }
+
+ context 'user does not belong to project' do
+ it 'returns an error status' do
+ expect(result[:status]).to eq(:error)
+ expect(result[:success_path]).to be_nil
+ end
+
+ it 'does not track a snowplow event' do
+ subject
+
+ expect_no_snowplow_event
+ end
+ end
+
+ context 'user belongs to project' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'does track the snowplow event' do
+ subject
+
+ expect_snowplow_event(
+ category: 'Security::CiConfiguration::SastCreateService',
+ action: 'create',
+ label: 'false'
+ )
+ end
+
+ it 'raises exception if the user does not have permission to create a new branch' do
+ allow(project).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "You are not allowed to create protected branches on this project.")
+
+ expect { subject }.to raise_error(Gitlab::Git::PreReceiveError)
+ end
+
+ context 'with no parameters' do
+ it 'returns the path to create a new merge request' do
+ expect(result[:status]).to eq(:success)
+ expect(result[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
+ end
+ end
+
+ context 'with parameters' do
+ let(:params) do
+ { 'stage' => 'security',
+ 'SEARCH_MAX_DEPTH' => 1,
+ 'SECURE_ANALYZERS_PREFIX' => 'new_registry',
+ 'SAST_EXCLUDED_PATHS' => 'spec,docs' }
+ end
+
+ it 'returns the path to create a new merge request' do
+ expect(result[:status]).to eq(:success)
+ expect(result[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore
index 259148fa18f..259148fa18f 100755..100644
--- a/vendor/gitignore/C++.gitignore
+++ b/vendor/gitignore/C++.gitignore
diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore
index a1c2a238a96..a1c2a238a96 100755..100644
--- a/vendor/gitignore/Java.gitignore
+++ b/vendor/gitignore/Java.gitignore
diff --git a/yarn.lock b/yarn.lock
index a075c24abb5..538c96146a4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -876,10 +876,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
-"@gitlab/ui@25.11.3":
- version "25.11.3"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-25.11.3.tgz#54719d1276f417e66904f9f951671633f1647006"
- integrity sha512-ur8UfgJ7giQZtp7pbVAwRYSWoxOzsFTpx/OpDge5EnmrH3S6YT0BOPxYs9T2HcMYN2Cejft1rhFJY+aPGxqxJA==
+"@gitlab/ui@25.12.2":
+ version "25.12.2"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-25.12.2.tgz#ad47680da4b067140e8d48a04e807352660b9cca"
+ integrity sha512-y+uks00z+4kivTYu+l2mrjYT3nfnBS+xKWIUQ9xrkZVCC069V+DffPK+jVRzzhQ67hOMP5LVdaUEOcUplgFvGA==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"