Commit 66d42037 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent a496f41f
...@@ -44,13 +44,15 @@ docs lint: ...@@ -44,13 +44,15 @@ docs lint:
- .default-tags - .default-tags
- .default-retry - .default-retry
- .docs:rules:docs-lint - .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 stage: test
needs: [] needs: []
script: script:
- scripts/lint-doc.sh - scripts/lint-doc.sh
# Lint Markdown # Lint Markdown
- markdownlint --config .markdownlint.json 'doc/**/*.md' - markdownlint --config .markdownlint.json 'doc/**/*.md'
# Lint content (error-level text-scoped rules only)
- vale --minAlertLevel error --ignore-syntax doc
# Prepare docs for build # Prepare docs for build
- mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX - mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX
- cd /tmp/gitlab-docs - cd /tmp/gitlab-docs
......
...@@ -207,11 +207,11 @@ karma-as-if-foss: ...@@ -207,11 +207,11 @@ karma-as-if-foss:
extends: .frontend-job-base extends: .frontend-job-base
script: script:
- date - date
- yarn jest --ci --coverage - yarn jest --ci --coverage --testSequencer ./scripts/frontend/parallel_ci_sequencer.js
cache: cache:
key: jest key: jest
paths: paths:
- tmp/jest/jest/ - tmp/cache/jest/
policy: pull-push policy: pull-push
jest: jest:
...@@ -229,6 +229,7 @@ jest: ...@@ -229,6 +229,7 @@ jest:
- tmp/tests/frontend/ - tmp/tests/frontend/
reports: reports:
junit: junit_jest.xml junit: junit_jest.xml
parallel: 2
jest-as-if-foss: jest-as-if-foss:
extends: extends:
...@@ -239,6 +240,26 @@ jest-as-if-foss: ...@@ -239,6 +240,26 @@ jest-as-if-foss:
cache: cache:
policy: pull 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: .qa-frontend-node:
extends: extends:
- .default-tags - .default-tags
......
...@@ -123,5 +123,6 @@ ...@@ -123,5 +123,6 @@
"YouTrack" "YouTrack"
], ],
"code_blocks": false "code_blocks": false
} },
"code-fence-style": false
} }
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import { GlIcon } from '@gitlab/ui'; import { GlIcon, GlButton } from '@gitlab/ui';
import successSvg from 'icons/_icon_status_success.svg'; import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg'; import warningSvg from 'icons/_icon_status_warning.svg';
import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge'; import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge';
...@@ -26,6 +26,7 @@ export default { ...@@ -26,6 +26,7 @@ export default {
CommitEdit, CommitEdit,
CommitMessageDropdown, CommitMessageDropdown,
GlIcon, GlIcon,
GlButton,
MergeImmediatelyConfirmationDialog: () => MergeImmediatelyConfirmationDialog: () =>
import( import(
'ee_component/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue' 'ee_component/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue'
...@@ -67,18 +68,13 @@ export default { ...@@ -67,18 +68,13 @@ export default {
return 'success'; return 'success';
}, },
mergeButtonClass() { mergeButtonVariant() {
const defaultClass = 'btn btn-sm btn-success accept-merge-request';
const failedClass = `${defaultClass} btn-danger`;
const inActionClass = `${defaultClass} btn-info`;
if (this.status === 'failed') { if (this.status === 'failed') {
return failedClass; return 'danger';
} else if (this.status === 'pending') { } else if (this.status === 'pending') {
return inActionClass; return 'info';
} }
return 'success';
return defaultClass;
}, },
iconClass() { iconClass() {
if ( if (
...@@ -267,16 +263,16 @@ export default { ...@@ -267,16 +263,16 @@ export default {
<div class="media-body"> <div class="media-body">
<div class="mr-widget-body-controls media space-children"> <div class="mr-widget-body-controls media space-children">
<span class="btn-group"> <span class="btn-group">
<button <gl-button
size="sm"
class="qa-merge-button accept-merge-request"
:variant="mergeButtonVariant"
:disabled="isMergeButtonDisabled" :disabled="isMergeButtonDisabled"
:class="mergeButtonClass" :loading="isMakingRequest"
type="button"
class="qa-merge-button"
@click="handleMergeButtonClick(isAutoMergeAvailable)" @click="handleMergeButtonClick(isAutoMergeAvailable)"
> >
<i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
{{ mergeButtonText }} {{ mergeButtonText }}
</button> </gl-button>
<button <button
v-if="shouldShowMergeImmediatelyDropdown" v-if="shouldShowMergeImmediatelyDropdown"
:disabled="isMergeButtonDisabled" :disabled="isMergeButtonDisabled"
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import { __ } from '~/locale'; import { GlButton } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import createFlash from '~/flash'; 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 tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
export default { export default {
name: 'WorkInProgress', name: 'WorkInProgress',
components: { components: {
statusIcon, StatusIcon,
GlButton,
}, },
directives: { directives: {
tooltip, tooltip,
...@@ -23,8 +25,15 @@ export default { ...@@ -23,8 +25,15 @@ export default {
isMakingRequest: false, 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: { methods: {
removeWIP() { handleRemoveWIP() {
this.isMakingRequest = true; this.isMakingRequest = true;
this.service this.service
.removeWIP() .removeWIP()
...@@ -52,29 +61,22 @@ export default { ...@@ -52,29 +61,22 @@ export default {
<i <i
v-tooltip v-tooltip
class="fa fa-question-circle" class="fa fa-question-circle"
:title=" :title="wipInfoTooltip"
s__( :aria-label="wipInfoTooltip"
'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',
)
"
> >
</i> </i>
</span> </span>
<button <gl-button
v-if="mr.removeWIPPath" v-if="mr.removeWIPPath"
size="sm"
variant="default"
:disabled="isMakingRequest" :disabled="isMakingRequest"
type="button" :loading="isMakingRequest"
class="btn btn-default btn-sm js-remove-wip" class="js-remove-wip"
@click="removeWIP" @click="handleRemoveWIP"
> >
<i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"> </i>
{{ s__('mrWidget|Resolve WIP status') }} {{ s__('mrWidget|Resolve WIP status') }}
</button> </gl-button>
</div> </div>
</div> </div>
</template> </template>
...@@ -28,6 +28,10 @@ module Resolvers ...@@ -28,6 +28,10 @@ module Resolvers
end end
end end
def self.complexity
0
end
def self.resolver_complexity(args, child_complexity:) def self.resolver_complexity(args, child_complexity:)
complexity = 1 complexity = 1
complexity += 1 if args[:sort] complexity += 1 if args[:sort]
......
...@@ -9,7 +9,7 @@ module Types ...@@ -9,7 +9,7 @@ module Types
def initialize(*args, **kwargs, &block) def initialize(*args, **kwargs, &block)
@calls_gitaly = !!kwargs.delete(:calls_gitaly) @calls_gitaly = !!kwargs.delete(:calls_gitaly)
@constant_complexity = !!kwargs[:complexity] @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] @feature_flag = kwargs[:feature_flag]
kwargs = check_feature_flag(kwargs) kwargs = check_feature_flag(kwargs)
...@@ -51,7 +51,9 @@ module Types ...@@ -51,7 +51,9 @@ module Types
args args
end end
def field_complexity(resolver_class) def field_complexity(resolver_class, current)
return current if current.present? && current > 0
if resolver_class if resolver_class
field_resolver_complexity field_resolver_complexity
else else
...@@ -66,22 +68,30 @@ module Types ...@@ -66,22 +68,30 @@ module Types
# proc because we set complexity depending on arguments and number of # proc because we set complexity depending on arguments and number of
# items which can be loaded. # items which can be loaded.
proc do |ctx, args, child_complexity| proc do |ctx, args, child_complexity|
next base_complexity unless resolver_complexity_enabled?(ctx)
# Resolvers may add extra complexity depending on used arguments # 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 = child_complexity + self.resolver&.try(:resolver_complexity, args, child_complexity: child_complexity).to_i
complexity += 1 if calls_gitaly? complexity += 1 if calls_gitaly?
complexity += complexity * connection_complexity_multiplier(ctx, args)
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.to_i complexity.to_i
end end
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
end end
...@@ -16,7 +16,7 @@ module Spammable ...@@ -16,7 +16,7 @@ module Spammable
attr_accessor :spam_log attr_accessor :spam_log
alias_method :spam?, :spam 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 cattr_accessor :spammable_attrs, instance_accessor: false do
[] []
...@@ -37,7 +37,7 @@ module Spammable ...@@ -37,7 +37,7 @@ module Spammable
end end
end end
def check_for_spam def invalidate_if_spam
error_msg = if Gitlab::Recaptcha.enabled? error_msg = if Gitlab::Recaptcha.enabled?
"Your #{spammable_entity_type} has been recognized as spam. "\ "Your #{spammable_entity_type} has been recognized as spam. "\
"Please, change the content or solve the reCAPTCHA to proceed." "Please, change the content or solve the reCAPTCHA to proceed."
......
---
title: Update iOS (Swift) project template logo
merge_request: 25049
author:
type: changed
require 'gitlab/testing/request_blocker_middleware' require 'gitlab/testing/request_blocker_middleware'
require 'gitlab/testing/request_inspector_middleware' require 'gitlab/testing/request_inspector_middleware'
require 'gitlab/testing/clear_thread_memory_cache_middleware'
Rails.application.configure do Rails.application.configure do
# Make sure the middleware is inserted first in middleware chain # 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::RequestBlockerMiddleware)
config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::RequestInspectorMiddleware) 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 # Settings specified here will take precedence over those in config/application.rb
......
...@@ -75,7 +75,7 @@ must disable the **primary** node. ...@@ -75,7 +75,7 @@ must disable the **primary** node.
single recommendation. You may need to: single recommendation. You may need to:
- Reconfigure the load balancers. - 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). node in order to stop usage of the **primary** node).
- Stop the virtual servers. - Stop the virtual servers.
- Block traffic through a firewall. - Block traffic through a firewall.
......
...@@ -337,8 +337,8 @@ How this feature will work: ...@@ -337,8 +337,8 @@ How this feature will work:
### Be careful with sensitive information ### Be careful with sensitive information
With some [Runner Executors](https://docs.gitlab.com/runner/executors/README.html), 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 if you can run a job on the Runner, you can get full access to the file system,
and get the token of the Runner. With shared Runners, this means that anyone 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 that runs jobs on the Runner, can access anyone else's code that runs on the
Runner. Runner.
......
...@@ -825,10 +825,8 @@ This could result in some unexpected behavior, including: ...@@ -825,10 +825,8 @@ This could result in some unexpected behavior, including:
Available rule clauses include: Available rule clauses include:
- [`if`](#rulesif) - [`if`](#rulesif) (similar to [`only:variables`](#onlyvariablesexceptvariables))
(similar to [`only:variables`](#onlyvariablesexceptvariables)). - [`changes`](#ruleschanges) (same as [`only:changes`](#onlychangesexceptchanges))
- [`changes`](#ruleschanges)
(same as [`only:changes`](#onlychangesexceptchanges)).
- [`exists`](#rulesexists) - [`exists`](#rulesexists)
For example, using `if`. This configuration specifies that `job` should be built For example, using `if`. This configuration specifies that `job` should be built
...@@ -895,7 +893,6 @@ docker build: ...@@ -895,7 +893,6 @@ docker build:
- if: '$VAR == "string value"' - 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: 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 - when: on_success # If neither of the first rules match, set to on_success
``` ```
In this example, a job either set to: In this example, a job either set to:
...@@ -956,6 +953,47 @@ job: ...@@ -956,6 +953,47 @@ job:
In this example, if the first rule matches, then the job will have `when: manual` and `allow_failure: true`. 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 #### Complex rule clauses
To conjoin `if`, `changes`, and `exists` clauses with an AND, use them in the To conjoin `if`, `changes`, and `exists` clauses with an AND, use them in the
......
...@@ -146,6 +146,7 @@ graph RL; ...@@ -146,6 +146,7 @@ graph RL;
U2["frontend-fixtures-as-if-foss<br/>(EE default refs only)"]; U2["frontend-fixtures-as-if-foss<br/>(EE default refs only)"];
V["webpack-dev-server, static-analysis"]; V["webpack-dev-server, static-analysis"];
M[coverage]; M[coverage];
O[coverage-frontend];
N["pages (master only)"]; N["pages (master only)"];
Q[package-and-qa]; Q[package-and-qa];
S["RSpec<br/>(e.g. rspec unit pg9)"] S["RSpec<br/>(e.g. rspec unit pg9)"]
...@@ -190,6 +191,7 @@ subgraph "`test` stage" ...@@ -190,6 +191,7 @@ subgraph "`test` stage"
subgraph "`post-test` stage" subgraph "`post-test` stage"
M --> |happens after| S M --> |happens after| S
O --> |needs `jest`| I
end end
subgraph "`review-prepare` stage" subgraph "`review-prepare` stage"
......
This diff is collapsed.
...@@ -41,7 +41,7 @@ Please note that for the deactivation option to be visible to an admin, the user ...@@ -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). Users can also be deactivated using the [GitLab API](../../api/users.md#deactivate-user).
NOTE: **Note:** 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 ## Activating a user
...@@ -60,7 +60,7 @@ Users can also be activated using the [GitLab API](../../api/users.md#activate-u ...@@ -60,7 +60,7 @@ Users can also be activated using the [GitLab API](../../api/users.md#activate-u
NOTE: **Note:** NOTE: **Note:**
Activating a user will change the user's state to active and it consumes a 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:** TIP: **Tip:**
A deactivated user can also activate their account by themselves by simply logging back via the UI. A deactivated user can also activate their account by themselves by simply logging back via the UI.
...@@ -30,7 +30,7 @@ Personal projects, and group and user history of the blocked user will be left i ...@@ -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). Users can also be blocked using the [GitLab API](../../api/users.md#block-user).
NOTE: **Note:** 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 ## Unblocking a user
...@@ -45,4 +45,4 @@ Users can also be unblocked using the [GitLab API](../../api/users.md#unblock-us ...@@ -45,4 +45,4 @@ Users can also be unblocked using the [GitLab API](../../api/users.md#unblock-us
NOTE: **Note:** NOTE: **Note:**
Unblocking a user will change the user's state to active and it consumes a 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).
...@@ -117,7 +117,7 @@ service included with GitLab that coordinates the jobs. ...@@ -117,7 +117,7 @@ service included with GitLab that coordinates the jobs.
If the project is on GitLab.com, shared Runners are available If the project is on GitLab.com, shared Runners are available
(the first 2000 minutes are free, you can (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 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 project-specific Runner is desired, or there are no shared Runners, it is easy
to deploy one. to deploy one.
......
...@@ -104,7 +104,7 @@ Linux Shared Runners on GitLab.com run in [autoscale mode] and are powered by Go ...@@ -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, 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 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 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/). 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 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
......
...@@ -55,6 +55,14 @@ if (IS_EE) { ...@@ -55,6 +55,14 @@ if (IS_EE) {
collectCoverageFrom.push(rootDirEE.replace('$1', '/**/*.{js,vue}')); 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 // eslint-disable-next-line import/no-commonjs
module.exports = { module.exports = {
clearMocks: true, clearMocks: true,
...@@ -62,7 +70,7 @@ module.exports = { ...@@ -62,7 +70,7 @@ module.exports = {
moduleFileExtensions: ['js', 'json', 'vue'], moduleFileExtensions: ['js', 'json', 'vue'],
moduleNameMapper, moduleNameMapper,
collectCoverageFrom, collectCoverageFrom,
coverageDirectory: '<rootDir>/coverage-frontend/', coverageDirectory: coverageDirectory(),
coverageReporters: ['json', 'lcov', 'text-summary', 'clover'], coverageReporters: ['json', 'lcov', 'text-summary', 'clover'],
cacheDirectory: '<rootDir>/tmp/cache/jest', cacheDirectory: '<rootDir>/tmp/cache/jest',
modulePathIgnorePatterns: ['<rootDir>/.yarn-cache/'], modulePathIgnorePatterns: ['<rootDir>/.yarn-cache/'],
......
...@@ -14,6 +14,7 @@ module Gitlab ...@@ -14,6 +14,7 @@ module Gitlab
MAX_CHANGED_LINES_IN_COMMIT = 30 MAX_CHANGED_LINES_IN_COMMIT = 30
SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(#|!|&|%)\d+\b}.freeze SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(#|!|&|%)\d+\b}.freeze
DEFAULT_SUBJECT_DESCRIPTION = 'commit subject' DEFAULT_SUBJECT_DESCRIPTION = 'commit subject'
WIP_PREFIX = 'WIP: '
PROBLEMS = { PROBLEMS = {
subject_too_short: "The %s must contain at least #{MIN_SUBJECT_WORDS_COUNT} words", 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", subject_too_long: "The %s may not be longer than #{MAX_LINE_LENGTH} characters",
...@@ -164,7 +165,7 @@ module Gitlab ...@@ -164,7 +165,7 @@ module Gitlab
end end
def subject def subject
message_parts[0] message_parts[0].delete_prefix(WIP_PREFIX)
end end
def separator def separator
......
...@@ -40,7 +40,7 @@ module Gitlab ...@@ -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('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('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('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('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('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'), ProjectTemplate.new('gomicro', 'Go Micro', _('Go Micro is a framework for micro service development.'), 'https://gitlab.com/gitlab-org/project-templates/go-micro'),
......
# 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
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);
});
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;
...@@ -143,16 +143,11 @@ describe "Issues > User edits issue", :js do ...@@ -143,16 +143,11 @@ describe "Issues > User edits issue", :js do
end end
it 'allows user to unselect themselves' do 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) visit project_issue_path(project, issue2)
page.within '.assignee' do page.within '.assignee' do
click_link 'Edit'
click_link user.name
close_dropdown_menu_if_visible
page.within '.value .author' do page.within '.value .author' do
expect(page).to have_content user.name expect(page).to have_content user.name
end end
......
...@@ -11,81 +11,77 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do ...@@ -11,81 +11,77 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
let(:admin) { create(:admin) } 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 it 'User sees their active sessions' do
Capybara::Session.new(:session1) Timecop.freeze(Time.zone.parse('2018-03-12 09:06')) do
Capybara::Session.new(:session2) Capybara::Session.new(:session1)
Capybara::Session.new(:session3) 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 # note: headers can only be set on the non-js (aka. rack-test) driver
Capybara.page.driver.header( using_session :session1 do
'User-Agent', Capybara.page.driver.header(
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0' 'User-Agent',
) 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0'
)
gitlab_sign_in(user)
end gitlab_sign_in(user)
end
# set an additional session on another device
using_session :session2 do # set an additional session on another device
Capybara.page.driver.header( using_session :session2 do
'User-Agent', Capybara.page.driver.header(
'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]' '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 gitlab_sign_in(user)
end
# set an admin session impersonating the user
using_session :session3 do # set an admin session impersonating the user
Capybara.page.driver.header( using_session :session3 do
'User-Agent', Capybara.page.driver.header(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36' '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)
gitlab_sign_in(admin)
visit admin_user_path(user)
visit admin_user_path(user)
click_link 'Impersonate'
end click_link 'Impersonate'
end
using_session :session1 do
visit profile_active_sessions_path 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', expect(page).to(
count: 2 })) have_selector('ul.list-group li.list-group-item', { text: 'Signed in on',
count: 2 }))
expect(page).to have_content(
'127.0.0.1 ' \ expect(page).to have_content(
'This is your current session ' \ '127.0.0.1 ' \
'Firefox on Ubuntu ' \ 'This is your current session ' \
'Signed in on 12 Mar 09:06' 'Firefox on Ubuntu ' \
) 'Signed in on 12 Mar 09:06'
)
expect(page).to have_selector '[title="Desktop"]', count: 1
expect(page).to have_selector '[title="Desktop"]', count: 1
expect(page).to have_content(
'127.0.0.1 ' \ expect(page).to have_content(
'Last accessed on 12 Mar 09:06 ' \ '127.0.0.1 ' \
'Mobile Safari on iOS ' \ 'Last accessed on 12 Mar 09:06 ' \
'Signed in 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).to have_selector '[title="Smartphone"]', count: 1
expect(page).not_to have_content('Chrome on Windows')
expect(page).not_to have_content('Chrome on Windows')
end
end 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(:session1)
Capybara::Session.new(:session2) Capybara::Session.new(:session2)
......
...@@ -200,7 +200,7 @@ describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do ...@@ -200,7 +200,7 @@ describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
end end
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) } let(:admin) { create(:admin) }
before do before do
......
...@@ -9,7 +9,7 @@ describe 'Projects > Snippets > Create Snippet', :js do ...@@ -9,7 +9,7 @@ describe 'Projects > Snippets > Create Snippet', :js do
let_it_be(:project) { create(:project, :public) } let_it_be(:project) { create(:project, :public) }
def description_field def description_field
find('.js-description-input input,textarea') find('.js-description-input').find('input,textarea')
end end
def fill_form def fill_form
......
...@@ -6,7 +6,7 @@ describe 'User creates snippet', :js do ...@@ -6,7 +6,7 @@ describe 'User creates snippet', :js do
let(:user) { create(:user) } let(:user) { create(:user) }
def description_field def description_field
find('.js-description-input input,textarea') find('.js-description-input').find('input,textarea')
end end
before do before do
......
...@@ -14,7 +14,7 @@ describe 'User creates snippet', :js do ...@@ -14,7 +14,7 @@ describe 'User creates snippet', :js do
end end
def description_field def description_field
find('.js-description-input input,textarea') find('.js-description-input').find('input,textarea')
end end
def fill_form def fill_form
......
...@@ -89,6 +89,8 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = ` ...@@ -89,6 +89,8 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
title="Copy login command" title="Copy login command"
type="button" type="button"
> >
<!---->
<svg <svg
class="gl-icon s16" class="gl-icon s16"
> >
...@@ -126,6 +128,8 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = ` ...@@ -126,6 +128,8 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
title="Copy build command" title="Copy build command"
type="button" type="button"
> >
<!---->
<svg <svg
class="gl-icon s16" class="gl-icon s16"
> >
...@@ -155,6 +159,8 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = ` ...@@ -155,6 +159,8 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
title="Copy push command" title="Copy push command"
type="button" type="button"
> >
<!---->
<svg <svg
class="gl-icon s16" class="gl-icon s16"
> >
......
...@@ -8,6 +8,8 @@ exports[`Expand button on click when short text is provided renders button after ...@@ -8,6 +8,8 @@ exports[`Expand button on click when short text is provided renders button after
style="display: none;" style="display: none;"
type="button" type="button"
> >
<!---->
<svg <svg
aria-hidden="true" aria-hidden="true"
class="s12 ic-ellipsis_h" class="s12 ic-ellipsis_h"
...@@ -32,6 +34,8 @@ exports[`Expand button on click when short text is provided renders button after ...@@ -32,6 +34,8 @@ exports[`Expand button on click when short text is provided renders button after
style="" style=""
type="button" type="button"
> >
<!---->
<svg <svg
aria-hidden="true" aria-hidden="true"
class="s12 ic-ellipsis_h" class="s12 ic-ellipsis_h"
...@@ -51,6 +55,8 @@ exports[`Expand button when short text is provided renders button before text 1` ...@@ -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" class="btn js-text-expander-prepend text-expander btn-blank btn-secondary btn-md"
type="button" type="button"
> >
<!---->
<svg <svg
aria-hidden="true" aria-hidden="true"
class="s12 ic-ellipsis_h" class="s12 ic-ellipsis_h"
...@@ -75,6 +81,8 @@ exports[`Expand button when short text is provided renders button before text 1` ...@@ -75,6 +81,8 @@ exports[`Expand button when short text is provided renders button before text 1`
style="display: none;" style="display: none;"
type="button" type="button"
> >
<!---->
<svg <svg
aria-hidden="true" aria-hidden="true"
class="s12 ic-ellipsis_h" class="s12 ic-ellipsis_h"
......
...@@ -46,7 +46,15 @@ describe Types::BaseField do ...@@ -46,7 +46,15 @@ describe Types::BaseField do
expect(field.to_graphql.complexity).to eq 12 expect(field.to_graphql.complexity).to eq 12
end 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 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) } 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 ...@@ -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 }, 2)).to eq 2
expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4 expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4
end 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 end
context 'and is not a connection' do context 'and is not a connection' do
......
...@@ -145,34 +145,30 @@ describe('ReadyToMerge', () => { ...@@ -145,34 +145,30 @@ describe('ReadyToMerge', () => {
}); });
}); });
describe('mergeButtonClass', () => { describe('mergeButtonVariant', () => {
const defaultClass = 'btn btn-sm btn-success accept-merge-request';
const failedClass = `${defaultClass} btn-danger`;
const inActionClass = `${defaultClass} btn-info`;
it('defaults to success class', () => { it('defaults to success class', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', []); Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.mergeButtonClass).toEqual(defaultClass); expect(vm.mergeButtonVariant).toEqual('success');
}); });
it('returns success class for success status', () => { it('returns success class for success status', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', []); Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
Vue.set(vm.mr, 'pipeline', true); Vue.set(vm.mr, 'pipeline', true);
expect(vm.mergeButtonClass).toEqual(defaultClass); expect(vm.mergeButtonVariant).toEqual('success');
}); });
it('returns info class for pending status', () => { it('returns info class for pending status', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', [MTWPS_MERGE_STRATEGY]); 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; vm.mr.hasCI = true;
expect(vm.mergeButtonClass).toEqual(failedClass); expect(vm.mergeButtonVariant).toEqual('danger');
}); });
}); });
......
...@@ -43,7 +43,7 @@ describe('Wip', () => { ...@@ -43,7 +43,7 @@ describe('Wip', () => {
is_new_mr_data: true, is_new_mr_data: true,
}; };
describe('removeWIP', () => { describe('handleRemoveWIP', () => {
it('should make a request to service and handle response', done => { it('should make a request to service and handle response', done => {
const vm = createComponent(); const vm = createComponent();
...@@ -57,7 +57,7 @@ describe('Wip', () => { ...@@ -57,7 +57,7 @@ describe('Wip', () => {
}), }),
); );
vm.removeWIP(); vm.handleRemoveWIP();
setTimeout(() => { setTimeout(() => {
expect(vm.isMakingRequest).toBeTruthy(); expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
......
...@@ -152,6 +152,18 @@ describe Gitlab::Danger::CommitLinter do ...@@ -152,6 +152,18 @@ describe Gitlab::Danger::CommitLinter do
end end
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 context 'when subject is too short and too long' do
let(:commit_message) { 'A ' + 'B' * described_class::MAX_LINE_LENGTH } let(:commit_message) { 'A ' + 'B' * described_class::MAX_LINE_LENGTH }
...@@ -183,7 +195,7 @@ describe Gitlab::Danger::CommitLinter do ...@@ -183,7 +195,7 @@ describe Gitlab::Danger::CommitLinter do
end end
end end
context 'when subject ands with a period' do context 'when subject ends with a period' do
let(:commit_message) { 'A B C.' } let(:commit_message) { 'A B C.' }
it 'adds a problem' do it 'adds a problem' do
......
...@@ -36,6 +36,46 @@ describe Spammable do ...@@ -36,6 +36,46 @@ describe Spammable do
end end
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 describe '#submittable_as_spam_by?' do
let(:admin) { build(:admin) } let(:admin) { build(:admin) }
let(:user) { build(:user) } let(:user) { build(:user) }
......
...@@ -152,4 +152,52 @@ describe 'GraphQL' do ...@@ -152,4 +152,52 @@ describe 'GraphQL' do
end end
end 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 end
...@@ -60,7 +60,7 @@ Capybara.register_driver :chrome do |app| ...@@ -60,7 +60,7 @@ Capybara.register_driver :chrome do |app|
) )
end end
Capybara.server = :webrick Capybara.server = :puma
Capybara.javascript_driver = :chrome Capybara.javascript_driver = :chrome
Capybara.default_max_wait_time = timeout Capybara.default_max_wait_time = timeout
Capybara.ignore_hidden_elements = true Capybara.ignore_hidden_elements = true
......
...@@ -740,15 +740,15 @@ ...@@ -740,15 +740,15 @@
dependencies: dependencies:
vue-eslint-parser "^7.0.0" vue-eslint-parser "^7.0.0"
"@gitlab/svgs@^1.99.0": "@gitlab/svgs@^1.101.0":
version "1.99.0" version "1.101.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.99.0.tgz#bcf971c3a14920218e86da71ca115244b23a4a3f" resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.101.0.tgz#5440ada1774758e42fd67212d495a29523dd0d5e"
integrity sha512-bxYFxnmuoWPBU9isL3/CYFlr+k2YWU47Pq0vfmSmL7uLnb/vYymfZZF5p3erlZ62WGwuT3kp4GnuoZBMfmannA== integrity sha512-GE6wRn0UqA5f0pmX5wL/vTgUnAgZEdTIDam+OTMuMxf5a1jfxc1KlSLudgZbS3O/W79jN4uMkTdZ7X8gEzAChw==
"@gitlab/ui@^9.11.2": "@gitlab/ui@^9.16.0":
version "9.11.2" version "9.16.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.11.2.tgz#ffb58bb10c6a8cd503a622946ed78512e9c18c6d" resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.16.0.tgz#ac66b55cffdfd9ac2df2abddb11445edc3494732"
integrity sha512-9acsjQ9+hSaAIGpiARNF4XfQUhulWiausns9JUTrN9XEQpa1o/EsDYqwP0HfSOMZ8JhnjSI2NGYVf+LIH5oudg== integrity sha512-9PbFgqNxIAGn1LyIcnlqQuNGAiBT/fqTx8vPdaDQkdScFZksZOBwiIhpxnRk9UFABC3h+0TNeHgVigD7TKGqJw==
dependencies: dependencies:
"@babel/standalone" "^7.0.0" "@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0" "@gitlab/vue-toasted" "^1.3.0"
...@@ -5418,6 +5418,11 @@ has-flag@^3.0.0: ...@@ -5418,6 +5418,11 @@ has-flag@^3.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 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: has-symbols@^1.0.0, has-symbols@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
...@@ -5552,6 +5557,11 @@ html-entities@^1.2.1: ...@@ -5552,6 +5557,11 @@ html-entities@^1.2.1:
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= 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: html-minifier@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-4.0.0.tgz#cca9aad8bce1175e02e17a8c33e46d8988889f56" 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: ...@@ -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" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49"
integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== 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: istanbul-lib-hook@^2.0.7:
version "2.0.7" version "2.0.7"
resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz#c95695f383d4f8f60df1f04252a9550e15b5b133" 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: ...@@ -6312,6 +6327,15 @@ istanbul-lib-report@^2.0.4, istanbul-lib-report@^2.0.8:
make-dir "^2.1.0" make-dir "^2.1.0"
supports-color "^6.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: istanbul-lib-source-maps@^3.0.1, istanbul-lib-source-maps@^3.0.6:
version "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" 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: ...@@ -6330,6 +6354,14 @@ istanbul-reports@^2.1.1, istanbul-reports@^2.2.4:
dependencies: dependencies:
handlebars "^4.1.2" 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: istextorbinary@^2.2.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.2.1.tgz#a5231a08ef6dd22b268d0895084cf8d58b5bec53" 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: ...@@ -10836,6 +10868,13 @@ supports-color@^5.2.0, supports-color@^5.3.0, supports-color@^5.4.0:
dependencies: dependencies:
has-flag "^3.0.0" 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: svg-tags@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment