提交 66d42037 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 a496f41f
......@@ -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
......
......@@ -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
......
......@@ -123,5 +123,6 @@
"YouTrack"
],
"code_blocks": false
}
},
"code-fence-style": false
}
<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"
......
<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>
......@@ -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]
......
......@@ -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
......@@ -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."
......
---
title: Update iOS (Swift) project template logo
merge_request: 25049
author:
type: changed
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
......
......@@ -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.
......
......@@ -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.
......
......@@ -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
......
......@@ -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"
......
此差异已折叠。
......@@ -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.
......@@ -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).
......@@ -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.
......
......@@ -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
......
......@@ -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/'],
......
......@@ -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
......
......@@ -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'),
......
# 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
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
......
......@@ -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)
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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"
>
......
......@@ -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"
......
......@@ -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
......
......@@ -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');
});
});
......
......@@ -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);
......
......@@ -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
......
......@@ -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) }
......
......@@ -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
......@@ -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
......
......@@ -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"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册