summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml25
-rw-r--r--.markdownlint.json3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue28
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue42
-rw-r--r--app/graphql/resolvers/base_resolver.rb4
-rw-r--r--app/graphql/types/base_field.rb34
-rw-r--r--app/models/concerns/spammable.rb4
-rw-r--r--changelogs/unreleased/dennis-update-ios-swift-project-template-logo.yml5
-rw-r--r--config/environments/test.rb2
-rw-r--r--doc/administration/geo/disaster_recovery/index.md2
-rw-r--r--doc/ci/runners/README.md4
-rw-r--r--doc/ci/yaml/README.md48
-rw-r--r--doc/development/pipelines.md2
-rw-r--r--doc/subscriptions/index.md408
-rw-r--r--doc/user/admin_area/activating_deactivating_users.md4
-rw-r--r--doc/user/admin_area/blocking_unblocking_users.md4
-rw-r--r--doc/user/clusters/applications.md2
-rw-r--r--doc/user/gitlab_com/index.md2
-rw-r--r--jest.config.js10
-rw-r--r--lib/gitlab/danger/commit_linter.rb3
-rw-r--r--lib/gitlab/project_template.rb2
-rw-r--r--lib/gitlab/testing/clear_thread_memory_cache_middleware.rb17
-rw-r--r--package.json7
-rw-r--r--scripts/frontend/merge_coverage_frontend.js31
-rw-r--r--scripts/frontend/parallel_ci_sequencer.js41
-rw-r--r--spec/features/issues/user_edits_issue_spec.rb7
-rw-r--r--spec/features/profiles/active_sessions_spec.rb138
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb2
-rw-r--r--spec/features/projects/snippets/create_snippet_spec.rb2
-rw-r--r--spec/features/snippets/spam_snippets_spec.rb2
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb2
-rw-r--r--spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap6
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap8
-rw-r--r--spec/graphql/types/base_field_spec.rb21
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js16
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js4
-rw-r--r--spec/lib/gitlab/danger/commit_linter_spec.rb14
-rw-r--r--spec/models/concerns/spammable_spec.rb40
-rw-r--r--spec/requests/api/graphql_spec.rb48
-rw-r--r--spec/support/capybara.rb2
-rw-r--r--yarn.lock55
42 files changed, 712 insertions, 393 deletions
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index c6f758b6719..d90bf4337e8 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -44,13 +44,15 @@ docs lint:
- .default-tags
- .default-retry
- .docs:rules:docs-lint
- image: "registry.gitlab.com/gitlab-org/gitlab-docs:docs-lint"
+ image: "registry.gitlab.com/gitlab-org/gitlab-docs:lint"
stage: test
needs: []
script:
- scripts/lint-doc.sh
# Lint Markdown
- markdownlint --config .markdownlint.json 'doc/**/*.md'
+ # Lint content (error-level text-scoped rules only)
+ - vale --minAlertLevel error --ignore-syntax doc
# Prepare docs for build
- mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX
- cd /tmp/gitlab-docs
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index d966d88a320..db27c73fd7b 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -207,11 +207,11 @@ karma-as-if-foss:
extends: .frontend-job-base
script:
- date
- - yarn jest --ci --coverage
+ - yarn jest --ci --coverage --testSequencer ./scripts/frontend/parallel_ci_sequencer.js
cache:
key: jest
paths:
- - tmp/jest/jest/
+ - tmp/cache/jest/
policy: pull-push
jest:
@@ -229,6 +229,7 @@ jest:
- tmp/tests/frontend/
reports:
junit: junit_jest.xml
+ parallel: 2
jest-as-if-foss:
extends:
@@ -239,6 +240,26 @@ jest-as-if-foss:
cache:
policy: pull
+coverage-frontend:
+ extends:
+ - .default-tags
+ - .default-retry
+ - .frontend:rules:default-frontend-jobs
+ needs: ["jest"]
+ stage: post-test
+ before_script:
+ - yarn install --frozen-lockfile --cache-folder .yarn-cache --prefer-offline
+ script:
+ - yarn node scripts/frontend/merge_coverage_frontend.js
+ artifacts:
+ name: coverage-frontend
+ expire_in: 31d
+ paths:
+ - coverage-frontend/
+ cache:
+ paths:
+ - .yarn-cache/
+
.qa-frontend-node:
extends:
- .default-tags
diff --git a/.markdownlint.json b/.markdownlint.json
index fe3790f47e6..4e2f74c6104 100644
--- a/.markdownlint.json
+++ b/.markdownlint.json
@@ -123,5 +123,6 @@
"YouTrack"
],
"code_blocks": false
- }
+ },
+ "code-fence-style": false
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 66167a0d748..5eccc0c543d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -1,6 +1,6 @@
<script>
import _ from 'underscore';
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlButton } from '@gitlab/ui';
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge';
@@ -26,6 +26,7 @@ export default {
CommitEdit,
CommitMessageDropdown,
GlIcon,
+ GlButton,
MergeImmediatelyConfirmationDialog: () =>
import(
'ee_component/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue'
@@ -67,18 +68,13 @@ export default {
return 'success';
},
- mergeButtonClass() {
- const defaultClass = 'btn btn-sm btn-success accept-merge-request';
- const failedClass = `${defaultClass} btn-danger`;
- const inActionClass = `${defaultClass} btn-info`;
-
+ mergeButtonVariant() {
if (this.status === 'failed') {
- return failedClass;
+ return 'danger';
} else if (this.status === 'pending') {
- return inActionClass;
+ return 'info';
}
-
- return defaultClass;
+ return 'success';
},
iconClass() {
if (
@@ -267,16 +263,16 @@ export default {
<div class="media-body">
<div class="mr-widget-body-controls media space-children">
<span class="btn-group">
- <button
+ <gl-button
+ size="sm"
+ class="qa-merge-button accept-merge-request"
+ :variant="mergeButtonVariant"
:disabled="isMergeButtonDisabled"
- :class="mergeButtonClass"
- type="button"
- class="qa-merge-button"
+ :loading="isMakingRequest"
@click="handleMergeButtonClick(isAutoMergeAvailable)"
>
- <i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
{{ mergeButtonText }}
- </button>
+ </gl-button>
<button
v-if="shouldShowMergeImmediatelyDropdown"
:disabled="isMergeButtonDisabled"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
index 8132b1a944b..e52ad9156d5 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
@@ -1,15 +1,17 @@
<script>
import $ from 'jquery';
-import { __ } from '~/locale';
+import { GlButton } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
import createFlash from '~/flash';
-import statusIcon from '../mr_widget_status_icon.vue';
+import StatusIcon from '../mr_widget_status_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
export default {
name: 'WorkInProgress',
components: {
- statusIcon,
+ StatusIcon,
+ GlButton,
},
directives: {
tooltip,
@@ -23,8 +25,15 @@ export default {
isMakingRequest: false,
};
},
+ computed: {
+ wipInfoTooltip() {
+ return s__(
+ 'mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged',
+ );
+ },
+ },
methods: {
- removeWIP() {
+ handleRemoveWIP() {
this.isMakingRequest = true;
this.service
.removeWIP()
@@ -52,29 +61,22 @@ export default {
<i
v-tooltip
class="fa fa-question-circle"
- :title="
- s__(
- 'mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged',
- )
- "
- :aria-label="
- s__(
- 'mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged',
- )
- "
+ :title="wipInfoTooltip"
+ :aria-label="wipInfoTooltip"
>
</i>
</span>
- <button
+ <gl-button
v-if="mr.removeWIPPath"
+ size="sm"
+ variant="default"
:disabled="isMakingRequest"
- type="button"
- class="btn btn-default btn-sm js-remove-wip"
- @click="removeWIP"
+ :loading="isMakingRequest"
+ class="js-remove-wip"
+ @click="handleRemoveWIP"
>
- <i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"> </i>
{{ s__('mrWidget|Resolve WIP status') }}
- </button>
+ </gl-button>
</div>
</div>
</template>
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 66cb224f157..cf0642930ad 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -28,6 +28,10 @@ module Resolvers
end
end
+ def self.complexity
+ 0
+ end
+
def self.resolver_complexity(args, child_complexity:)
complexity = 1
complexity += 1 if args[:sort]
diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb
index 3ade1300c2d..8ff2b5ad532 100644
--- a/app/graphql/types/base_field.rb
+++ b/app/graphql/types/base_field.rb
@@ -9,7 +9,7 @@ module Types
def initialize(*args, **kwargs, &block)
@calls_gitaly = !!kwargs.delete(:calls_gitaly)
@constant_complexity = !!kwargs[:complexity]
- kwargs[:complexity] ||= field_complexity(kwargs[:resolver_class])
+ kwargs[:complexity] = field_complexity(kwargs[:resolver_class], kwargs[:complexity])
@feature_flag = kwargs[:feature_flag]
kwargs = check_feature_flag(kwargs)
@@ -51,7 +51,9 @@ module Types
args
end
- def field_complexity(resolver_class)
+ def field_complexity(resolver_class, current)
+ return current if current.present? && current > 0
+
if resolver_class
field_resolver_complexity
else
@@ -66,22 +68,30 @@ module Types
# proc because we set complexity depending on arguments and number of
# items which can be loaded.
proc do |ctx, args, child_complexity|
+ next base_complexity unless resolver_complexity_enabled?(ctx)
+
# Resolvers may add extra complexity depending on used arguments
complexity = child_complexity + self.resolver&.try(:resolver_complexity, args, child_complexity: child_complexity).to_i
complexity += 1 if calls_gitaly?
-
- field_defn = to_graphql
-
- if field_defn.connection?
- # Resolvers may add extra complexity depending on number of items being loaded.
- page_size = field_defn.connection_max_page_size || ctx.schema.default_max_page_size
- limit_value = [args[:first], args[:last], page_size].compact.min
- multiplier = self.resolver&.try(:complexity_multiplier, args).to_f
- complexity += complexity * limit_value * multiplier
- end
+ complexity += complexity * connection_complexity_multiplier(ctx, args)
complexity.to_i
end
end
+
+ def resolver_complexity_enabled?(ctx)
+ ctx.fetch(:graphql_resolver_complexity_flag) { |key| ctx[key] = Feature.enabled?(:graphql_resolver_complexity) }
+ end
+
+ def connection_complexity_multiplier(ctx, args)
+ # Resolvers may add extra complexity depending on number of items being loaded.
+ field_defn = to_graphql
+ return 0 unless field_defn.connection?
+
+ page_size = field_defn.connection_max_page_size || ctx.schema.default_max_page_size
+ limit_value = [args[:first], args[:last], page_size].compact.min
+ multiplier = self.resolver&.try(:complexity_multiplier, args).to_f
+ limit_value * multiplier
+ end
end
end
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index 10bbeecc2f7..7c12fe29ade 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -16,7 +16,7 @@ module Spammable
attr_accessor :spam_log
alias_method :spam?, :spam
- after_validation :check_for_spam, on: [:create, :update]
+ after_validation :invalidate_if_spam, on: [:create, :update]
cattr_accessor :spammable_attrs, instance_accessor: false do
[]
@@ -37,7 +37,7 @@ module Spammable
end
end
- def check_for_spam
+ def invalidate_if_spam
error_msg = if Gitlab::Recaptcha.enabled?
"Your #{spammable_entity_type} has been recognized as spam. "\
"Please, change the content or solve the reCAPTCHA to proceed."
diff --git a/changelogs/unreleased/dennis-update-ios-swift-project-template-logo.yml b/changelogs/unreleased/dennis-update-ios-swift-project-template-logo.yml
new file mode 100644
index 00000000000..2f5ecb0e280
--- /dev/null
+++ b/changelogs/unreleased/dennis-update-ios-swift-project-template-logo.yml
@@ -0,0 +1,5 @@
+---
+title: Update iOS (Swift) project template logo
+merge_request: 25049
+author:
+type: changed
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 0ee2c6a2a20..71cd5200415 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -1,10 +1,12 @@
require 'gitlab/testing/request_blocker_middleware'
require 'gitlab/testing/request_inspector_middleware'
+require 'gitlab/testing/clear_thread_memory_cache_middleware'
Rails.application.configure do
# Make sure the middleware is inserted first in middleware chain
config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::RequestBlockerMiddleware)
config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::RequestInspectorMiddleware)
+ config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::ClearThreadMemoryCacheMiddleware)
# Settings specified here will take precedence over those in config/application.rb
diff --git a/doc/administration/geo/disaster_recovery/index.md b/doc/administration/geo/disaster_recovery/index.md
index a329b02e7f2..5be1e1ff86e 100644
--- a/doc/administration/geo/disaster_recovery/index.md
+++ b/doc/administration/geo/disaster_recovery/index.md
@@ -75,7 +75,7 @@ must disable the **primary** node.
single recommendation. You may need to:
- Reconfigure the load balancers.
- - Change DNS records (e.g., point the primary DNS record to the **secondary**
+ - Change DNS records (for example, point the primary DNS record to the **secondary**
node in order to stop usage of the **primary** node).
- Stop the virtual servers.
- Block traffic through a firewall.
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 1e6b8bcc4a7..6416e6ec745 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -337,8 +337,8 @@ How this feature will work:
### Be careful with sensitive information
With some [Runner Executors](https://docs.gitlab.com/runner/executors/README.html),
-if you can run a job on the Runner, you can get access to any code it runs
-and get the token of the Runner. With shared Runners, this means that anyone
+if you can run a job on the Runner, you can get full access to the file system,
+and thus any code it runs as well as the token of the Runner. With shared Runners, this means that anyone
that runs jobs on the Runner, can access anyone else's code that runs on the
Runner.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 1a301481f05..b9afa6dea14 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -825,10 +825,8 @@ This could result in some unexpected behavior, including:
Available rule clauses include:
-- [`if`](#rulesif)
- (similar to [`only:variables`](#onlyvariablesexceptvariables)).
-- [`changes`](#ruleschanges)
- (same as [`only:changes`](#onlychangesexceptchanges)).
+- [`if`](#rulesif) (similar to [`only:variables`](#onlyvariablesexceptvariables))
+- [`changes`](#ruleschanges) (same as [`only:changes`](#onlychangesexceptchanges))
- [`exists`](#rulesexists)
For example, using `if`. This configuration specifies that `job` should be built
@@ -895,7 +893,6 @@ docker build:
- if: '$VAR == "string value"'
when: manual # Will include the job and set to when:manual if the expression evaluates to true, after the `changes:` rule fails to match.
- when: on_success # If neither of the first rules match, set to on_success
-
```
In this example, a job either set to:
@@ -956,6 +953,47 @@ job:
In this example, if the first rule matches, then the job will have `when: manual` and `allow_failure: true`.
+#### Exclude jobs with `rules:` from certain pipelines
+
+Jobs with `rules:` can cause two pipelines to be created unexpectedly:
+
+- One pipeline from pushing a commit to a branch.
+- A second ["detached" pipeline for a merge request](../merge_request_pipelines/index.md).
+
+`only` and `except` jobs do not trigger merge request pipelines by default, but this
+is not the case for jobs with `rules:`, which may be surprising if migrating from `only`
+and `except` to `rules:`.
+
+If you are using `rules:` and you see two pipelines for commits to branches that have
+a merge request, you have two options:
+
+- Individually exclude each job that uses `rules:` from merge request pipelines. The
+ example below will cause the job to **not** run in *pipelines for merge requests*,
+ but it **will** run in pipelines for *new tags and pipelines running on branch refs*:
+
+ ```yaml
+ job:
+ rules:
+ - if: $CI_MERGE_REQUEST_ID
+ when: never
+ - when: manual
+ script:
+ - echo hello
+ ```
+
+- Add a global [`workflow: rules`](#workflowrules) to allow pipelines in only certain
+ situations. The example below will only run pipelines for merge requests, new tags and
+ changes to master. It will **not** run any pipelines *on any branch except master*, but
+ it will run **detached merge request pipelines** for any merge request, targeting any branch:
+
+ ```yaml
+ workflow:
+ rules:
+ - if: $CI_MERGE_REQUEST_ID
+ - if: $CI_COMMIT_TAG
+ - if: $CI_COMMIT_BRANCH == "master"
+ ```
+
#### Complex rule clauses
To conjoin `if`, `changes`, and `exists` clauses with an AND, use them in the
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index a38d58f604e..2c81c41360a 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -146,6 +146,7 @@ graph RL;
U2["frontend-fixtures-as-if-foss<br/>(EE default refs only)"];
V["webpack-dev-server, static-analysis"];
M[coverage];
+ O[coverage-frontend];
N["pages (master only)"];
Q[package-and-qa];
S["RSpec<br/>(e.g. rspec unit pg9)"]
@@ -190,6 +191,7 @@ subgraph "`test` stage"
subgraph "`post-test` stage"
M --> |happens after| S
+ O --> |needs `jest`| I
end
subgraph "`review-prepare` stage"
diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md
index 1ec7c2aa400..0c74c39a067 100644
--- a/doc/subscriptions/index.md
+++ b/doc/subscriptions/index.md
@@ -2,70 +2,77 @@
type: index, reference
---
-# Customer Docs
+# GitLab subscription
-This section contains information for:
+Access to GitLab features is provided on a subscription basis. A subscription entitles users to the features of a specific GitLab tier. Each tier provides a set of features. A subscription is valid for 12 months.
-- New customers about choosing [which GitLab](#which-gitlab) is right for you.
-- Existing customers about [managing subscriptions](#managing-subscriptions).
+## Choosing a GitLab subscription
-Also see our [subscription FAQ](https://about.gitlab.com/pricing/licensing-faq/).
+When choosing a subscription, consider the following factors:
-## Which GitLab?
+- [GitLab tier](#choosing-a-gitlab-tier)
+- [GitLab.com or self-managed](#choosing-between-gitlabcom-or-self-managed)
+- [Group or personal subscription (GitLab.com only)](#choosing-a-gitlabcom-group-or-personal-subscription)
+- [Number of users](#choosing-the-number-of-users)
-There are two ways to use GitLab:
+### Choosing a GitLab tier
-- [GitLab.com](#gitlabcom): GitLab's SaaS offering. You don't need to install
- anything to use GitLab.com, you only need to
- [sign up](https://gitlab.com/users/sign_in) and start using GitLab straight away.
-- [GitLab self-managed](#gitlab-self-managed): Install, administer, and maintain
- your own GitLab instance.
+Pricing is [tier-based](https://about.gitlab.com/pricing/), allowing you to choose the features which fit your budget. See the [feature comparison](https://about.gitlab.com/pricing/gitlab-com/feature-comparison/) for information on what features are available at each tier.
-The following sections outline tiers and features within GitLab.com
-and GitLab self-managed.
+### Choosing between GitLab.com or self-managed
-### GitLab.com
+There are some differences in how a subscription applies, depending if you use GitLab.com or a self-managed instance.
-GitLab.com is hosted, managed, and administered by GitLab, Inc., with
-[free and paid subscriptions](https://about.gitlab.com/pricing/) for individuals
-and teams in the following tiers:
+- [GitLab.com](#gitlabcom): GitLab's software-as-a-service offering. You don't need to install anything to use GitLab.com, you only need to [sign up](https://gitlab.com/users/sign_in) and start using GitLab straight away.
+- [GitLab self-managed](#self-managed): Install, administer, and maintain your own GitLab instance.
-| Tier | Includes same features available in |
-|:-------|:----------------------------------------------------|
-| Free | [Core](#gitlab-self-managed) self-managed tier. |
-| Bronze | [Starter](#gitlab-self-managed) self-managed tier. |
-| Silver | [Premium](#gitlab-self-managed) self-managed tier. |
-| Gold | [Ultimate](#gitlab-self-managed) self-managed tier. |
+On a self-managed instance, a GitLab subscription provides the same set of features for all users. On GitLab.com you can apply a subscription to either a group or a personal namespace.
-GitLab.com subscriptions grant access
-to the same features available in GitLab self-managed, **except
-[administration](../administration/index.md) tools and settings**.
+### Choosing a GitLab.com group or personal subscription
-GitLab.com allows you to apply your subscription to a group or your personal user.
+On GitLab.com you can apply a subscription to either a group or a personal namespace.
When applied to:
- A **group**, the group, all subgroups, and all projects under the selected
- group on GitLab.com will have the features of the associated plan. It is
- recommended to go with a group plan when managing projects and users of an
- organization.
+ group on GitLab.com will have the features of the associated tier. GitLab recommends
+ choosing a group plan when managing an organization's projects and users.
- A **personal userspace** instead, all projects will have features with the
- subscription applied, but as it is not a group, group features will not be available.
+ subscription applied, but as it's not a group, group features won't be available.
+
+### Choosing the number of users
+
+There are some differences between who is counted in a subscription, depending if you use GitLab.com or a self-managed instance.
+
+#### GitLab.com
+
+A GitLab.com subscription uses a concurrent (_seat_) model. You pay for a subscription according to the maximum number of users enabled at once. You can add and remove users during the subscription period, as long as the total users at any given time is within your subscription count.
+
+Every occupied seat, whether by person, job, or bot is counted in the subscription, with the following exception:
+
+- Members with Guest permissions on a Gold subscription.
TIP: **Tip:**
To support the open source community and encourage the development of open
source projects, GitLab grants access to **Gold** features for all GitLab.com
**public** projects, regardless of the subscription.
-The following resources are available for more information on GitLab.com:
+#### Self-managed
+
+A self-managed subscription uses a hybrid model. You pay for a subscription according to the maximum number of users enabled during the subscription period. At the end of the subscription period, the maximum number of simultaneous users in the self-managed installation is checked. If the number of users is higher than your subscription, you are billed for the extra users. The maximum number of simultaneous users is also used to calculate the cost of subscription renewal.
+
+Every occupied seat, whether by person, job, or bot is counted in the subscription, with the following exceptions:
+
+- Blocked users who are blocked prior to the renewal of a subscription won't be counted as active users for the renewal subscription. They may count as active users in the subscription period in which they were originally added.
+- Members with Guest permissions on an Ultimate subscription.
+- Special internal GitLab accounts: `Ghost User` and `Support Bot`.
-- [Feature comparison](https://about.gitlab.com/pricing/gitlab-com/feature-comparison/), for information on what features are available at each tier.
-- [GitLab pricing page](https://about.gitlab.com/pricing/), for subscription information and a free trial.
-- Our [product marketing page](https://about.gitlab.com/handbook/marketing/product-marketing/), for additional information including:
- - How [different tiers are licensed](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers).
- - The different [GitLab distributions](https://about.gitlab.com/handbook/marketing/product-marketing/#distributions).
+NOTE: **Note:**
+If you have LDAP integration enabled, anyone in the configured domain can sign up for a GitLab account. This can result in an unexpected bill at time of renewal. Consider [disabling new signups](../user/admin_area/settings/sign_up_restrictions.md) and managing new users manually instead.
+
+## Obtain a GitLab subscription
-#### Subscribing to GitLab.com
+### Subscribe to GitLab.com
To subscribe to GitLab.com:
@@ -78,10 +85,10 @@ To subscribe to GitLab.com:
1. Create additional users and
[add them to the group](../user/group/index.md#add-users-to-a-group).
1. Select the **Bronze**, **Silver**, or **Gold** GitLab.com plan through the
- [GitLab Subscription Manager](https://customers.gitlab.com/).
-1. Link your GitLab.com account with your GitLab Subscription Manager account.
- Once signed into the GitLab Subscription Manager, if your account is not
- already linked, you will prompted to link your account with a
+ [Customers Portal](https://customers.gitlab.com/).
+1. Link your GitLab.com account with your Customers Portal account.
+ Once signed into the Customers Portal, if your account is not
+ already linked, you will be prompted to link your account with a
**Link my GitLab Account** button.
1. Associate the group with the subscription.
@@ -89,112 +96,60 @@ TIP: **Tip:**
You can also go to the [**My Account**](https://customers.gitlab.com/customers/edit)
page to add or change the GitLab.com account link.
-### GitLab self-managed
-
-With GitLab self-managed, you deploy your own GitLab instance on-premises or on a cloud of your choice.
-GitLab self-managed is available for [free and with paid subscriptions](https://about.gitlab.com/pricing/#self-managed) in the following tiers:
-
-| Tier | Includes |
-|:---------|:-----------------------------------------------|
-| Core | Core features. |
-| Starter | Core and Starter features. |
-| Premium | Core, Starter, and Premium features. |
-| Ultimate | Core, Starter, Premium, and Ultimate features. |
-
-The following resources are available for more information on GitLab self-managed:
-
-- [Feature comparison](https://about.gitlab.com/pricing/self-managed/feature-comparison/), for information on what features are available at each tier.
-- [GitLab pricing page](https://about.gitlab.com/pricing/#self-managed), for subscription information and a free trial.
-- Our [product marketing page](https://about.gitlab.com/handbook/marketing/product-marketing/), for additional information including:
- - How [different tiers are licensed](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers).
- - The different [GitLab distributions](https://about.gitlab.com/handbook/marketing/product-marketing/#distributions).
-
-#### Subscribing through GitLab self-managed
+### Subscribe through GitLab self-managed
To subscribe to GitLab through a self-managed installation:
-1. [Install](https://about.gitlab.com/install/) GitLab.
-1. Complete the installation with
- [administration tasks](../administration/index.md).
-1. Select the **Starter**, **Premium**, or **Ultimate** self-managed plan
- through the [GitLab Subscription Manager](https://customers.gitlab.com/).
-1. Apply your license file. After purchase, a license file is sent to the email
- address associated to the GitLab Subscription Manager account,
- which needs to be
- [uploaded to your GitLab instance](../user/admin_area/license.md#uploading-your-license).
+1. Go to the [Customers Portal](https://customers.gitlab.com/) and purchase a **Starter**, **Premium**, or **Ultimate** self-managed plan.
+1. After purchase, a license file is sent to the email address associated to the Customers Portal account,
+ which must be [uploaded to your GitLab instance](../user/admin_area/license.md#uploading-your-license).
TIP: **Tip:**
-If you are purchasing a subscription for an existing **Core** self-managed
-instance, ensure you are purchasing enough seats to
+If you're purchasing a subscription for an existing **Core** self-managed
+instance, ensure you're purchasing enough seats to
[cover your users](../user/admin_area/index.md#administering-users).
-## Managing subscriptions
-
-You can view and manage subscriptions through our
-[GitLab Subscription Manager](https://customers.gitlab.com/).
-
-### View subscription and seats
+## Manage your GitLab account
-Visit the
-[GitLab Subscription Manager](https://customers.gitlab.com/subscriptions) to
-view and manage:
+With the [Customers Portal](https://customers.gitlab.com/) you can:
-- The subscriptions you have purchased.
-- The number of seats associated with the subscription.
-- Retrieve copies of invoices.
-- Change the credit card on file.
-
-For more information, please see our:
-
-- [Subscription FAQ](https://about.gitlab.com/pricing/licensing-faq/).
-- [Pricing page](https://about.gitlab.com/pricing/), which includes information
- on our [true-up pricing policy](https://about.gitlab.com/handbook/ceo/pricing/#true-up-pricing)
- when adding more users other than at the time of purchase.
-
-NOTE: **Note:**
-The GitLab Subscription Manager account can have the same email address as your
-GitLab.com account, but is a _separate_ login. If the two accounts are
-linked together, you can use the **Or sign in with GitLab.com**
-link underneath the **Sign In** button.
+- [Change billing information](#change-billing-information)
+- [Change the linked account](#change-the-linked-account)
+- [Change the associated namespace](#change-the-associated-namespace)
### Change billing information
To change billing information:
-1. Log in to [GitLab Subscription Manager](https://customers.gitlab.com/customers/sign_in).
+1. Log in to [Customers Portal](https://customers.gitlab.com/customers/sign_in).
1. Go to the **My Account** page.
1. Make the required changes to the **Account Details** information.
1. Click **Update Account**.
NOTE: **Note:**
Future purchases will use the information in this section.
-The email listed in this section is used for the GitLab Subscription Manager
+The email listed in this section is used for the Customers Portal
login and for license-related email communication.
-### Manage GitLab.com account
-
-This section provided information specific to managing subscriptions with a
-GitLab.com account.
+### Change the linked account
-#### Change linked account
-
-To change the GitLab.com account associated with a GitLab Subscription Manager
+To change the GitLab.com account associated with a Customers Portal
account:
1. Log in to the
- [GitLab Subscription Manager](https://customers.gitlab.com/customers/sign_in).
+ [Customers Portal](https://customers.gitlab.com/customers/sign_in).
1. Go to [GitLab.com](https://gitlab.com) in a separate browser tab. Ensure you
are not logged in.
-1. On the GitLab Subscription Manager page, click
+1. On the Customers Portal page, click
[**My Account**](https://customers.gitlab.com/customers/edit) in the top menu.
1. Under **Your GitLab.com account**, click **Change linked account** button.
-1. Log in to [GitLab.com](https://gitlab.com) account to link to.
+1. Log in to the [GitLab.com](https://gitlab.com) account you want to link to Customers Portal.
-#### Change associated namespace
+### Change the associated namespace
With a linked GitLab.com account:
-1. Log in to the [GitLab Subscription Manager](https://customers.gitlab.com/customers/sign_in).
+1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
1. Navigate to the **Manage Purchases** page.
1. Click **Change linked group**.
1. Select the desired group from the **This subscription is for** dropdown.
@@ -202,15 +157,15 @@ With a linked GitLab.com account:
Subscription charges are calculated based on the total number of users in a group, including its subgroups and nested projects. If the total number of users exceeds the number of seats in your subscription, you will be charged for the additional users.
-### Confirm or upgrade your subscription
+## View your subscription
-To see the status of your GitLab.com subscription, you can click on the
-**Billings** section of the relevant namespace:
+To see the status of your GitLab.com subscription, log into GitLab.com and go to the **Billing** section of the relevant namespace:
- For individuals:
1. Go to **User Avatar > Settings**.
1. Click **Billing**.
-- For groups, go to the group's **Settings** dropdown, under **Billing**.
+- For groups:
+ 1. From the group page (*not* from a project within the group), go to **Settings > Billing**.
The following table describes details of your subscription for groups:
@@ -219,161 +174,182 @@ The following table describes details of your subscription for groups:
| Seats in subscription | If this is a paid plan, represents the number of seats you've paid to support in your group. |
| Seats currently in use | Number of active seats currently in use. |
| Max seats used | Highest number of seats you've used. If this exceeds the seats in subscription, you may owe an additional fee for the additional users. |
-| Seats owed | If your max seats used exceeds the seats in your subscription, you'll owe an additional fee for the users you've added. |
+| Seats owed | If your maximum seats used exceeds the seats in your subscription, you'll owe an additional fee for the users you've added. |
| Subscription start date | Date your subscription started. If this is for a Free plan, is the date you transitioned off your group's paid plan. |
| Subscription end date | Date your current subscription will end. Does not apply to Free plans. |
-#### CI pipeline minutes
+## Renew your subscription
-CI pipeline minutes are the execution time for your [pipelines](../ci/pipelines.md) on GitLab's shared runners. Each [GitLab.com tier](https://about.gitlab.com/pricing/) includes a monthly quota of CI pipeline minutes.
+To renew your subscription, [prepare for the renewal](#prepare-for-renewal), then do one of the following:
-Quotas apply to:
+- [Renew a GitLab.com subscription](#renew-or-change-a-gitlabcom-subscription).
+- [Renew a self-managed subscription](#renew-a-self-managed-subscription).
-- Groups, where the minutes are shared across all members of the group, its subgroups, and nested projects. To view the group's usage, navigate to the group's page, then **Settings > Usage Quotas**.
+### Prepare for renewal
-- Your personal account, where the minutes are available for your personal projects. To view and buy personal minutes, click your avatar, then **Settings > Pipeline quota**.
+The [Customers Portal](https://customers.gitlab.com/customers/sign_in) is your tool for renewing and modifying your subscription. Before going ahead with renewal, log in and verify or update:
-Only pipeline minutes for GitLab shared runners are restricted. If you have a specific runner set up for your projects, there is no limit to your build time on GitLab.com.
+- The invoice contact details on the **My Account** page.
+- The credit card on file in the **Payment Methods** page.
-The minutes limit does not apply to public projects.
+TIP: **Tip:**
+Contact our [support team](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=360000071293) if you need assistance accessing the Customers Portal or if you need to change the contact person who manages your subscription.
-The available quota is reset on the first of each calendar month at midnight UTC.
+Check who is accessing your system. Are there user accounts which are no longer active? It's important to regularly review your GitLab user accounts because:
-If you reach your limit, you can [purchase additional CI minutes](#extra-shared-runners-pipeline-minutes), or upgrade your account to [Silver or Gold](https://about.gitlab.com/pricing/). Your own runners can still be used even if you reach your limits.
+- A GitLab subscription is based on the number of users. Renewing a subscription for too many users results in you paying more than you should. Attempting to renew a subscription for too few users will result in the renewal failing.
+- Stale user accounts can be a security risk. A regular review helps reduce this risk.
-##### How pipeline quota usage is calculated
+#### Users over License
-Pipeline quota usage is calculated as the sum of the duration of each individual job. This is slightly different to how pipeline _duration_ is [calculated](../ci/pipelines.md#how-pipeline-duration-is-calculated). Pipeline quota usage doesn't consider the intersection of jobs.
+A GitLab subscription is valid for a specific number of users. For details, see [Choose the number of users](#choosing-the-number-of-users). If the active user count exceeds the number included in the subscription, known as the number of _users over license_, you must pay for the excess number of users either before renewal, or at the time of renewal. This is also known the _true up_ process.
-A simple example is:
+##### Purchase additional seats for GitLab.com
-A (1, 3)
-B (2, 4)
-C (6, 7)
+There is no self-service option for purchasing additional seats. You must request a quotation from GitLab Sales. To do so, contact GitLab via our [support form](https://support.gitlab.com/hc/en-us/requests/new) and select **Licensing and Renewals Problems** from the menu.
-In the example:
+The amount charged per seat is calculated by one of the following methods:
-A begins at 1 and ends at 3.
-B begins at 2 and ends at 4.
-C begins at 6 and ends at 7.
-Visually, it can be viewed as:
+- If paid before renewal, the amount per seat is calculated on a prorated basis. For example, if the user was added 3 months before the end of the subscription period, the amount owing is calculated as: (3 / 12) x annual fee.
+- If paid on renewal, the amount per seat is the standard annual fee.
-```
-0 1 2 3 4 5 6 7
- AAAAAAA
- BBBBBBB
- CCCC
-```
+##### Purchase additional users for self-managed
-The sum of each individual job is being calculated therefore in this example, `8` runner minutes would be used for this pipeline:
+Self-managed instances can add users to a subscription any time during the subscription period. The cost of additional users added during the subscription period is prorated from the date of purchase through the end of the subscription period.
-```
-A + B + C = 3 + 3 + 2 => 8
-```
+To add users to a subscription:
-#### Extra Shared Runners pipeline minutes
+1. Log in to [Customers Portal](https://customers.gitlab.com/).
+1. Select **Manage Purchases**.
+1. Select **Add more seats**.
+1. Enter the number of additional users.
+1. Select **Proceed to checkout**.
+1. Review the **Subscription Upgrade Detail**. The system lists the total price for all users on the system and a credit for what you've already paid. You will only be charged for the net change.
+1. Select **Confirm Upgrade**.
-If you're using GitLab.com, you can purchase additional CI minutes so your
-pipelines will not be blocked after you have used all your CI minutes from your
-main quota. Additional minutes:
+The following will be emailed to you:
-- Are only used once the shared quota included in your subscription runs out.
-- Roll over month to month.
+- A payment receipt. You can also access this information in the Customers Portal under **Payment History**.
+- A new license. [Upload this license](../user/admin_area/license.md#uploading-your-license) to your instance to use it.
-Each month, any minutes that you used will be deducted from your balance of additional minutes.
-Therefore, the number of minutes used and available will reflect your *current*
-month's usage and availability. Purchased remaining minutes not used in the
-current month will be rolled out over to the next month.
+### Renew or change a GitLab.com subscription
-For example:
+To renew for more users than are currently active in your GitLab.com system, contact our sales team via `renewals@gitlab.com` for assistance as this can't be done in Customers Portal.
-- February 15: A group buys 4000 minutes. The count reads 0/4000 minutes.
-- February 28: The group has used 1500 minutes. The count reads 1500/4000 minutes. Thus, there are 2500 minutes remaining.
-- March 1: The counter reads: 0/2500 minutes rolled out from February's remaining quota.
+To change the [GitLab tier](https://about.gitlab.com/pricing/), select **Upgrade** under your subscription on the [My Account](https://customers.gitlab.com/subscriptions) page.
-##### Purchasing additional minutes
+#### Automatic renewal
-To purchase additional minutes, follow these steps.
+To view or change automatic subscription renewal (at the same tier as the previous period), log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in), and:
-1. For group minutes, go to **Group > Settings > Pipelines quota**.
+- If you see a **Resume subscription** button, your subscription was cancelled previously. Click it to resume automatic renewal.
+- If you see **Cancel subscription**, your subscription is set to automatically renew at the end of the subscription period. Click it to cancel automatic renewal.
- For personal project minutes, click your avatar, then **Settings > Pipeline quota**.
+With automatic renewal enabled, the subscription will automatically renew on the expiration date and there will be no gap in available service.
+An invoice will be generated for the renewal and available for viewing or download in the [Payment History](https://customers.gitlab.com/receipts) page. If you have difficulty during the renewal process, contact our [support team](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=360000071293) for assistance.
-1. Click **Buy additional minutes**.
+### Renew a self-managed subscription
-1. Locate the subscription card that is linked to your group on GitLab.com,
- click **Buy more CI minutes**, and complete the details about the transaction.
+Starting 30 days before a subscription expires, GitLab notifies administrators of the date of expiry with a banner in the GitLab user interface.
- ![Buy additional minutes](img/buy_minutes_card.png)
+We recommend following these steps during renewal:
-1. Once we have processed your payment, the extra CI minutes
- will be synced to your Group and you can visualize it from the
- **Group > Settings > Pipelines quota** page:
+1. Prune any inactive or unwanted users by [blocking them](../user/admin_area/blocking_unblocking_users.md#blocking-a-user).
+1. Determine if you have a need for user growth in the upcoming subscription.
+1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in) and select the **Renew** button beneath your existing subscription.
- ![Additional minutes](img/additional_minutes.png)
+ TIP: **Tip:**
+ If you need to change your [GitLab tier](https://about.gitlab.com/pricing/), contact our sales team via `renewals@gitlab.com` for assistance as this can't be done in Customers Portal.
- The **Additional minutes** displayed now includes the purchased additional CI minutes, plus any
- minutes rolled over from last month.
+1. In the first box, enter the total number of user licenses you’ll need for the upcoming year. Be sure this number is at least **equal to, or greater than** the number of active users in the system at the time of performing the renewal.
+1. Enter the number of [users over license](#users-over-license) in the second box for the user overage incurred in your previous subscription term.
-Be aware that:
-
-1. If you have purchased extra CI minutes before the purchase of a paid plan,
- we will calculate a pro-rated charge for your paid plan. That means you may
- be charged for less than one year since your subscription was previously
- created with the extra CI minutes.
-1. Once the extra CI minutes has been assigned to a Group they cannot be transferred
- to a different Group.
-1. If you have some minutes used over your default quota, these minutes will
- be deducted from your Additional Minutes quota immediately after your purchase of additional
- minutes.
-
-##### What happens when my CI minutes run out
-
-When the CI minutes run out, an email is sent automatically to notify the owner(s)
-of the group/namespace, including a link to [purchase more minutes](https://customers.gitlab.com/plans).
-
-If you are not the owner of the group, you will need to contact them to let them know they need to
-[purchase more minutes](https://customers.gitlab.com/plans).
+ TIP: **Tip:**
+ You can find the _users over license_ in your instance's **Admin** dashboard by clicking on {**admin**} (**Admin Area**) in the top bar, or going to `/admin`.
-## Subscription changes and your data
+1. Review your renewal details and complete the payment process.
+1. A license for the renewal term will be available on the [Manage Purchases](https://customers.gitlab.com/subscriptions) page beneath your new subscription details.
+1. [Upload](../user/admin_area/license.md) your new license to your instance.
-When your subscription or trial expires, GitLab does not delete your data.
+An invoice will be generated for the renewal and available for viewing or download in the [Payment History](https://customers.gitlab.com/receipts) page. If you have difficulty during the renewal process, contact our [support team](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=360000071293) for assistance.
-However, depending on the tier and feature, your data may become inaccessible.
+## Subscription expiry
-Please note that some features may not behave as expected if a graceful
-fallback is not currently implemented. For example,
-[environment specific variables not being passed](https://gitlab.com/gitlab-org/gitlab-foss/issues/52825).
+When your subscription or trial expires, GitLab does not delete your data, but it may become inaccessible, depending on the tier at expiry. Some features may not behave as expected if you're not prepared for the expiry. For example, [environment specific variables not being passed](https://gitlab.com/gitlab-org/gitlab/issues/24759).
If you renew or upgrade, your data will again be accessible.
-### Self-managed data
+### Self-managed GitLab data
For self-managed customers, there is a two-week grace period when your features
will continue to work as-is, after which the entire instance will become read
only.
However, if you remove the license, you will immediately revert to Core
-features.
+features, and the instance will be read / write again.
-## Need help?
+## CI pipeline minutes
-[GitLab's Documentation](https://docs.gitlab.com/) offers a wide range of
-topics covering the use and administration of GitLab.
+CI pipeline minutes are the execution time for your [pipelines](../ci/pipelines.md) on GitLab's shared runners. Each [GitLab.com tier](https://about.gitlab.com/pricing/) includes a monthly quota of CI pipeline minutes.
-We also encourage all users to search our project trackers for known issues and
-existing feature requests in:
+Quotas apply to:
+
+- Groups, where the minutes are shared across all members of the group, its subgroups, and nested projects. To view the group's usage, navigate to the group's page, then **Settings > Usage Quotas**.
+- Your personal account, where the minutes are available for your personal projects. To view and buy personal minutes, click your avatar, then **Settings > Pipeline quota**.
+
+Only pipeline minutes for GitLab shared runners are restricted. If you have a specific runner set up for your projects, there is no limit to your build time on GitLab.com.
+
+The minutes limit does not apply to public projects.
+
+The available quota is reset on the first of each calendar month at midnight UTC.
+
+When the CI minutes are depleted, an email is sent automatically to notify the owner(s)
+of the group/namespace. You can [purchase additional CI minutes](#purchasing-additional-ci-minutes), or upgrade your account to [Silver or Gold](https://about.gitlab.com/pricing/). Your own runners can still be used even if you reach your limits.
-- [GitLab CE](https://gitlab.com/gitlab-org/gitlab-foss/issues/) for features
- included in all tiers.
-- [GitLab EE](https://gitlab.com/gitlab-org/gitlab/issues/) for paid-tier
- features.
+### Purchasing additional CI minutes
+
+If you're using GitLab.com, you can purchase additional CI minutes so your
+pipelines won't be blocked after you have used all your CI minutes from your
+main quota. Additional minutes:
+
+- Are only used once the shared quota included in your subscription runs out.
+- Roll over month to month.
+
+To purchase additional minutes for your group on GitLab.com:
+
+1. From your group, go to **Settings > Pipeline quota**.
+1. Locate the subscription card that's linked to your group on GitLab.com, click **Buy more CI minutes**, and complete the details about the transaction.
+1. Once we have processed your payment, the extra CI minutes will be synced to your group.
+1. To confirm the available CI minutes, go to **Group > Settings > Pipelines quota**.
+ The **Additional minutes** displayed now includes the purchased additional CI minutes, plus any minutes rolled over from last month.
+
+To purchase additional minutes for your personal namespace:
+
+1. Click your avatar, then go to **Settings > Pipeline quota**.
+1. Locate the subscription card that's linked to your personal namespace on GitLab.com, click **Buy more CI minutes**, and complete the details about the transaction. Once we have processed your payment, the extra CI minutes will be synced to your Group.
+1. To confirm the available CI minutes for your personal projects, click your avatar, then go to **Settings > Pipeline quota**.
+ The **Additional minutes** displayed now includes the purchased additional CI minutes, plus any minutes rolled over from last month.
+
+Be aware that:
+
+- If you have purchased extra CI minutes before the purchase of a paid plan,
+ we will calculate a pro-rated charge for your paid plan. That means you may
+ be charged for less than one year since your subscription was previously
+ created with the extra CI minutes.
+- Once the extra CI minutes has been assigned to a Group they can't be transferred
+ to a different Group.
+- If you have used more minutes than your default quota, these minutes will
+ be deducted from your Additional Minutes quota immediately after your purchase of additional
+ minutes.
+
+## Contact Support
+
+We also encourage all users to search our project trackers for known issues and
+existing feature requests in the [GitLab](https://gitlab.com/gitlab-org/gitlab/issues/) project.
These issues are the best avenue for getting updates on specific product plans
and for communicating directly with the relevant GitLab team members.
-### Contacting Support
-
Learn more about:
- The tiers of [GitLab Support](https://about.gitlab.com/support/).
diff --git a/doc/user/admin_area/activating_deactivating_users.md b/doc/user/admin_area/activating_deactivating_users.md
index dcd7407ac85..9c153497e74 100644
--- a/doc/user/admin_area/activating_deactivating_users.md
+++ b/doc/user/admin_area/activating_deactivating_users.md
@@ -41,7 +41,7 @@ Please note that for the deactivation option to be visible to an admin, the user
Users can also be deactivated using the [GitLab API](../../api/users.md#deactivate-user).
NOTE: **Note:**
-A deactivated user does not consume a [seat](../../subscriptions/index.md#managing-subscriptions).
+A deactivated user does not consume a [seat](../../subscriptions/index.md#choosing-the-number-of-users).
## Activating a user
@@ -60,7 +60,7 @@ Users can also be activated using the [GitLab API](../../api/users.md#activate-u
NOTE: **Note:**
Activating a user will change the user's state to active and it consumes a
-[seat](../../subscriptions/index.md#managing-subscriptions).
+[seat](../../subscriptions/index.md#choosing-the-number-of-users).
TIP: **Tip:**
A deactivated user can also activate their account by themselves by simply logging back via the UI.
diff --git a/doc/user/admin_area/blocking_unblocking_users.md b/doc/user/admin_area/blocking_unblocking_users.md
index cb86e28ff1e..e3b9cd1218c 100644
--- a/doc/user/admin_area/blocking_unblocking_users.md
+++ b/doc/user/admin_area/blocking_unblocking_users.md
@@ -30,7 +30,7 @@ Personal projects, and group and user history of the blocked user will be left i
Users can also be blocked using the [GitLab API](../../api/users.md#block-user).
NOTE: **Note:**
-A blocked user does not consume a [seat](../../subscriptions/index.md#managing-subscriptions).
+A blocked user does not consume a [seat](../../subscriptions/index.md#choosing-the-number-of-users).
## Unblocking a user
@@ -45,4 +45,4 @@ Users can also be unblocked using the [GitLab API](../../api/users.md#unblock-us
NOTE: **Note:**
Unblocking a user will change the user's state to active and it consumes a
-[seat](../../subscriptions/index.md#managing-subscriptions).
+[seat](../../subscriptions/index.md#choosing-the-number-of-users).
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index c526f7339d5..7ea7feac9ba 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -117,7 +117,7 @@ service included with GitLab that coordinates the jobs.
If the project is on GitLab.com, shared Runners are available
(the first 2000 minutes are free, you can
-[buy more later](../../subscriptions/index.md#extra-shared-runners-pipeline-minutes))
+[buy more later](../../subscriptions/index.md#purchasing-additional-ci-minutes))
and you do not have to deploy one if they are enough for your needs. If a
project-specific Runner is desired, or there are no shared Runners, it is easy
to deploy one.
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 7d02346af67..143da6b7fd6 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -104,7 +104,7 @@ Linux Shared Runners on GitLab.com run in [autoscale mode] and are powered by Go
Autoscaling means reduced waiting times to spin up CI/CD jobs, and isolated VMs for each project,
thus maximizing security. They're free to use for public open source projects and limited
to 2000 CI minutes per month per group for private projects. More minutes
-[can be purchased](../../subscriptions/index.md#extra-shared-runners-pipeline-minutes), if
+[can be purchased](../../subscriptions/index.md#purchasing-additional-ci-minutes), if
needed. Read about all [GitLab.com plans](https://about.gitlab.com/pricing/).
All your CI/CD jobs run on [n1-standard-1 instances](https://cloud.google.com/compute/docs/machine-types) with 3.75GB of RAM, CoreOS and the latest Docker Engine
diff --git a/jest.config.js b/jest.config.js
index d07c034e88e..fe05141dfd7 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -55,6 +55,14 @@ if (IS_EE) {
collectCoverageFrom.push(rootDirEE.replace('$1', '/**/*.{js,vue}'));
}
+const coverageDirectory = () => {
+ if (process.env.CI_NODE_INDEX && process.env.CI_NODE_TOTAL) {
+ return `<rootDir>/coverage-frontend/jest-${process.env.CI_NODE_INDEX}-${process.env.CI_NODE_TOTAL}`;
+ }
+
+ return '<rootDir>/coverage-frontend/';
+};
+
// eslint-disable-next-line import/no-commonjs
module.exports = {
clearMocks: true,
@@ -62,7 +70,7 @@ module.exports = {
moduleFileExtensions: ['js', 'json', 'vue'],
moduleNameMapper,
collectCoverageFrom,
- coverageDirectory: '<rootDir>/coverage-frontend/',
+ coverageDirectory: coverageDirectory(),
coverageReporters: ['json', 'lcov', 'text-summary', 'clover'],
cacheDirectory: '<rootDir>/tmp/cache/jest',
modulePathIgnorePatterns: ['<rootDir>/.yarn-cache/'],
diff --git a/lib/gitlab/danger/commit_linter.rb b/lib/gitlab/danger/commit_linter.rb
index c0748a4b8e6..c9cb66e1718 100644
--- a/lib/gitlab/danger/commit_linter.rb
+++ b/lib/gitlab/danger/commit_linter.rb
@@ -14,6 +14,7 @@ module Gitlab
MAX_CHANGED_LINES_IN_COMMIT = 30
SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(#|!|&|%)\d+\b}.freeze
DEFAULT_SUBJECT_DESCRIPTION = 'commit subject'
+ WIP_PREFIX = 'WIP: '
PROBLEMS = {
subject_too_short: "The %s must contain at least #{MIN_SUBJECT_WORDS_COUNT} words",
subject_too_long: "The %s may not be longer than #{MAX_LINE_LENGTH} characters",
@@ -164,7 +165,7 @@ module Gitlab
end
def subject
- message_parts[0]
+ message_parts[0].delete_prefix(WIP_PREFIX)
end
def separator
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index 7a5b5b0c6d9..9ed6a23632c 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -40,7 +40,7 @@ module Gitlab
ProjectTemplate.new('rails', 'Ruby on Rails', _('Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/rails', 'illustrations/logos/rails.svg'),
ProjectTemplate.new('spring', 'Spring', _('Includes an MVC structure, mvnw and pom.xml to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/spring', 'illustrations/logos/spring.svg'),
ProjectTemplate.new('express', 'NodeJS Express', _('Includes an MVC structure to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/express', 'illustrations/logos/express.svg'),
- ProjectTemplate.new('iosswift', 'iOS (Swift)', _('A ready-to-go template for use with iOS Swift apps.'), 'https://gitlab.com/gitlab-org/project-templates/iosswift'),
+ ProjectTemplate.new('iosswift', 'iOS (Swift)', _('A ready-to-go template for use with iOS Swift apps.'), 'https://gitlab.com/gitlab-org/project-templates/iosswift', 'illustrations/logos/swift.svg'),
ProjectTemplate.new('dotnetcore', '.NET Core', _('A .NET Core console application template, customizable for any .NET Core project'), 'https://gitlab.com/gitlab-org/project-templates/dotnetcore', 'illustrations/logos/dotnet.svg'),
ProjectTemplate.new('android', 'Android', _('A ready-to-go template for use with Android apps.'), 'https://gitlab.com/gitlab-org/project-templates/android', 'illustrations/logos/android.svg'),
ProjectTemplate.new('gomicro', 'Go Micro', _('Go Micro is a framework for micro service development.'), 'https://gitlab.com/gitlab-org/project-templates/go-micro'),
diff --git a/lib/gitlab/testing/clear_thread_memory_cache_middleware.rb b/lib/gitlab/testing/clear_thread_memory_cache_middleware.rb
new file mode 100644
index 00000000000..6f54038ae22
--- /dev/null
+++ b/lib/gitlab/testing/clear_thread_memory_cache_middleware.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Testing
+ class ClearThreadMemoryCacheMiddleware
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ Gitlab::ThreadMemoryCache.cache_backend.clear
+
+ @app.call(env)
+ end
+ end
+ end
+end
diff --git a/package.json b/package.json
index ccd9c41cf8d..ec870a420ed 100644
--- a/package.json
+++ b/package.json
@@ -40,8 +40,8 @@
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.6.2",
"@gitlab/at.js": "^1.5.5",
- "@gitlab/svgs": "^1.99.0",
- "@gitlab/ui": "^9.11.2",
+ "@gitlab/svgs": "^1.101.0",
+ "@gitlab/ui": "^9.16.0",
"@gitlab/visual-review-tools": "1.5.1",
"@sentry/browser": "^5.10.2",
"@sourcegraph/code-host-integration": "0.0.30",
@@ -166,6 +166,9 @@
"gettext-extractor": "^3.4.3",
"gettext-extractor-vue": "^4.0.2",
"graphql-tag": "^2.10.0",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-reports": "^3.0.0",
"jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
"jasmine-jquery": "^2.1.1",
diff --git a/scripts/frontend/merge_coverage_frontend.js b/scripts/frontend/merge_coverage_frontend.js
new file mode 100644
index 00000000000..507695b45e5
--- /dev/null
+++ b/scripts/frontend/merge_coverage_frontend.js
@@ -0,0 +1,31 @@
+const { create } = require('istanbul-reports');
+const { createCoverageMap } = require('istanbul-lib-coverage');
+const { createContext } = require('istanbul-lib-report');
+const { resolve } = require('path');
+const { sync } = require('glob');
+
+const coverageMap = createCoverageMap();
+
+const coverageDir = resolve(__dirname, '../../coverage-frontend');
+const reportFiles = sync(`${coverageDir}/*/coverage-final.json`);
+
+// Normalize coverage report generated by jest that has additional "data" key
+// https://github.com/facebook/jest/issues/2418#issuecomment-423806659
+const normalizeReport = report => {
+ const normalizedReport = Object.assign({}, report);
+ Object.entries(normalizedReport).forEach(([k, v]) => {
+ if (v.data) normalizedReport[k] = v.data;
+ });
+ return normalizedReport;
+};
+
+reportFiles
+ .map(reportFile => require(reportFile))
+ .map(normalizeReport)
+ .forEach(report => coverageMap.merge(report));
+
+const context = createContext({ coverageMap: coverageMap, dir: 'coverage-frontend' });
+
+['json', 'lcov', 'text-summary', 'clover'].forEach(reporter => {
+ create(reporter, {}).execute(context);
+});
diff --git a/scripts/frontend/parallel_ci_sequencer.js b/scripts/frontend/parallel_ci_sequencer.js
new file mode 100644
index 00000000000..d7a674535a6
--- /dev/null
+++ b/scripts/frontend/parallel_ci_sequencer.js
@@ -0,0 +1,41 @@
+const Sequencer = require('@jest/test-sequencer').default;
+
+class ParallelCISequencer extends Sequencer {
+ constructor() {
+ super();
+ this.ciNodeIndex = Number(process.env.CI_NODE_INDEX || '1');
+ this.ciNodeTotal = Number(process.env.CI_NODE_TOTAL || '1');
+ }
+
+ sort(tests) {
+ const sortedTests = this.sortByPath(tests);
+ const testsForThisRunner = this.distributeAcrossCINodes(sortedTests);
+
+ console.log(`CI_NODE_INDEX: ${this.ciNodeIndex}`);
+ console.log(`CI_NODE_TOTAL: ${this.ciNodeTotal}`);
+ console.log(`Total number of tests: ${tests.length}`);
+ console.log(`Total number of tests for this runner: ${testsForThisRunner.length}`);
+
+ return testsForThisRunner;
+ }
+
+ sortByPath(tests) {
+ return tests.sort((test1, test2) => {
+ if (test1.path < test2.path) {
+ return -1;
+ }
+ if (test1.path > test2.path) {
+ return 1;
+ }
+ return 0;
+ });
+ }
+
+ distributeAcrossCINodes(tests) {
+ return tests.filter((test, index) => {
+ return index % this.ciNodeTotal === this.ciNodeIndex - 1;
+ });
+ }
+}
+
+module.exports = ParallelCISequencer;
diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb
index ad984cf07e2..d50cf16d8ef 100644
--- a/spec/features/issues/user_edits_issue_spec.rb
+++ b/spec/features/issues/user_edits_issue_spec.rb
@@ -143,16 +143,11 @@ describe "Issues > User edits issue", :js do
end
it 'allows user to unselect themselves' do
- issue2 = create(:issue, project: project, author: user)
+ issue2 = create(:issue, project: project, author: user, assignees: [user])
visit project_issue_path(project, issue2)
page.within '.assignee' do
- click_link 'Edit'
- click_link user.name
-
- close_dropdown_menu_if_visible
-
page.within '.value .author' do
expect(page).to have_content user.name
end
diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb
index bab6251a5d4..8f63ce2a197 100644
--- a/spec/features/profiles/active_sessions_spec.rb
+++ b/spec/features/profiles/active_sessions_spec.rb
@@ -11,81 +11,77 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
let(:admin) { create(:admin) }
- around do |example|
- Timecop.freeze(Time.zone.parse('2018-03-12 09:06')) do
- example.run
- end
- end
-
it 'User sees their active sessions' do
- Capybara::Session.new(:session1)
- Capybara::Session.new(:session2)
- Capybara::Session.new(:session3)
-
- # note: headers can only be set on the non-js (aka. rack-test) driver
- using_session :session1 do
- Capybara.page.driver.header(
- 'User-Agent',
- 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0'
- )
-
- gitlab_sign_in(user)
- end
-
- # set an additional session on another device
- using_session :session2 do
- Capybara.page.driver.header(
- 'User-Agent',
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12B466 [FBDV/iPhone7,2]'
- )
-
- gitlab_sign_in(user)
- end
-
- # set an admin session impersonating the user
- using_session :session3 do
- Capybara.page.driver.header(
- 'User-Agent',
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'
- )
-
- gitlab_sign_in(admin)
-
- visit admin_user_path(user)
-
- click_link 'Impersonate'
- end
-
- using_session :session1 do
- visit profile_active_sessions_path
-
- expect(page).to(
- have_selector('ul.list-group li.list-group-item', { text: 'Signed in on',
- count: 2 }))
-
- expect(page).to have_content(
- '127.0.0.1 ' \
- 'This is your current session ' \
- 'Firefox on Ubuntu ' \
- 'Signed in on 12 Mar 09:06'
- )
-
- expect(page).to have_selector '[title="Desktop"]', count: 1
-
- expect(page).to have_content(
- '127.0.0.1 ' \
- 'Last accessed on 12 Mar 09:06 ' \
- 'Mobile Safari on iOS ' \
- 'Signed in on 12 Mar 09:06'
- )
-
- expect(page).to have_selector '[title="Smartphone"]', count: 1
-
- expect(page).not_to have_content('Chrome on Windows')
+ Timecop.freeze(Time.zone.parse('2018-03-12 09:06')) do
+ Capybara::Session.new(:session1)
+ Capybara::Session.new(:session2)
+ Capybara::Session.new(:session3)
+
+ # note: headers can only be set on the non-js (aka. rack-test) driver
+ using_session :session1 do
+ Capybara.page.driver.header(
+ 'User-Agent',
+ 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0'
+ )
+
+ gitlab_sign_in(user)
+ end
+
+ # set an additional session on another device
+ using_session :session2 do
+ Capybara.page.driver.header(
+ 'User-Agent',
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12B466 [FBDV/iPhone7,2]'
+ )
+
+ gitlab_sign_in(user)
+ end
+
+ # set an admin session impersonating the user
+ using_session :session3 do
+ Capybara.page.driver.header(
+ 'User-Agent',
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'
+ )
+
+ gitlab_sign_in(admin)
+
+ visit admin_user_path(user)
+
+ click_link 'Impersonate'
+ end
+
+ using_session :session1 do
+ visit profile_active_sessions_path
+
+ expect(page).to(
+ have_selector('ul.list-group li.list-group-item', { text: 'Signed in on',
+ count: 2 }))
+
+ expect(page).to have_content(
+ '127.0.0.1 ' \
+ 'This is your current session ' \
+ 'Firefox on Ubuntu ' \
+ 'Signed in on 12 Mar 09:06'
+ )
+
+ expect(page).to have_selector '[title="Desktop"]', count: 1
+
+ expect(page).to have_content(
+ '127.0.0.1 ' \
+ 'Last accessed on 12 Mar 09:06 ' \
+ 'Mobile Safari on iOS ' \
+ 'Signed in on 12 Mar 09:06'
+ )
+
+ expect(page).to have_selector '[title="Smartphone"]', count: 1
+
+ expect(page).not_to have_content('Chrome on Windows')
+ end
end
end
- it 'User can revoke a session', :js, :redis_session_store do
+ it 'User can revoke a session', :js do
Capybara::Session.new(:session1)
Capybara::Session.new(:session2)
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 0143461eadb..56ee2819bdb 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -200,7 +200,7 @@ describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
end
end
- context 'when third party offers are disabled' do
+ context 'when third party offers are disabled', :clean_gitlab_redis_shared_state do
let(:admin) { create(:admin) }
before do
diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb
index 33113dce620..40c54b2f873 100644
--- a/spec/features/projects/snippets/create_snippet_spec.rb
+++ b/spec/features/projects/snippets/create_snippet_spec.rb
@@ -9,7 +9,7 @@ describe 'Projects > Snippets > Create Snippet', :js do
let_it_be(:project) { create(:project, :public) }
def description_field
- find('.js-description-input input,textarea')
+ find('.js-description-input').find('input,textarea')
end
def fill_form
diff --git a/spec/features/snippets/spam_snippets_spec.rb b/spec/features/snippets/spam_snippets_spec.rb
index dac36ba2b28..02b807ce071 100644
--- a/spec/features/snippets/spam_snippets_spec.rb
+++ b/spec/features/snippets/spam_snippets_spec.rb
@@ -6,7 +6,7 @@ describe 'User creates snippet', :js do
let(:user) { create(:user) }
def description_field
- find('.js-description-input input,textarea')
+ find('.js-description-input').find('input,textarea')
end
before do
diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index 7584789b99d..69a44513e7f 100644
--- a/spec/features/snippets/user_creates_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -14,7 +14,7 @@ describe 'User creates snippet', :js do
end
def description_field
- find('.js-description-input input,textarea')
+ find('.js-description-input').find('input,textarea')
end
def fill_form
diff --git a/spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap b/spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap
index 426bc5c0e6c..c072950f3e2 100644
--- a/spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap
+++ b/spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap
@@ -89,6 +89,8 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
title="Copy login command"
type="button"
>
+ <!---->
+
<svg
class="gl-icon s16"
>
@@ -126,6 +128,8 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
title="Copy build command"
type="button"
>
+ <!---->
+
<svg
class="gl-icon s16"
>
@@ -155,6 +159,8 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
title="Copy push command"
type="button"
>
+ <!---->
+
<svg
class="gl-icon s16"
>
diff --git a/spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap
index 2abcc53bf14..1f54405928b 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap
@@ -8,6 +8,8 @@ exports[`Expand button on click when short text is provided renders button after
style="display: none;"
type="button"
>
+ <!---->
+
<svg
aria-hidden="true"
class="s12 ic-ellipsis_h"
@@ -32,6 +34,8 @@ exports[`Expand button on click when short text is provided renders button after
style=""
type="button"
>
+ <!---->
+
<svg
aria-hidden="true"
class="s12 ic-ellipsis_h"
@@ -51,6 +55,8 @@ exports[`Expand button when short text is provided renders button before text 1`
class="btn js-text-expander-prepend text-expander btn-blank btn-secondary btn-md"
type="button"
>
+ <!---->
+
<svg
aria-hidden="true"
class="s12 ic-ellipsis_h"
@@ -75,6 +81,8 @@ exports[`Expand button when short text is provided renders button before text 1`
style="display: none;"
type="button"
>
+ <!---->
+
<svg
aria-hidden="true"
class="s12 ic-ellipsis_h"
diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb
index 1f82f316aa7..9e5f6dd2037 100644
--- a/spec/graphql/types/base_field_spec.rb
+++ b/spec/graphql/types/base_field_spec.rb
@@ -46,7 +46,15 @@ describe Types::BaseField do
expect(field.to_graphql.complexity).to eq 12
end
- context 'when field has a resolver proc' do
+ context 'when field has a resolver' do
+ context 'when a valid complexity is already set' do
+ let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: resolver, complexity: 2, max_page_size: 100, null: true) }
+
+ it 'uses this complexity' do
+ expect(field.to_graphql.complexity).to eq 2
+ end
+ end
+
context 'and is a connection' do
let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: resolver, max_page_size: 100, null: true) }
@@ -59,6 +67,17 @@ describe Types::BaseField do
expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2
expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4
end
+
+ context 'when graphql_resolver_complexity is disabled' do
+ before do
+ stub_feature_flags(graphql_resolver_complexity: false)
+ end
+
+ it 'sets default field complexity' do
+ expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 1
+ expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 1
+ end
+ end
end
context 'and is not a connection' do
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 6c44ffc6ec9..d396f2d9271 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -145,34 +145,30 @@ describe('ReadyToMerge', () => {
});
});
- describe('mergeButtonClass', () => {
- const defaultClass = 'btn btn-sm btn-success accept-merge-request';
- const failedClass = `${defaultClass} btn-danger`;
- const inActionClass = `${defaultClass} btn-info`;
-
+ describe('mergeButtonVariant', () => {
it('defaults to success class', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
- expect(vm.mergeButtonClass).toEqual(defaultClass);
+ expect(vm.mergeButtonVariant).toEqual('success');
});
it('returns success class for success status', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
Vue.set(vm.mr, 'pipeline', true);
- expect(vm.mergeButtonClass).toEqual(defaultClass);
+ expect(vm.mergeButtonVariant).toEqual('success');
});
it('returns info class for pending status', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', [MTWPS_MERGE_STRATEGY]);
- expect(vm.mergeButtonClass).toEqual(inActionClass);
+ expect(vm.mergeButtonVariant).toEqual('info');
});
- it('returns failed class for failed status', () => {
+ it('returns danger class for failed status', () => {
vm.mr.hasCI = true;
- expect(vm.mergeButtonClass).toEqual(failedClass);
+ expect(vm.mergeButtonVariant).toEqual('danger');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js
index 5844dad42ff..9153231b974 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js
@@ -43,7 +43,7 @@ describe('Wip', () => {
is_new_mr_data: true,
};
- describe('removeWIP', () => {
+ describe('handleRemoveWIP', () => {
it('should make a request to service and handle response', done => {
const vm = createComponent();
@@ -57,7 +57,7 @@ describe('Wip', () => {
}),
);
- vm.removeWIP();
+ vm.handleRemoveWIP();
setTimeout(() => {
expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
diff --git a/spec/lib/gitlab/danger/commit_linter_spec.rb b/spec/lib/gitlab/danger/commit_linter_spec.rb
index 0cf7ac64e43..3d0d51caf29 100644
--- a/spec/lib/gitlab/danger/commit_linter_spec.rb
+++ b/spec/lib/gitlab/danger/commit_linter_spec.rb
@@ -152,6 +152,18 @@ describe Gitlab::Danger::CommitLinter do
end
end
+ context 'when subject is a WIP' do
+ let(:final_message) { 'A B C' }
+ # commit message with prefix will be over max length. commit message without prefix will be of maximum size
+ let(:commit_message) { described_class::WIP_PREFIX + final_message + 'D' * (described_class::WARN_SUBJECT_LENGTH - final_message.size) }
+
+ it 'does not have any problems' do
+ commit_linter.lint
+
+ expect(commit_linter.problems).to be_empty
+ end
+ end
+
context 'when subject is too short and too long' do
let(:commit_message) { 'A ' + 'B' * described_class::MAX_LINE_LENGTH }
@@ -183,7 +195,7 @@ describe Gitlab::Danger::CommitLinter do
end
end
- context 'when subject ands with a period' do
+ context 'when subject ends with a period' do
let(:commit_message) { 'A B C.' }
it 'adds a problem' do
diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb
index 67353475251..b8537dd39f6 100644
--- a/spec/models/concerns/spammable_spec.rb
+++ b/spec/models/concerns/spammable_spec.rb
@@ -36,6 +36,46 @@ describe Spammable do
end
end
+ describe '#invalidate_if_spam' do
+ using RSpec::Parameterized::TableSyntax
+
+ context 'when the model is spam' do
+ where(:recaptcha_enabled, :error) do
+ true | /solve the reCAPTCHA to proceed/
+ false | /has been discarded/
+ end
+
+ with_them do
+ subject { invalidate_if_spam(true, recaptcha_enabled) }
+
+ it 'has an error related to spam on the model' do
+ expect(subject.errors.messages[:base]).to match_array error
+ end
+ end
+ end
+
+ context 'when the model is not spam' do
+ [true, false].each do |enabled|
+ let(:recaptcha_enabled) { enabled }
+
+ subject { invalidate_if_spam(false, recaptcha_enabled) }
+
+ it 'returns no error' do
+ expect(subject.errors.messages[:base]).to be_empty
+ end
+ end
+ end
+
+ def invalidate_if_spam(is_spam, recaptcha_enabled)
+ stub_application_setting(recaptcha_enabled: recaptcha_enabled)
+
+ issue.tap do |i|
+ i.spam = is_spam
+ i.invalidate_if_spam
+ end
+ end
+ end
+
describe '#submittable_as_spam_by?' do
let(:admin) { build(:admin) }
let(:user) { build(:user) }
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index ece80424791..92c5c52be7d 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -152,4 +152,52 @@ describe 'GraphQL' do
end
end
end
+
+ describe 'resolver complexity' do
+ let_it_be(:project) { create(:project, :public) }
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field(resource, {}, 'edges { node { iid } }')
+ )
+ end
+
+ before do
+ stub_const('GitlabSchema::DEFAULT_MAX_COMPLEXITY', 6)
+ stub_feature_flags(graphql_resolver_complexity: true)
+ end
+
+ context 'when fetching single resource' do
+ let(:resource) { 'issues(first: 1)' }
+
+ it 'processes the query' do
+ post_graphql(query)
+
+ expect(graphql_errors).to be_nil
+ end
+ end
+
+ context 'when fetching too many resources' do
+ let(:resource) { 'issues(first: 100)' }
+
+ it 'returns an error' do
+ post_graphql(query)
+
+ expect_graphql_errors_to_include(/which exceeds max complexity/)
+ end
+
+ context 'when graphql_resolver_complexity is disabled' do
+ before do
+ stub_feature_flags(graphql_resolver_complexity: false)
+ end
+
+ it 'processes the query' do
+ post_graphql(query)
+
+ expect(graphql_errors).to be_nil
+ end
+ end
+ end
+ end
end
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 1a64ffea8f4..af179e81b08 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -60,7 +60,7 @@ Capybara.register_driver :chrome do |app|
)
end
-Capybara.server = :webrick
+Capybara.server = :puma
Capybara.javascript_driver = :chrome
Capybara.default_max_wait_time = timeout
Capybara.ignore_hidden_elements = true
diff --git a/yarn.lock b/yarn.lock
index 94ac80c7061..12d3c3144c1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -740,15 +740,15 @@
dependencies:
vue-eslint-parser "^7.0.0"
-"@gitlab/svgs@^1.99.0":
- version "1.99.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.99.0.tgz#bcf971c3a14920218e86da71ca115244b23a4a3f"
- integrity sha512-bxYFxnmuoWPBU9isL3/CYFlr+k2YWU47Pq0vfmSmL7uLnb/vYymfZZF5p3erlZ62WGwuT3kp4GnuoZBMfmannA==
+"@gitlab/svgs@^1.101.0":
+ version "1.101.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.101.0.tgz#5440ada1774758e42fd67212d495a29523dd0d5e"
+ integrity sha512-GE6wRn0UqA5f0pmX5wL/vTgUnAgZEdTIDam+OTMuMxf5a1jfxc1KlSLudgZbS3O/W79jN4uMkTdZ7X8gEzAChw==
-"@gitlab/ui@^9.11.2":
- version "9.11.2"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.11.2.tgz#ffb58bb10c6a8cd503a622946ed78512e9c18c6d"
- integrity sha512-9acsjQ9+hSaAIGpiARNF4XfQUhulWiausns9JUTrN9XEQpa1o/EsDYqwP0HfSOMZ8JhnjSI2NGYVf+LIH5oudg==
+"@gitlab/ui@^9.16.0":
+ version "9.16.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.16.0.tgz#ac66b55cffdfd9ac2df2abddb11445edc3494732"
+ integrity sha512-9PbFgqNxIAGn1LyIcnlqQuNGAiBT/fqTx8vPdaDQkdScFZksZOBwiIhpxnRk9UFABC3h+0TNeHgVigD7TKGqJw==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"
@@ -5418,6 +5418,11 @@ has-flag@^3.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
has-symbols@^1.0.0, has-symbols@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
@@ -5552,6 +5557,11 @@ html-entities@^1.2.1:
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=
+html-escaper@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491"
+ integrity sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig==
+
html-minifier@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-4.0.0.tgz#cca9aad8bce1175e02e17a8c33e46d8988889f56"
@@ -6283,6 +6293,11 @@ istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5:
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49"
integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==
+istanbul-lib-coverage@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec"
+ integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==
+
istanbul-lib-hook@^2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz#c95695f383d4f8f60df1f04252a9550e15b5b133"
@@ -6312,6 +6327,15 @@ istanbul-lib-report@^2.0.4, istanbul-lib-report@^2.0.8:
make-dir "^2.1.0"
supports-color "^6.1.0"
+istanbul-lib-report@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6"
+ integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==
+ dependencies:
+ istanbul-lib-coverage "^3.0.0"
+ make-dir "^3.0.0"
+ supports-color "^7.1.0"
+
istanbul-lib-source-maps@^3.0.1, istanbul-lib-source-maps@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8"
@@ -6330,6 +6354,14 @@ istanbul-reports@^2.1.1, istanbul-reports@^2.2.4:
dependencies:
handlebars "^4.1.2"
+istanbul-reports@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.0.tgz#d4d16d035db99581b6194e119bbf36c963c5eb70"
+ integrity sha512-2osTcC8zcOSUkImzN2EWQta3Vdi4WjjKw99P2yWx5mLnigAM0Rd5uYFn1cf2i/Ois45GkNjaoTqc5CxgMSX80A==
+ dependencies:
+ html-escaper "^2.0.0"
+ istanbul-lib-report "^3.0.0"
+
istextorbinary@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.2.1.tgz#a5231a08ef6dd22b268d0895084cf8d58b5bec53"
@@ -10836,6 +10868,13 @@ supports-color@^5.2.0, supports-color@^5.3.0, supports-color@^5.4.0:
dependencies:
has-flag "^3.0.0"
+supports-color@^7.1.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
+ integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
+ dependencies:
+ has-flag "^4.0.0"
+
svg-tags@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"