Commit 0d587445 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-12-05

# Conflicts:
#	app/assets/javascripts/diffs/store/actions.js
#	app/models/ci/pipeline.rb
#	app/serializers/issue_board_entity.rb
#	app/services/ci/create_pipeline_service.rb
#	app/views/admin/users/index.html.haml
#	doc/user/project/clusters/index.md
#	lib/gitlab/group_hierarchy.rb
#	lib/system_check/rake_task/app_task.rb
#	lib/system_check/rake_task/gitlab_task.rb
#	locale/gitlab.pot
#	spec/fixtures/api/schemas/entities/issue_boards.json
#	spec/models/project_spec.rb

[ci skip]
parents cef19348 cfe48479
...@@ -35,4 +35,10 @@ if (BABEL_ENV === 'karma' || BABEL_ENV === 'coverage') { ...@@ -35,4 +35,10 @@ if (BABEL_ENV === 'karma' || BABEL_ENV === 'coverage') {
plugins.push('babel-plugin-rewire'); plugins.push('babel-plugin-rewire');
} }
// Jest is running in node environment
if (BABEL_ENV === 'jest') {
plugins.push('transform-es2015-modules-commonjs');
plugins.push('dynamic-import-node');
}
module.exports = { presets, plugins }; module.exports = { presets, plugins };
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/config/ /config/
/builds/ /builds/
/coverage/ /coverage/
/coverage-frontend/
/coverage-javascript/ /coverage-javascript/
/node_modules/ /node_modules/
/public/ /public/
......
...@@ -78,5 +78,5 @@ eslint-report.html ...@@ -78,5 +78,5 @@ eslint-report.html
/plugins/* /plugins/*
/.gitlab_pages_secret /.gitlab_pages_secret
package-lock.json package-lock.json
/junit_rspec.xml /junit_*.xml
/junit_karma.xml /coverage-frontend/
...@@ -877,6 +877,32 @@ karma: ...@@ -877,6 +877,32 @@ karma:
reports: reports:
junit: junit_karma.xml junit: junit_karma.xml
jest:
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
<<: *use-pg
dependencies:
- compile-assets
- setup-test-env
script:
- scripts/gitaly-test-spawn
- date
- bundle exec rake karma:fixtures
- date
- yarn jest --ci --coverage
artifacts:
name: coverage-frontend
expire_in: 31d
when: always
paths:
- coverage-frontend/
- junit_jest.xml
reports:
junit: junit_jest.xml
cache:
key: jest
paths:
- tmp/jest/jest/
code_quality: code_quality:
<<: *dedicated-no-docs-no-db-pull-cache-job <<: *dedicated-no-docs-no-db-pull-cache-job
image: docker:stable image: docker:stable
......
...@@ -147,12 +147,26 @@ export const scrollToLineIfNeededParallel = (_, line) => { ...@@ -147,12 +147,26 @@ export const scrollToLineIfNeededParallel = (_, line) => {
} }
}; };
<<<<<<< HEAD
export const loadCollapsedDiff = ({ commit }, file) => export const loadCollapsedDiff = ({ commit }, file) =>
axios.get(file.load_collapsed_diff_url).then(res => { axios.get(file.load_collapsed_diff_url).then(res => {
commit(types.ADD_COLLAPSED_DIFFS, { commit(types.ADD_COLLAPSED_DIFFS, {
file, file,
data: res.data, data: res.data,
=======
export const loadCollapsedDiff = ({ commit, getters }, file) =>
axios
.get(file.load_collapsed_diff_url, {
params: {
commit_id: getters.commitId,
},
})
.then(res => {
commit(types.ADD_COLLAPSED_DIFFS, {
file,
data: res.data,
}); });
>>>>>>> upstream/master
}); });
export const expandAllFiles = ({ commit }) => { export const expandAllFiles = ({ commit }) => {
......
...@@ -130,7 +130,7 @@ export default { ...@@ -130,7 +130,7 @@ export default {
if (file.highlighted_diff_lines) { if (file.highlighted_diff_lines) {
file.highlighted_diff_lines = file.highlighted_diff_lines.map(line => { file.highlighted_diff_lines = file.highlighted_diff_lines.map(line => {
if (lineCheck(line)) { if (!line.discussions.some(({ id }) => discussion.id === id) && lineCheck(line)) {
return { return {
...line, ...line,
discussions: line.discussions.concat(discussion), discussions: line.discussions.concat(discussion),
...@@ -150,11 +150,17 @@ export default { ...@@ -150,11 +150,17 @@ export default {
return { return {
left: { left: {
...line.left, ...line.left,
discussions: left ? line.left.discussions.concat(discussion) : [], discussions:
left && !line.left.discussions.some(({ id }) => id === discussion.id)
? line.left.discussions.concat(discussion)
: (line.left && line.left.discussions) || [],
}, },
right: { right: {
...line.right, ...line.right,
discussions: right && !left ? line.right.discussions.concat(discussion) : [], discussions:
right && !left && !line.right.discussions.some(({ id }) => id === discussion.id)
? line.right.discussions.concat(discussion)
: (line.right && line.right.discussions) || [],
}, },
}; };
} }
......
...@@ -19,3 +19,4 @@ $info: $blue-500; ...@@ -19,3 +19,4 @@ $info: $blue-500;
$warning: $orange-500; $warning: $orange-500;
$danger: $red-500; $danger: $red-500;
$zindex-modal-backdrop: 1040; $zindex-modal-backdrop: 1040;
$nav-divider-margin-y: ($grid-size / 2);
...@@ -24,12 +24,9 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -24,12 +24,9 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def render_diffs def render_diffs
@environment = @merge_request.environments_for(current_user).last @environment = @merge_request.environments_for(current_user).last
notes_grouped_by_path = renderable_notes.group_by { |note| note.position.file_path }
@diffs.diff_files.each do |diff_file| note_positions = renderable_notes.map(&:position).compact
notes = notes_grouped_by_path.fetch(diff_file.file_path, []) @diffs.unfold_diff_files(note_positions)
notes.each { |note| diff_file.unfold_diff_lines(note.position) }
end
@diffs.write_cache @diffs.write_cache
......
...@@ -12,8 +12,11 @@ module Ci ...@@ -12,8 +12,11 @@ module Ci
include AtomicInternalId include AtomicInternalId
include EnumWithNil include EnumWithNil
<<<<<<< HEAD
prepend ::EE::Ci::Pipeline prepend ::EE::Ci::Pipeline
=======
>>>>>>> upstream/master
belongs_to :project, inverse_of: :all_pipelines belongs_to :project, inverse_of: :all_pipelines
belongs_to :user belongs_to :user
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
......
...@@ -114,7 +114,8 @@ module Ci ...@@ -114,7 +114,8 @@ module Ci
cached_attr_reader :version, :revision, :platform, :architecture, :ip_address, :contacted_at cached_attr_reader :version, :revision, :platform, :architecture, :ip_address, :contacted_at
chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout,
error_message: 'Maximum job timeout has a value which could not be accepted'
validates :maximum_timeout, allow_nil: true, validates :maximum_timeout, allow_nil: true,
numericality: { greater_than_or_equal_to: 600, numericality: { greater_than_or_equal_to: 600,
......
...@@ -24,7 +24,7 @@ module ChronicDurationAttribute ...@@ -24,7 +24,7 @@ module ChronicDurationAttribute
end end
end end
validates virtual_attribute, allow_nil: true, duration: true validates virtual_attribute, allow_nil: true, duration: { message: parameters[:error_message] }
end end
alias_method :chronic_duration_attr, :chronic_duration_attr_writer alias_method :chronic_duration_attr, :chronic_duration_attr_writer
......
...@@ -404,7 +404,8 @@ class Project < ActiveRecord::Base ...@@ -404,7 +404,8 @@ class Project < ActiveRecord::Base
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 } enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600 chronic_duration_attr :build_timeout_human_readable, :build_timeout,
default: 3600, error_message: 'Maximum job timeout has a value which could not be accepted'
validates :build_timeout, allow_nil: true, validates :build_timeout, allow_nil: true,
numericality: { greater_than_or_equal_to: 10.minutes, numericality: { greater_than_or_equal_to: 10.minutes,
......
...@@ -2,7 +2,10 @@ ...@@ -2,7 +2,10 @@
class IssueBoardEntity < Grape::Entity class IssueBoardEntity < Grape::Entity
include RequestAwareEntity include RequestAwareEntity
<<<<<<< HEAD
prepend ::EE::IssueBoardEntity prepend ::EE::IssueBoardEntity
=======
>>>>>>> upstream/master
expose :id expose :id
expose :iid expose :iid
......
...@@ -17,7 +17,11 @@ module Ci ...@@ -17,7 +17,11 @@ module Ci
Gitlab::Ci::Pipeline::Chain::Create, Gitlab::Ci::Pipeline::Chain::Create,
EE::Gitlab::Ci::Pipeline::Chain::Limit::Activity].freeze EE::Gitlab::Ci::Pipeline::Chain::Limit::Activity].freeze
<<<<<<< HEAD
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, mirror_update: false, &block) def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, mirror_update: false, &block)
=======
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, &block)
>>>>>>> upstream/master
@pipeline = Ci::Pipeline.new @pipeline = Ci::Pipeline.new
command = Gitlab::Ci::Pipeline::Chain::Command.new( command = Gitlab::Ci::Pipeline::Chain::Command.new(
......
...@@ -14,6 +14,10 @@ class DurationValidator < ActiveModel::EachValidator ...@@ -14,6 +14,10 @@ class DurationValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
ChronicDuration.parse(value) ChronicDuration.parse(value)
rescue ChronicDuration::DurationParseError rescue ChronicDuration::DurationParseError
if options[:message]
record.errors.add(:base, options[:message])
else
record.errors.add(attribute, "is not a correct duration") record.errors.add(attribute, "is not a correct duration")
end end
end
end end
...@@ -23,7 +23,10 @@ ...@@ -23,7 +23,10 @@
- users_sort_options_hash.each do |value, title| - users_sort_options_hash.each do |value, title|
= link_to admin_users_path(sort: value, filter: params[:filter], search_query: params[:search_query]) do = link_to admin_users_path(sort: value, filter: params[:filter], search_query: params[:search_query]) do
= title = title
<<<<<<< HEAD
= link_to 'Send email to users', admin_email_path, class: 'btn' = link_to 'Send email to users', admin_email_path, class: 'btn'
=======
>>>>>>> upstream/master
= link_to 'New user', new_admin_user_path, class: 'btn btn-success btn-search' = link_to 'New user', new_admin_user_path, class: 'btn btn-success btn-search'
.top-area.scrolling-tabs-container.inner-page-scroll-tabs .top-area.scrolling-tabs-container.inner-page-scroll-tabs
......
- page_title import_in_progress_title - page_title import_in_progress_title
- @no_container = true
- @content_class = "limit-container-width" unless fluid_layout
.save-project-loader .save-project-loader
.center .center
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
= f.label :build_timeout_human_readable, _('Timeout'), class: 'label-bold' = f.label :build_timeout_human_readable, _('Timeout'), class: 'label-bold'
= f.text_field :build_timeout_human_readable, class: 'form-control' = f.text_field :build_timeout_human_readable, class: 'form-control'
%p.form-text.text-muted %p.form-text.text-muted
= _("Per job. If a job passes this threshold, it will be marked as failed") = _('If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like "1 hour". Values without specification represent seconds.')
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank' = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank'
%hr %hr
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
= _('Maximum job timeout') = _('Maximum job timeout')
.col-sm-10 .col-sm-10
= f.text_field :maximum_timeout_human_readable, class: 'form-control' = f.text_field :maximum_timeout_human_readable, class: 'form-control'
.form-text.text-muted= _('This timeout will take precedence when lower than Project-defined timeout') .form-text.text-muted= _('This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like "1 hour". Values without specification represent seconds.')
.form-group.row .form-group.row
= label_tag :tag_list, class: 'col-form-label col-sm-2' do = label_tag :tag_list, class: 'col-form-label col-sm-2' do
= _('Tags') = _('Tags')
......
---
title: Use read_repository scope on read-only files API
merge_request: 23534
author:
type: fixed
---
title: Fixed diff files expanding not loading commit content
merge_request:
author:
type: fixed
---
title: Improve help and validation sections of maximum build timeout inputs
merge_request: 23586
author:
type: fixed
---
title: Change container width for project import
merge_request: 23318
author: George Tsiolis
type: fixed
---
title: Fixed duplicate discussions getting added to diff lines
merge_request:
author:
type: fixed
---
title: Avoid 500's when serializing legacy diff notes
merge_request: 23544
author:
type: fixed
---
title: Adjust divider margin to comply with design specs
merge_request: 23548
author:
type: changed
/* eslint-disable filenames/match-regex */
const reporters = ['default'];
if (process.env.CI) {
reporters.push([
'jest-junit',
{
output: './junit_jest.xml',
},
]);
}
// eslint-disable-next-line import/no-commonjs
module.exports = {
testMatch: ['<rootDir>/spec/frontend/**/*_spec.js'],
moduleNameMapper: {
'^~(.*)$': '<rootDir>/app/assets/javascripts$1',
},
collectCoverageFrom: ['<rootDir>/app/assets/javascripts/**/*.{js,vue}'],
coverageDirectory: '<rootDir>/coverage-frontend/',
coverageReporters: ['json', 'lcov', 'text-summary', 'clover'],
cacheDirectory: '<rootDir>/tmp/cache/jest',
modulePathIgnorePatterns: ['<rootDir>/.yarn-cache/'],
reporters,
rootDir: '..', // necessary because this file is in the config/ subdirectory
};
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'yaml' require 'yaml'
NO_CHANGELOG_LABELS = %w[backstage Documentation QA test].freeze NO_CHANGELOG_LABELS = %w[backstage ci-build Documentation meta QA test].freeze
SEE_DOC = "See [the documentation](https://docs.gitlab.com/ce/development/changelog.html).".freeze SEE_DOC = "See [the documentation](https://docs.gitlab.com/ce/development/changelog.html).".freeze
CREATE_CHANGELOG_MESSAGE = <<~MSG.freeze CREATE_CHANGELOG_MESSAGE = <<~MSG.freeze
You can create one with: You can create one with:
......
...@@ -4,6 +4,16 @@ ...@@ -4,6 +4,16 @@
**Create, read, update and delete repository files using this API** **Create, read, update and delete repository files using this API**
The different scopes available using [personal access tokens](../user/profile/personal_access_tokens.md) are depicted
in the following table.
| Scope | Description |
| ----- | ----------- |
| `read_repository` | Allows read-access to the repository files. |
| `api` | Allows read-write access to the repository files. |
> `read_repository` scope was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23534) in GitLab 11.6.
## Get file from repository ## Get file from repository
Allows you to receive information about file in repository like name, size, Allows you to receive information about file in repository like name, size,
......
...@@ -57,12 +57,14 @@ are very appreciative of the work done by translators and proofreaders! ...@@ -57,12 +57,14 @@ are very appreciative of the work done by translators and proofreaders!
- Chang-Ho Cha - [GitLab](https://gitlab.com/changho-cha), [Crowdin](https://crowdin.com/profile/zzazang) - Chang-Ho Cha - [GitLab](https://gitlab.com/changho-cha), [Crowdin](https://crowdin.com/profile/zzazang)
- Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve) - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
- Ji Hun Oh - [GitLab](https://gitlab.com/Baw-Appie), [Crowdin](https://crowdin.com/profile/BawAppie) - Ji Hun Oh - [GitLab](https://gitlab.com/Baw-Appie), [Crowdin](https://crowdin.com/profile/BawAppie)
- Jeongwhan Choi - [GitLab](https://gitlab.com/jeongwhanchoi), [Crowdin](https://crowdin.com/profile/jeongwhanchoi)
- Mongolian - Mongolian
- Proofreaders needed. - Proofreaders needed.
- Norwegian Bokmal - Norwegian Bokmal
- Proofreaders needed. - Proofreaders needed.
- Polish - Polish
- Filip Mech - [GitLab](https://gitlab.com/mehenz), [Crowdin](https://crowdin.com/profile/mehenz) - Filip Mech - [GitLab](https://gitlab.com/mehenz), [Crowdin](https://crowdin.com/profile/mehenz)
- Maksymilian Roman - [GitLab](https://gitlab.com/villaincandle), [Crowdin](https://crowdin.com/profile/villaincandle)
- Portuguese - Portuguese
- Proofreaders needed. - Proofreaders needed.
- Portuguese, Brazilian - Portuguese, Brazilian
......
...@@ -6,9 +6,15 @@ Tests relevant for frontend development can be found at two places: ...@@ -6,9 +6,15 @@ Tests relevant for frontend development can be found at two places:
- [frontend unit tests](#frontend-unit-tests) - [frontend unit tests](#frontend-unit-tests)
- [frontend component tests](#frontend-component-tests) - [frontend component tests](#frontend-component-tests)
- [frontend integration tests](#frontend-integration-tests) - [frontend integration tests](#frontend-integration-tests)
- `spec/frontend/` which are run by Jest and contain
- [frontend unit tests](#frontend-unit-tests)
- [frontend component tests](#frontend-component-tests)
- [frontend integration tests](#frontend-integration-tests)
- `spec/features/` which are run by RSpec and contain - `spec/features/` which are run by RSpec and contain
- [feature tests](#feature-tests) - [feature tests](#feature-tests)
All tests in `spec/javascripts/` will eventually be migrated to `spec/frontend/` (see also [#53757]).
In addition there were feature tests in `features/` run by Spinach in the past. In addition there were feature tests in `features/` run by Spinach in the past.
These have been removed from our codebase in May 2018 ([#23036](https://gitlab.com/gitlab-org/gitlab-ce/issues/23036)). These have been removed from our codebase in May 2018 ([#23036](https://gitlab.com/gitlab-org/gitlab-ce/issues/23036)).
...@@ -17,6 +23,8 @@ See also: ...@@ -17,6 +23,8 @@ See also:
- [old testing guide](../../testing_guide/frontend_testing.html) - [old testing guide](../../testing_guide/frontend_testing.html)
- [notes on testing Vue components](../../fe_guide/vue.html#testing-vue-components) - [notes on testing Vue components](../../fe_guide/vue.html#testing-vue-components)
[#53757]: https://gitlab.com/gitlab-org/gitlab-ce/issues/53757
## Frontend unit tests ## Frontend unit tests
Unit tests are on the lowest abstraction level and typically test functionality that is not directly perceivable by a user. Unit tests are on the lowest abstraction level and typically test functionality that is not directly perceivable by a user.
......
...@@ -7,9 +7,10 @@ GitLab provides official Docker images to allowing you to easily take advantage ...@@ -7,9 +7,10 @@ GitLab provides official Docker images to allowing you to easily take advantage
## Omnibus GitLab based images ## Omnibus GitLab based images
GitLab maintains a set of [official Docker images](https://hub.docker.com/r/gitlab) based on our [Omnibus GitLab package](https://docs.gitlab.com/omnibus/README.html). These images include: GitLab maintains a set of [official Docker images](https://hub.docker.com/r/gitlab) based on our [Omnibus GitLab package](https://docs.gitlab.com/omnibus/README.html). These images include:
* [GitLab Community Edition](https://hub.docker.com/r/gitlab/gitlab-ce/)
* [GitLab Enterprise Edition](https://hub.docker.com/r/gitlab/gitlab-ee/) - [GitLab Community Edition](https://hub.docker.com/r/gitlab/gitlab-ce/).
* [GitLab Runner](https://hub.docker.com/r/gitlab/gitlab-runner/) - [GitLab Enterprise Edition](https://hub.docker.com/r/gitlab/gitlab-ee/).
- [GitLab Runner](https://hub.docker.com/r/gitlab/gitlab-runner/).
A [complete usage guide](https://docs.gitlab.com/omnibus/docker/) to these images is available, as well as the [Dockerfile used for building the images](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker). A [complete usage guide](https://docs.gitlab.com/omnibus/docker/) to these images is available, as well as the [Dockerfile used for building the images](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker).
......
...@@ -6,7 +6,7 @@ password to login, they'll be prompted for a code generated by an application on ...@@ -6,7 +6,7 @@ password to login, they'll be prompted for a code generated by an application on
their phone. their phone.
You can read more about it here: You can read more about it here:
[Two-factor Authentication (2FA)](../profile/two_factor_authentication.md) [Two-factor Authentication (2FA)](../user/profile/account/two_factor_authentication.md)
## Enforcing 2FA for all users ## Enforcing 2FA for all users
......
...@@ -461,6 +461,7 @@ builds is that they must have a matching ...@@ -461,6 +461,7 @@ builds is that they must have a matching
your build has no `environment:name` set, it will not be passed the Kubernetes your build has no `environment:name` set, it will not be passed the Kubernetes
credentials. credentials.
<<<<<<< HEAD
## Monitoring your Kubernetes cluster **[ULTIMATE]** ## Monitoring your Kubernetes cluster **[ULTIMATE]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4701) in [GitLab Ultimate][ee] 10.6. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4701) in [GitLab Ultimate][ee] 10.6.
...@@ -469,6 +470,8 @@ When [Prometheus is deployed](#installing-applications), GitLab will automatical ...@@ -469,6 +470,8 @@ When [Prometheus is deployed](#installing-applications), GitLab will automatical
![Cluster Monitoring](img/k8s_cluster_monitoring.png) ![Cluster Monitoring](img/k8s_cluster_monitoring.png)
=======
>>>>>>> upstream/master
## Enabling or disabling the Kubernetes cluster integration ## Enabling or disabling the Kubernetes cluster integration
After you have successfully added your cluster information, you can enable the After you have successfully added your cluster information, you can enable the
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module API module API
class Files < Grape::API class Files < Grape::API
include APIGuard
FILE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(file_path: API::NO_SLASH_URL_PART_REGEX) FILE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(file_path: API::NO_SLASH_URL_PART_REGEX)
# Prevents returning plain/text responses for files with .txt extension # Prevents returning plain/text responses for files with .txt extension
...@@ -79,6 +81,8 @@ module API ...@@ -79,6 +81,8 @@ module API
requires :id, type: String, desc: 'The project ID' requires :id, type: String, desc: 'The project ID'
end end
resource :projects, requirements: FILE_ENDPOINT_REQUIREMENTS do resource :projects, requirements: FILE_ENDPOINT_REQUIREMENTS do
allow_access_with_scope :read_repository, if: -> (request) { request.get? || request.head? }
desc 'Get raw file metadata from repository' desc 'Get raw file metadata from repository'
params do params do
requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
......
...@@ -34,6 +34,16 @@ module Gitlab ...@@ -34,6 +34,16 @@ module Gitlab
@diff_files ||= diffs.decorate! { |diff| decorate_diff!(diff) } @diff_files ||= diffs.decorate! { |diff| decorate_diff!(diff) }
end end
# This mutates `diff_files` lines.
def unfold_diff_files(positions)
positions_grouped_by_path = positions.group_by { |position| position.file_path }
diff_files.each do |diff_file|
positions = positions_grouped_by_path.fetch(diff_file.file_path, [])
positions.each { |position| diff_file.unfold_diff_lines(position) }
end
end
def diff_file_with_old_path(old_path) def diff_file_with_old_path(old_path)
diff_files.find { |diff_file| diff_file.old_path == old_path } diff_files.find { |diff_file| diff_file.old_path == old_path }
end end
......
...@@ -10,6 +10,10 @@ module Gitlab ...@@ -10,6 +10,10 @@ module Gitlab
diff_options: diff_options, diff_options: diff_options,
diff_refs: diff_refs) diff_refs: diff_refs)
end end
def unfold_diff_lines(positions)
# no-op
end
end end
end end
end end
......
...@@ -36,12 +36,15 @@ module Gitlab ...@@ -36,12 +36,15 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def ancestors(upto: nil, hierarchy_order: nil) def ancestors(upto: nil, hierarchy_order: nil)
base_and_ancestors(upto: upto, hierarchy_order: hierarchy_order).where.not(id: ancestors_base.select(:id)) base_and_ancestors(upto: upto, hierarchy_order: hierarchy_order).where.not(id: ancestors_base.select(:id))
<<<<<<< HEAD
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def roots def roots
base_and_ancestors.where(namespaces: { parent_id: nil }) base_and_ancestors.where(namespaces: { parent_id: nil })
=======
>>>>>>> upstream/master
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -127,10 +130,17 @@ module Gitlab ...@@ -127,10 +130,17 @@ module Gitlab
def base_and_ancestors_cte(stop_id = nil, hierarchy_order = nil) def base_and_ancestors_cte(stop_id = nil, hierarchy_order = nil)
cte = SQL::RecursiveCTE.new(:base_and_ancestors) cte = SQL::RecursiveCTE.new(:base_and_ancestors)
depth_column = :depth depth_column = :depth
<<<<<<< HEAD
base_query = ancestors_base.except(:order) base_query = ancestors_base.except(:order)
base_query = base_query.select("1 as #{depth_column}", groups_table[Arel.star]) if hierarchy_order base_query = base_query.select("1 as #{depth_column}", groups_table[Arel.star]) if hierarchy_order
=======
base_query = ancestors_base.except(:order)
base_query = base_query.select("1 as #{depth_column}", groups_table[Arel.star]) if hierarchy_order
>>>>>>> upstream/master
cte << base_query cte << base_query
# Recursively get all the ancestors of the base set. # Recursively get all the ancestors of the base set.
......
...@@ -57,7 +57,11 @@ module Gitlab ...@@ -57,7 +57,11 @@ module Gitlab
environments: count(::Environment), environments: count(::Environment),
clusters: count(::Clusters::Cluster), clusters: count(::Clusters::Cluster),
clusters_enabled: count(::Clusters::Cluster.enabled), clusters_enabled: count(::Clusters::Cluster.enabled),
project_clusters_enabled: count(::Clusters::Cluster.enabled.project_type),
group_clusters_enabled: count(::Clusters::Cluster.enabled.group_type),
clusters_disabled: count(::Clusters::Cluster.disabled), clusters_disabled: count(::Clusters::Cluster.disabled),
project_clusters_disabled: count(::Clusters::Cluster.disabled.project_type),
group_clusters_disabled: count(::Clusters::Cluster.disabled.group_type),
clusters_platforms_gke: count(::Clusters::Cluster.gcp_installed.enabled), clusters_platforms_gke: count(::Clusters::Cluster.gcp_installed.enabled),
clusters_platforms_user: count(::Clusters::Cluster.user_provided.enabled), clusters_platforms_user: count(::Clusters::Cluster.user_provided.enabled),
clusters_applications_helm: count(::Clusters::Applications::Helm.installed), clusters_applications_helm: count(::Clusters::Applications::Helm.installed),
......
...@@ -36,5 +36,8 @@ module SystemCheck ...@@ -36,5 +36,8 @@ module SystemCheck
end end
end end
end end
<<<<<<< HEAD
SystemCheck::RakeTask::AppTask.prepend(EE::SystemCheck::RakeTask::AppTask) SystemCheck::RakeTask::AppTask.prepend(EE::SystemCheck::RakeTask::AppTask)
=======
>>>>>>> upstream/master
...@@ -31,5 +31,8 @@ module SystemCheck ...@@ -31,5 +31,8 @@ module SystemCheck
end end
end end
end end
<<<<<<< HEAD
SystemCheck::RakeTask::GitlabTask.prepend(EE::SystemCheck::RakeTask::GitlabTask) SystemCheck::RakeTask::GitlabTask.prepend(EE::SystemCheck::RakeTask::GitlabTask)
=======
>>>>>>> upstream/master
...@@ -4479,10 +4479,14 @@ msgstr "" ...@@ -4479,10 +4479,14 @@ msgstr ""
msgid "Identities" msgid "Identities"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Identity provider single sign on URL" msgid "Identity provider single sign on URL"
msgstr "" msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
=======
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
>>>>>>> upstream/master
msgstr "" msgstr ""
msgid "If disabled, the access level will depend on the user's permissions in the project." msgid "If disabled, the access level will depend on the user's permissions in the project."
...@@ -6061,9 +6065,6 @@ msgstr "" ...@@ -6061,9 +6065,6 @@ msgstr ""
msgid "People without permission will never get a notification and won't be able to comment." msgid "People without permission will never get a notification and won't be able to comment."
msgstr "" msgstr ""
msgid "Per job. If a job passes this threshold, it will be marked as failed"
msgstr ""
msgid "Perform advanced options such as changing path, transferring, or removing the group." msgid "Perform advanced options such as changing path, transferring, or removing the group."
msgstr "" msgstr ""
...@@ -8589,7 +8590,7 @@ msgstr "" ...@@ -8589,7 +8590,7 @@ msgstr ""
msgid "This source diff could not be displayed because it is too large." msgid "This source diff could not be displayed because it is too large."
msgstr "" msgstr ""
msgid "This timeout will take precedence when lower than Project-defined timeout" msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr "" msgstr ""
msgid "This user has no identities" msgid "This user has no identities"
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
"eslint": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .", "eslint": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .",
"eslint-fix": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .", "eslint-fix": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .",
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html --no-inline-config .", "eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html --no-inline-config .",
"jest": "BABEL_ENV=jest jest --config=config/jest.config.js",
"karma": "BABEL_ENV=${BABEL_ENV:=karma} karma start --single-run true config/karma.config.js", "karma": "BABEL_ENV=${BABEL_ENV:=karma} karma start --single-run true config/karma.config.js",
"karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js", "karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "BABEL_ENV=karma karma start config/karma.config.js", "karma-start": "BABEL_ENV=karma karma start config/karma.config.js",
...@@ -120,17 +121,23 @@ ...@@ -120,17 +121,23 @@
"@gitlab/eslint-config": "^1.2.0", "@gitlab/eslint-config": "^1.2.0",
"@vue/test-utils": "^1.0.0-beta.25", "@vue/test-utils": "^1.0.0-beta.25",
"axios-mock-adapter": "^1.15.0", "axios-mock-adapter": "^1.15.0",
"babel-core": "^7.0.0-bridge",
"babel-jest": "^23.6.0",
"babel-plugin-dynamic-import-node": "^2.2.0",
"babel-plugin-istanbul": "^5.1.0", "babel-plugin-istanbul": "^5.1.0",
"babel-plugin-rewire": "^1.2.0", "babel-plugin-rewire": "^1.2.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"babel-template": "^6.26.0", "babel-template": "^6.26.0",
"babel-types": "^6.26.0", "babel-types": "^6.26.0",
"chalk": "^2.4.1", "chalk": "^2.4.1",
"commander": "^2.18.0", "commander": "^2.18.0",
"eslint": "~5.6.0", "eslint": "~5.6.0",
"eslint-import-resolver-jest": "^2.1.1",
"eslint-import-resolver-webpack": "^0.10.1", "eslint-import-resolver-webpack": "^0.10.1",
"eslint-plugin-html": "4.0.5", "eslint-plugin-html": "4.0.5",
"eslint-plugin-import": "^2.14.0", "eslint-plugin-import": "^2.14.0",
"eslint-plugin-jasmine": "^2.10.1", "eslint-plugin-jasmine": "^2.10.1",
"eslint-plugin-jest": "^22.1.0",
"gettext-extractor": "^3.3.2", "gettext-extractor": "^3.3.2",
"gettext-extractor-vue": "^4.0.1", "gettext-extractor-vue": "^4.0.1",
"graphql-tag": "^2.10.0", "graphql-tag": "^2.10.0",
...@@ -138,6 +145,8 @@ ...@@ -138,6 +145,8 @@
"jasmine-core": "^2.9.0", "jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3", "jasmine-diff": "^0.1.3",
"jasmine-jquery": "^2.1.1", "jasmine-jquery": "^2.1.1",
"jest": "^23.6.0",
"jest-junit": "^5.2.0",
"karma": "^3.0.0", "karma": "^3.0.0",
"karma-chrome-launcher": "^2.2.0", "karma-chrome-launcher": "^2.2.0",
"karma-coverage-istanbul-reporter": "^2.0.4", "karma-coverage-istanbul-reporter": "^2.0.4",
......
...@@ -36,6 +36,18 @@ describe Projects::MergeRequests::DiffsController do ...@@ -36,6 +36,18 @@ describe Projects::MergeRequests::DiffsController do
end end
end end
context 'when note has no position' do
before do
create(:legacy_diff_note_on_merge_request, project: project, noteable: merge_request, position: nil)
end
it 'serializes merge request diff collection' do
expect_any_instance_of(DiffsSerializer).to receive(:represent).with(an_instance_of(Gitlab::Diff::FileCollection::MergeRequestDiff), an_instance_of(Hash))
go
end
end
context 'with forked projects with submodules' do context 'with forked projects with submodules' do
render_views render_views
......
...@@ -2,16 +2,24 @@ ...@@ -2,16 +2,24 @@
"type": "object", "type": "object",
"required" : [ "required" : [
"issues", "issues",
<<<<<<< HEAD
"size", "size",
"total_weight" "total_weight"
=======
"size"
>>>>>>> upstream/master
], ],
"properties" : { "properties" : {
"issues": { "issues": {
"type": "array", "type": "array",
"items": { "$ref": "issue_board.json" } "items": { "$ref": "issue_board.json" }
}, },
<<<<<<< HEAD
"size": { "type": "integer" }, "size": { "type": "integer" },
"total_weight": { "type": "integer" } "total_weight": { "type": "integer" }
=======
"size": { "type": "integer" }
>>>>>>> upstream/master
}, },
"additionalProperties": false "additionalProperties": false
} }
---
env:
jest/globals: true
plugins:
- jest
settings:
import/resolver:
jest:
jestConfigFile: "config/jest.config.js"
it('does nothing', () => {});
...@@ -382,24 +382,47 @@ describe('DiffsStoreActions', () => { ...@@ -382,24 +382,47 @@ describe('DiffsStoreActions', () => {
const file = { hash: 123, load_collapsed_diff_url: '/load/collapsed/diff/url' }; const file = { hash: 123, load_collapsed_diff_url: '/load/collapsed/diff/url' };
const data = { hash: 123, parallelDiffLines: [{ lineCode: 1 }] }; const data = { hash: 123, parallelDiffLines: [{ lineCode: 1 }] };
const mock = new MockAdapter(axios); const mock = new MockAdapter(axios);
const commit = jasmine.createSpy('commit');
mock.onGet(file.loadCollapsedDiffUrl).reply(200, data); mock.onGet(file.loadCollapsedDiffUrl).reply(200, data);
testAction( loadCollapsedDiff({ commit, getters: { commitId: null } }, file)
loadCollapsedDiff, .then(() => {
file, expect(commit).toHaveBeenCalledWith(types.ADD_COLLAPSED_DIFFS, { file, data });
{},
[
{
type: types.ADD_COLLAPSED_DIFFS,
payload: { file, data },
},
],
[],
() => {
mock.restore(); mock.restore();
done(); done();
}, })
); .catch(done.fail);
});
it('should fetch data without commit ID', () => {
const file = { load_collapsed_diff_url: '/load/collapsed/diff/url' };
const getters = {
commitId: null,
};
spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
loadCollapsedDiff({ commit() {}, getters }, file);
expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
params: { commit_id: null },
});
});
it('should fetch data with commit ID', () => {
const file = { load_collapsed_diff_url: '/load/collapsed/diff/url' };
const getters = {
commitId: '123',
};
spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
loadCollapsedDiff({ commit() {}, getters }, file);
expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
params: { commit_id: '123' },
});
}); });
}); });
......
...@@ -199,6 +199,84 @@ describe('DiffsStoreMutations', () => { ...@@ -199,6 +199,84 @@ describe('DiffsStoreMutations', () => {
expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1); expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1);
}); });
it('should not duplicate discussions on line', () => {
const diffPosition = {
base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910',
head_sha: 'b921914f9a834ac47e6fd9420f78db0f83559130',
new_line: null,
new_path: '500-lines-4.txt',
old_line: 5,
old_path: '500-lines-4.txt',
start_sha: 'ed13df29948c41ba367caa757ab3ec4892509910',
};
const state = {
latestDiff: true,
diffFiles: [
{
file_hash: 'ABC',
parallel_diff_lines: [
{
left: {
line_code: 'ABC_1',
discussions: [],
},
right: {
line_code: 'ABC_1',
discussions: [],
},
},
],
highlighted_diff_lines: [
{
line_code: 'ABC_1',
discussions: [],
},
],
},
],
};
const discussion = {
id: 1,
line_code: 'ABC_1',
diff_discussion: true,
resolvable: true,
original_position: diffPosition,
position: diffPosition,
diff_file: {
file_hash: state.diffFiles[0].file_hash,
},
};
const diffPositionByLineCode = {
ABC_1: diffPosition,
};
mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, {
discussion,
diffPositionByLineCode,
});
expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions.length).toEqual(1);
expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].id).toEqual(1);
expect(state.diffFiles[0].parallel_diff_lines[0].right.discussions).toEqual([]);
expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toEqual(1);
expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1);
mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, {
discussion,
diffPositionByLineCode,
});
expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions.length).toEqual(1);
expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].id).toEqual(1);
expect(state.diffFiles[0].parallel_diff_lines[0].right.discussions).toEqual([]);
expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toEqual(1);
expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1);
});
it('should add legacy discussions to the given line', () => { it('should add legacy discussions to the given line', () => {
const diffPosition = { const diffPosition = {
base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910', base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910',
......
...@@ -12,4 +12,8 @@ describe Gitlab::Diff::FileCollection::Commit do ...@@ -12,4 +12,8 @@ describe Gitlab::Diff::FileCollection::Commit do
let(:diffable) { project.commit } let(:diffable) { project.commit }
let(:stub_path) { 'bar/branch-test.txt' } let(:stub_path) { 'bar/branch-test.txt' }
end end
it_behaves_like 'unfoldable diff' do
let(:diffable) { project.commit }
end
end end
...@@ -2,8 +2,10 @@ require 'spec_helper' ...@@ -2,8 +2,10 @@ require 'spec_helper'
describe Gitlab::Diff::FileCollection::MergeRequestDiff do describe Gitlab::Diff::FileCollection::MergeRequestDiff do
let(:merge_request) { create(:merge_request) } let(:merge_request) { create(:merge_request) }
let(:diff_files) { described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files } let(:subject) { described_class.new(merge_request.merge_request_diff, diff_options: nil) }
let(:diff_files) { subject.diff_files }
describe '#diff_files' do
it 'does not highlight binary files' do it 'does not highlight binary files' do
allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(false) allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(false)
...@@ -19,6 +21,11 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do ...@@ -19,6 +21,11 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do
diff_files diff_files
end end
end
it_behaves_like 'unfoldable diff' do
let(:diffable) { merge_request.merge_request_diff }
end
it 'it uses a different cache key if diff line keys change' do it 'it uses a different cache key if diff line keys change' do
mr_diff = described_class.new(merge_request.merge_request_diff, diff_options: nil) mr_diff = described_class.new(merge_request.merge_request_diff, diff_options: nil)
......
...@@ -18,6 +18,9 @@ describe Gitlab::UsageData do ...@@ -18,6 +18,9 @@ describe Gitlab::UsageData do
gcp_cluster = create(:cluster, :provided_by_gcp) gcp_cluster = create(:cluster, :provided_by_gcp)
create(:cluster, :provided_by_user) create(:cluster, :provided_by_user)
create(:cluster, :provided_by_user, :disabled) create(:cluster, :provided_by_user, :disabled)
create(:cluster, :group)
create(:cluster, :group, :disabled)
create(:cluster, :group, :disabled)
create(:clusters_applications_helm, :installed, cluster: gcp_cluster) create(:clusters_applications_helm, :installed, cluster: gcp_cluster)
create(:clusters_applications_ingress, :installed, cluster: gcp_cluster) create(:clusters_applications_ingress, :installed, cluster: gcp_cluster)
create(:clusters_applications_cert_managers, :installed, cluster: gcp_cluster) create(:clusters_applications_cert_managers, :installed, cluster: gcp_cluster)
...@@ -78,7 +81,11 @@ describe Gitlab::UsageData do ...@@ -78,7 +81,11 @@ describe Gitlab::UsageData do
environments environments
clusters clusters
clusters_enabled clusters_enabled
project_clusters_enabled
group_clusters_enabled
clusters_disabled clusters_disabled
project_clusters_disabled
group_clusters_disabled
clusters_platforms_gke clusters_platforms_gke
clusters_platforms_user clusters_platforms_user
clusters_applications_helm clusters_applications_helm
...@@ -128,8 +135,13 @@ describe Gitlab::UsageData do ...@@ -128,8 +135,13 @@ describe Gitlab::UsageData do
expect(count_data[:projects_slack_notifications_active]).to eq(2) expect(count_data[:projects_slack_notifications_active]).to eq(2)
expect(count_data[:projects_slack_slash_active]).to eq(1) expect(count_data[:projects_slack_slash_active]).to eq(1)
expect(count_data[:clusters_enabled]).to eq(6) expect(count_data[:clusters_enabled]).to eq(7)
expect(count_data[:clusters_disabled]).to eq(1) expect(count_data[:project_clusters_enabled]).to eq(6)
expect(count_data[:group_clusters_enabled]).to eq(1)
expect(count_data[:clusters_disabled]).to eq(3)
expect(count_data[:project_clusters_disabled]).to eq(1)
expect(count_data[:group_clusters_disabled]).to eq(2)
expect(count_data[:group_clusters_enabled]).to eq(1)
expect(count_data[:clusters_platforms_gke]).to eq(1) expect(count_data[:clusters_platforms_gke]).to eq(1)
expect(count_data[:clusters_platforms_user]).to eq(1) expect(count_data[:clusters_platforms_user]).to eq(1)
expect(count_data[:clusters_applications_helm]).to eq(1) expect(count_data[:clusters_applications_helm]).to eq(1)
......
...@@ -54,7 +54,8 @@ shared_examples 'ChronicDurationAttribute writer' do ...@@ -54,7 +54,8 @@ shared_examples 'ChronicDurationAttribute writer' do
subject.send("#{virtual_field}=", '-10m') subject.send("#{virtual_field}=", '-10m')
expect(subject.valid?).to be_falsey expect(subject.valid?).to be_falsey
expect(subject.errors&.messages).to include(virtual_field => ['is not a correct duration']) expect(subject.errors&.messages)
.to include(base: ['Maximum job timeout has a value which could not be accepted'])
end end
end end
......
...@@ -158,6 +158,29 @@ describe Project do ...@@ -158,6 +158,29 @@ describe Project do
end end
end end
end end
describe 'ci_pipelines association' do
context 'when feature flag pipeline_ci_sources_only is enabled' do
it 'returns only pipelines from ci_sources' do
stub_feature_flags(pipeline_ci_sources_only: true)
expect(Ci::Pipeline).to receive(:ci_sources).and_call_original
subject.ci_pipelines
end
end
context 'when feature flag pipeline_ci_sources_only is disabled' do
it 'returns all pipelines' do
stub_feature_flags(pipeline_ci_sources_only: false)
expect(Ci::Pipeline).not_to receive(:ci_sources).and_call_original
expect(Ci::Pipeline).to receive(:all).and_call_original.at_least(:once)
subject.ci_pipelines
end
end
end
end end
describe 'modules' do describe 'modules' do
...@@ -170,6 +193,7 @@ describe Project do ...@@ -170,6 +193,7 @@ describe Project do
it { is_expected.to include_module(Sortable) } it { is_expected.to include_module(Sortable) }
end end
<<<<<<< HEAD
describe 'scopes' do describe 'scopes' do
context '#with_wiki_enabled' do context '#with_wiki_enabled' do
it 'returns a project' do it 'returns a project' do
...@@ -182,6 +206,8 @@ describe Project do ...@@ -182,6 +206,8 @@ describe Project do
end end
end end
=======
>>>>>>> upstream/master
describe '.missing_kubernetes_namespace' do describe '.missing_kubernetes_namespace' do
let!(:project) { create(:project) } let!(:project) { create(:project) }
let!(:cluster) { create(:cluster, :provided_by_user, :group) } let!(:cluster) { create(:cluster, :provided_by_user, :group) }
......
...@@ -121,6 +121,13 @@ describe API::Files do ...@@ -121,6 +121,13 @@ describe API::Files do
end end
end end
context 'when PATs are used' do
it_behaves_like 'repository files' do
let(:token) { create(:personal_access_token, scopes: ['read_repository'], user: user) }
let(:current_user) { { personal_access_token: token } }
end
end
context 'when authenticated', 'as a developer' do context 'when authenticated', 'as a developer' do
it_behaves_like 'repository files' do it_behaves_like 'repository files' do
let(:current_user) { user } let(:current_user) { user }
...@@ -217,6 +224,13 @@ describe API::Files do ...@@ -217,6 +224,13 @@ describe API::Files do
end end
end end
context 'when PATs are used' do
it_behaves_like 'repository files' do
let(:token) { create(:personal_access_token, scopes: ['read_repository'], user: user) }
let(:current_user) { { personal_access_token: token } }
end
end
context 'when unauthenticated', 'and project is private' do context 'when unauthenticated', 'and project is private' do
it_behaves_like '404 response' do it_behaves_like '404 response' do
let(:request) { get api(route(file_path)), params } let(:request) { get api(route(file_path)), params }
...@@ -317,6 +331,21 @@ describe API::Files do ...@@ -317,6 +331,21 @@ describe API::Files do
let(:request) { get api(route(file_path), guest), params } let(:request) { get api(route(file_path), guest), params }
end end
end end
context 'when PATs are used' do
it 'returns file by commit sha' do
token = create(:personal_access_token, scopes: ['read_repository'], user: user)
# This file is deleted on HEAD
file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
expect(Gitlab::Workhorse).to receive(:send_git_blob)
get api(route(file_path) + "/raw", personal_access_token: token), params
expect(response).to have_gitlab_http_status(200)
end
end
end end
describe "POST /projects/:id/repository/files/:file_path" do describe "POST /projects/:id/repository/files/:file_path" do
...@@ -362,6 +391,24 @@ describe API::Files do ...@@ -362,6 +391,24 @@ describe API::Files do
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
end end
context 'with PATs' do
it 'returns 403 with `read_repository` scope' do
token = create(:personal_access_token, scopes: ['read_repository'], user: user)
post api(route(file_path), personal_access_token: token), params
expect(response).to have_gitlab_http_status(403)
end
it 'returns 201 with `api` scope' do
token = create(:personal_access_token, scopes: ['api'], user: user)
post api(route(file_path), personal_access_token: token), params
expect(response).to have_gitlab_http_status(201)
end
end
context "when specifying an author" do context "when specifying an author" do
it "creates a new file with the specified author" do it "creates a new file with the specified author" do
params.merge!(author_email: author_email, author_name: author_name) params.merge!(author_email: author_email, author_name: author_name)
......
...@@ -36,4 +36,12 @@ describe IssueSerializer do ...@@ -36,4 +36,12 @@ describe IssueSerializer do
expect(json_entity).to match_schema('entities/issue_board') expect(json_entity).to match_schema('entities/issue_board')
end end
end end
context 'board issue serialization' do
let(:serializer) { 'board' }
it 'matches board issue json schema' do
expect(json_entity).to match_schema('entities/issue_board')
end
end
end end
...@@ -45,3 +45,19 @@ shared_examples 'diff statistics' do |test_include_stats_flag: true| ...@@ -45,3 +45,19 @@ shared_examples 'diff statistics' do |test_include_stats_flag: true|
end end
end end
end end
shared_examples 'unfoldable diff' do
let(:subject) { described_class.new(diffable, diff_options: nil) }
it 'calls Gitlab::Diff::File#unfold_diff_lines with correct position' do
position = instance_double(Gitlab::Diff::Position, file_path: 'README')
readme_file = instance_double(Gitlab::Diff::File, file_path: 'README')
other_file = instance_double(Gitlab::Diff::File, file_path: 'foo.rb')
nil_path_file = instance_double(Gitlab::Diff::File, file_path: nil)
allow(subject).to receive(:diff_files) { [readme_file, other_file, nil_path_file] }
expect(readme_file).to receive(:unfold_diff_lines).with(position)
subject.unfold_diff_files([position])
end
end
require 'spec_helper' require 'spec_helper'
RSpec.describe 'admin active tab' do describe 'layouts/nav/sidebar/_admin' do
before do
sign_in(create(:admin))
end
shared_examples 'page has active tab' do |title| shared_examples 'page has active tab' do |title|
it "activates #{title} tab" do it "activates #{title} tab" do
expect(page).to have_selector('.nav-sidebar .sidebar-top-level-items > li.active', count: 1) render
expect(page.find('.nav-sidebar .sidebar-top-level-items > li.active')).to have_content(title)
expect(rendered).to have_selector('.nav-sidebar .sidebar-top-level-items > li.active', count: 1)
expect(rendered).to have_css('.nav-sidebar .sidebar-top-level-items > li.active', text: title)
end end
end end
shared_examples 'page has active sub tab' do |title| shared_examples 'page has active sub tab' do |title|
it "activates #{title} sub tab" do it "activates #{title} sub tab" do
expect(page).to have_selector('.sidebar-sub-level-items > li.active', count: 2) render
expect(page.all('.sidebar-sub-level-items > li.active')[1]).to have_content(title)
expect(rendered).to have_css('.sidebar-sub-level-items > li.active', text: title)
end end
end end
context 'on home page' do context 'on home page' do
before do before do
visit admin_root_path allow(controller).to receive(:controller_name).and_return('dashboard')
end end
it_behaves_like 'page has active tab', 'Overview' it_behaves_like 'page has active tab', 'Overview'
...@@ -29,7 +28,8 @@ RSpec.describe 'admin active tab' do ...@@ -29,7 +28,8 @@ RSpec.describe 'admin active tab' do
context 'on projects' do context 'on projects' do
before do before do
visit admin_projects_path allow(controller).to receive(:controller_name).and_return('projects')
allow(controller).to receive(:controller_path).and_return('admin/projects')
end end
it_behaves_like 'page has active tab', 'Overview' it_behaves_like 'page has active tab', 'Overview'
...@@ -38,7 +38,7 @@ RSpec.describe 'admin active tab' do ...@@ -38,7 +38,7 @@ RSpec.describe 'admin active tab' do
context 'on groups' do context 'on groups' do
before do before do
visit admin_groups_path allow(controller).to receive(:controller_name).and_return('groups')
end end
it_behaves_like 'page has active tab', 'Overview' it_behaves_like 'page has active tab', 'Overview'
...@@ -47,7 +47,7 @@ RSpec.describe 'admin active tab' do ...@@ -47,7 +47,7 @@ RSpec.describe 'admin active tab' do
context 'on users' do context 'on users' do
before do before do
visit admin_users_path allow(controller).to receive(:controller_name).and_return('users')
end end
it_behaves_like 'page has active tab', 'Overview' it_behaves_like 'page has active tab', 'Overview'
...@@ -56,7 +56,7 @@ RSpec.describe 'admin active tab' do ...@@ -56,7 +56,7 @@ RSpec.describe 'admin active tab' do
context 'on logs' do context 'on logs' do
before do before do
visit admin_logs_path allow(controller).to receive(:controller_name).and_return('logs')
end end
it_behaves_like 'page has active tab', 'Monitoring' it_behaves_like 'page has active tab', 'Monitoring'
...@@ -65,7 +65,7 @@ RSpec.describe 'admin active tab' do ...@@ -65,7 +65,7 @@ RSpec.describe 'admin active tab' do
context 'on messages' do context 'on messages' do
before do before do
visit admin_broadcast_messages_path allow(controller).to receive(:controller_name).and_return('broadcast_messages')
end end
it_behaves_like 'page has active tab', 'Messages' it_behaves_like 'page has active tab', 'Messages'
...@@ -73,7 +73,7 @@ RSpec.describe 'admin active tab' do ...@@ -73,7 +73,7 @@ RSpec.describe 'admin active tab' do
context 'on hooks' do context 'on hooks' do
before do before do
visit admin_hooks_path allow(controller).to receive(:controller_name).and_return('hooks')
end end
it_behaves_like 'page has active tab', 'Hooks' it_behaves_like 'page has active tab', 'Hooks'
...@@ -81,7 +81,7 @@ RSpec.describe 'admin active tab' do ...@@ -81,7 +81,7 @@ RSpec.describe 'admin active tab' do
context 'on background jobs' do context 'on background jobs' do
before do before do
visit admin_background_jobs_path allow(controller).to receive(:controller_name).and_return('background_jobs')
end end
it_behaves_like 'page has active tab', 'Monitoring' it_behaves_like 'page has active tab', 'Monitoring'
......
This source diff could not be displayed because it is too large. You can view the blob instead.
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