Commit 0412eed5 authored by GitLab Bot's avatar GitLab Bot

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

# Conflicts:
#	app/assets/javascripts/jobs/components/job_app.vue
#	app/assets/javascripts/jobs/store/getters.js
#	app/serializers/build_details_entity.rb
#	doc/api/merge_requests.md
#	locale/gitlab.pot
#	spec/javascripts/jobs/components/job_app_spec.js

[ci skip]
parents 89bcbdca c350f3cc
......@@ -769,7 +769,7 @@ static-analysis:
docs lint:
<<: *dedicated-runner
<<: *except-qa
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine"
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-docs-lint"
stage: test
variables:
SETUP_DB: "false"
......@@ -779,8 +779,8 @@ docs lint:
script:
- scripts/lint-doc.sh
- scripts/lint-changelog-yaml
- mv doc/ /nanoc/content/
- cd /nanoc
- mv doc/ /tmp/gitlab-docs/content/
- cd /tmp/gitlab-docs
# Build HTML from Markdown
- bundle exec nanoc
# Check the internal links
......
......@@ -307,6 +307,7 @@ gem 'peek-mysql2', '~> 1.1.0', group: :mysql
gem 'peek-pg', '~> 1.3.0', group: :postgres
gem 'peek-rblineprof', '~> 0.2.0'
gem 'peek-redis', '~> 1.2.0'
gem 'gitlab-sidekiq-fetcher', require: 'sidekiq-reliable-fetch'
# Metrics
group :metrics do
......
......@@ -326,6 +326,8 @@ GEM
posix-spawn (~> 0.3)
gitlab-license (1.0.0)
gitlab-markup (1.6.4)
gitlab-sidekiq-fetcher (0.3.0)
sidekiq (~> 5)
gitlab-styles (2.4.1)
rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0)
......@@ -1066,6 +1068,7 @@ DEPENDENCIES
gitlab-gollum-lib (~> 4.2)
gitlab-license (~> 1.0)
gitlab-markup (~> 1.6.4)
gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2)
......
......@@ -329,6 +329,8 @@ GEM
posix-spawn (~> 0.3)
gitlab-license (1.0.0)
gitlab-markup (1.6.4)
gitlab-sidekiq-fetcher (0.3.0)
sidekiq (~> 5)
gitlab-styles (2.4.1)
rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0)
......@@ -1075,6 +1077,7 @@ DEPENDENCIES
gitlab-gollum-lib (~> 4.2)
gitlab-license (~> 1.0)
gitlab-markup (~> 1.6.4)
gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2)
......
......@@ -5,6 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CIIcon from '~/vue_shared/components/ci_icon.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
/**
* CommitItem
......@@ -29,6 +30,7 @@ export default {
ClipboardButton,
CIIcon,
TimeAgoTooltip,
CommitPipelineStatus,
},
props: {
commit: {
......@@ -102,6 +104,14 @@ export default {
></pre>
</div>
<div class="commit-actions flex-row d-none d-sm-flex">
<div
v-if="commit.signatureHtml"
v-html="commit.signatureHtml"
></div>
<commit-pipeline-status
v-if="commit.pipelineStatusPath"
:endpoint="commit.pipelineStatusPath"
/>
<div class="commit-sha-group">
<div
class="label label-monospace"
......
......@@ -469,7 +469,9 @@ export default {
class="gl-responsive-table-row"
role="row">
<div
class="table-section section-wrap section-15"
v-tooltip
:title="model.name"
class="table-section section-wrap section-15 text-truncate"
role="gridcell"
>
<div
......@@ -500,9 +502,7 @@ export default {
v-if="!model.isFolder"
class="environment-name table-mobile-content">
<a
v-tooltip
:href="environmentPath"
:title="model.name"
>
{{ model.name }}
</a>
......
......@@ -66,7 +66,7 @@
<button
:class="{ disabled: formState.updateLoading || !isSubmitEnabled }"
:disabled="formState.updateLoading || !isSubmitEnabled"
class="btn btn-success float-left"
class="btn btn-success float-left qa-save-button"
type="submit"
@click.prevent="updateIssuable">
Save changes
......@@ -86,7 +86,7 @@
v-if="shouldShowDeleteButton"
:class="{ disabled: deleteLoading }"
:disabled="deleteLoading"
class="btn btn-danger float-right append-right-default"
class="btn btn-danger float-right append-right-default qa-delete-button"
type="button"
@click="deleteIssuable">
Delete
......
......@@ -61,7 +61,8 @@
ref="textarea"
slot="textarea"
v-model="formState.description"
class="note-textarea js-gfm-input js-autosize markdown-area"
class="note-textarea js-gfm-input js-autosize markdown-area
qa-description-textarea"
data-supports-quick-actions="false"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
......
......@@ -22,7 +22,7 @@
<input
id="issuable-title"
v-model="formState.title"
class="form-control"
class="form-control qa-title-input"
type="text"
placeholder="Title"
aria-label="Title"
......
......@@ -79,7 +79,8 @@ export default {
v-if="showInlineEditButton && canUpdate"
v-tooltip
type="button"
class="btn btn-default btn-edit btn-svg js-issuable-edit"
class="btn btn-default btn-edit btn-svg js-issuable-edit
qa-edit-button"
title="Edit title and description"
data-placement="bottom"
data-container="body"
......
......@@ -2,9 +2,12 @@
import { mapGetters, mapState } from 'vuex';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import Callout from '~/vue_shared/components/callout.vue';
<<<<<<< HEAD
// ee-only start
import SharedRunner from 'ee/jobs/components/shared_runner_limit_block.vue';
// ee-only end
=======
>>>>>>> upstream/master
import EmptyState from './empty_state.vue';
import EnvironmentsBlock from './environments_block.vue';
import ErasedBlock from './erased_block.vue';
......@@ -37,7 +40,10 @@
'jobHasStarted',
'hasEnvironment',
'isJobStuck',
<<<<<<< HEAD
'shouldRenderSharedRunnerLimitWarning',
=======
>>>>>>> upstream/master
'hasTrace',
'emptyStateIllustration',
]),
......
......@@ -48,11 +48,14 @@ export const emptyStateIllustration = state =>
export const isJobStuck = state =>
state.job.status.group === 'pending' &&
(!_.isEmpty(state.job.runners) && state.job.runners.available === false);
<<<<<<< HEAD
// ee-only start
export const shouldRenderSharedRunnerLimitWarning = state =>
state.job.runners && state.job.runners.quota && state.job.runners.quota.used;
// ee-only end
=======
>>>>>>> upstream/master
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -365,7 +365,7 @@ Please check your network connection and try again.`;
:disabled="isSubmitting"
name="note[note]"
class="note-textarea js-vue-comment-form js-note-text
js-gfm-input js-autosize markdown-area js-vue-textarea"
js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
data-supports-quick-actions="true"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
......@@ -380,7 +380,8 @@ js-gfm-input js-autosize markdown-area js-vue-textarea"
append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown">
<button
:disabled="isSubmitButtonDisabled"
class="btn btn-success comment-btn js-comment-button js-comment-submit-button"
class="btn btn-create comment-btn js-comment-button js-comment-submit-button
qa-comment-button"
type="submit"
@click.prevent="handleSave()">
{{ __(commentButtonTitle) }}
......
......@@ -79,10 +79,13 @@ export default class Todos {
.then(({ data }) => {
this.updateRowState(target);
this.updateBadges(data);
}).catch(() => flash(__('Error updating todo status.')));
}).catch(() => {
this.updateRowState(target, true);
return flash(__('Error updating todo status.'));
});
}
updateRowState(target) {
updateRowState(target, isInactive = false) {
const row = target.closest('li');
const restoreBtn = row.querySelector('.js-undo-todo');
const doneBtn = row.querySelector('.js-done-todo');
......@@ -91,7 +94,10 @@ export default class Todos {
target.removeAttribute('disabled');
target.classList.remove('disabled');
if (target === doneBtn) {
if (isInactive === true) {
restoreBtn.classList.add('hidden');
doneBtn.classList.remove('hidden');
} else if (target === doneBtn) {
row.classList.add('done-reversible');
restoreBtn.classList.remove('hidden');
} else if (target === restoreBtn) {
......
......@@ -592,7 +592,7 @@ function UsersSelect(currentUser, els, options = {}) {
if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
var trimmed = query.term.trim();
emailUser = {
name: "Invite \"" + query.term + "\" by email",
name: "Invite \"" + trimmed + "\" by email",
username: trimmed,
id: trimmed,
invite: true
......
......@@ -20,20 +20,24 @@
display: inline-block;
overflow-x: auto;
border: 0;
border-color: $gray-100;
border-color: $gl-gray-100;
@supports (width: fit-content) {
display: block;
width: fit-content;
}
tbody {
background-color: $white-light;
}
tr {
th {
border-bottom: solid 2px $gray-100;
border-bottom: solid 2px $gl-gray-100;
}
td {
border-color: $gray-100;
border-color: $gl-gray-100;
}
}
}
......
......@@ -18,3 +18,4 @@ $success: $green-500;
$info: $blue-500;
$warning: $orange-500;
$danger: $red-500;
$zindex-modal-backdrop: 1040;
@import 'framework/variables';
@import 'framework/variables_overrides';
@import 'peek/views/rblineprof';
#js-peek {
......@@ -6,7 +7,7 @@
left: 0;
top: 0;
width: 100%;
z-index: 1039;
z-index: #{$zindex-modal-backdrop + 1};
height: $performance-bar-height;
background: $black;
......
......@@ -25,7 +25,13 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
@diffs.write_cache
render json: DiffsSerializer.new(current_user: current_user, project: @merge_request.project).represent(@diffs, additional_attributes)
request = {
current_user: current_user,
project: @merge_request.project,
render: ->(partial, locals) { view_to_html_string(partial, locals) }
}
render json: DiffsSerializer.new(request).represent(@diffs, additional_attributes)
end
def define_diff_vars
......
......@@ -428,6 +428,10 @@ class IssuableFinder
params[:milestone_title] == Milestone::Upcoming.name
end
def filter_by_any_milestone?
params[:milestone_title] == Milestone::Any.title
end
def filter_by_started_milestone?
params[:milestone_title] == Milestone::Started.name
end
......@@ -437,6 +441,8 @@ class IssuableFinder
if milestones?
if filter_by_no_milestone?
items = items.left_joins_milestones.where(milestone_id: [-1, nil])
elsif filter_by_any_milestone?
items = items.any_milestone
elsif filter_by_upcoming_milestone?
upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items))
items = items.left_joins_milestones.where(milestone_id: upcoming_ids)
......
......@@ -129,7 +129,7 @@ class LabelsFinder < UnionFinder
end
def project?
params[:project_id].present?
params[:project].present? || params[:project_id].present?
end
def projects?
......@@ -152,7 +152,7 @@ class LabelsFinder < UnionFinder
return @project if defined?(@project)
if project?
@project = Project.find(params[:project_id])
@project = params[:project] || Project.find(params[:project_id])
@project = nil unless authorized_to_read_labels?(@project)
else
@project = nil
......
......@@ -319,7 +319,11 @@ class Commit
def status(ref = nil)
return @statuses[ref] if @statuses.key?(ref)
@statuses[ref] = project.pipelines.latest_status_per_commit(id, ref)[id]
@statuses[ref] = status_for_project(ref, project)
end
def status_for_project(ref, pipeline_project)
pipeline_project.pipelines.latest_status_per_commit(id, ref)[id]
end
def set_status_for_ref(ref, status)
......
......@@ -78,6 +78,7 @@ module Issuable
scope :recent, -> { reorder(id: :desc) }
scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :any_milestone, -> { where('milestone_id IS NOT NULL') }
scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
scope :opened, -> { with_state(:opened) }
scope :only_opened, -> { with_state(:opened) }
......
......@@ -31,9 +31,11 @@ module Subscribable
end
def subscribers(project)
subscriptions_available(project)
.where(subscribed: true)
.map(&:user)
relation = subscriptions_available(project)
.where(subscribed: true)
.select(:user_id)
User.where(id: relation)
end
def toggle_subscription(user, project = nil)
......
......@@ -391,7 +391,11 @@ class ProjectPolicy < BasePolicy
greedy_load_subject ||= !@user.persisted?
if greedy_load_subject
project.team.members.include?(user)
# We want to load all the members with one query. Calling #include? on
# project.team.members will perform a separate query for each user, unless
# project.team.members was loaded before somewhere else. Calling #to_a
# ensures it's always loaded before checking for membership.
project.team.members.to_a.include?(user)
else
# otherwise we just make a specific query for
# this particular user.
......
# frozen_string_literal: true
class BuildDetailsEntity < JobEntity
<<<<<<< HEAD
prepend ::EE::BuildDetailEntity
=======
>>>>>>> upstream/master
expose :coverage, :erased_at, :duration
expose :tag_list, as: :tags
expose :has_trace?, as: :has_trace
......
......@@ -25,4 +25,25 @@ class CommitEntity < API::Entities::Commit
expose :title_html, if: { type: :full } do |commit|
markdown_field(commit, :title)
end
expose :signature_html, if: { type: :full } do |commit|
render('projects/commit/_signature', signature: commit.signature) if commit.has_signature?
end
expose :pipeline_status_path, if: { type: :full } do |commit, options|
pipeline_ref = options[:pipeline_ref]
pipeline_project = options[:pipeline_project] || commit.project
next unless pipeline_ref && pipeline_project
status = commit.status_for_project(pipeline_ref, pipeline_project)
next unless status
pipelines_project_commit_path(pipeline_project, commit.id, ref: pipeline_ref)
end
def render(*args)
return unless request.respond_to?(:render) && request.render.respond_to?(:call)
request.render.call(*args)
end
end
......@@ -18,7 +18,9 @@ class DiffsEntity < Grape::Entity
expose :commit do |diffs, options|
CommitEntity.represent options[:commit], options.merge(
type: :full,
commit_url_params: { merge_request_iid: merge_request&.iid }
commit_url_params: { merge_request_iid: merge_request&.iid },
pipeline_ref: merge_request&.source_branch,
pipeline_project: merge_request&.source_project
)
end
......
- add_to_breadcrumbs "Projects", admin_projects_path
- breadcrumb_title @project.full_name
- page_title @project.full_name, "Projects"
- @content_class = "admin-projects"
%h3.page-title
Project: #{@project.full_name}
= link_to edit_project_path(@project), class: "btn btn-nr float-right" do
......
%p.slead
Should you ever lose your phone, each of these recovery codes can be used one
Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one
time each to regain access to your account. Please save them in a safe place, or you
%b will
lose access to your account.
......
......@@ -6,13 +6,13 @@
.row.prepend-top-default
.col-lg-4
%h4.prepend-top-0
Register Two-Factor Authentication App
Register Two-Factor Authenticator
%p
Use an app on your mobile device to enable two-factor authentication (2FA).
Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).
.col-lg-8
- if current_user.two_factor_otp_enabled?
%p
You've already enabled two-factor authentication using mobile authenticator applications. In order to register a different device, you must first disable two-factor authentication.
You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication.
%p
If you lose your recovery codes you can generate new ones, invalidating all previous codes.
%div
......
......@@ -27,7 +27,7 @@
.form-group
.form-check
= platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input' }, 'rbac', 'abac'
= platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input qa-rbac-checkbox' }, 'rbac', 'abac'
= platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
.form-text.text-muted
= s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
......
---
title: "Fix the issue where long environment names aren't being truncated, causing the environment name to overlap into the column next to it."
merge_request: 22104
type: fixed
---
title: Rephrase 2FA and TOTP documentation and view
merge_request: 21998
author: Marc Schwede
type: other
---
title: Reduce queries needed to compute notification recipients
merge_request: 22050
author:
type: performance
---
title: Fix the state of the Done button when there is an error in the GitLab Todos
section
merge_request:
author: marcos8896
type: fixed
---
title: Fix performance bar modal position
merge_request: 21577
author:
type: fixed
---
title: Allows to filter issues by Any milestone in the API
merge_request: 22080
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Add borders and white background to markdown tables
merge_request:
author:
type: fixed
---
title: Trim whitespace when inviting a new user by email
merge_request: 22119
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: Use Reliable Sidekiq fetch
merge_request: 21715
author:
type: fixed
---
title: Copy nurtch demo notebooks at Jupyter startup
merge_request: 21698
author: Amit Rathi
type: added
---
title: Enable unauthenticated access to public SSH keys via the API
merge_request: 20118
author: Ronald Claveau
type: changed
---
title: Banzai label ref finder - minimize SQL calls by sharing context more aggresively
merge_request: 22070
author:
type: performance
......@@ -40,6 +40,10 @@ Sidekiq.configure_server do |config|
ActiveRecord::Base.clear_all_connections!
end
if Feature.enabled?(:gitlab_sidekiq_reliable_fetcher)
Sidekiq::ReliableFetcher.setup_reliable_fetch!(config)
end
# Sidekiq-cron: load recurring jobs from gitlab.yml
# UGLY Hack to get nested hash from settingslogic
cron_jobs = JSON.parse(Gitlab.config.cron_jobs.to_json)
......@@ -61,10 +65,10 @@ Sidekiq.configure_server do |config|
Gitlab::SidekiqVersioning.install!
config = Gitlab::Database.config ||
db_config = Gitlab::Database.config ||
Rails.application.config.database_configuration[Rails.env]
config['pool'] = Sidekiq.options[:concurrency]
ActiveRecord::Base.establish_connection(config)
db_config['pool'] = Sidekiq.options[:concurrency]
ActiveRecord::Base.establish_connection(db_config)
Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
# EE only
......
......@@ -37,7 +37,7 @@ GET /issues?my_reaction_emoji=star
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone |
| `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone. `Any+Milestone` lists all issues that have an assigned milestone |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
......
......@@ -117,8 +117,12 @@ Parameters:
"human_time_estimate": null,
"human_total_time_spent": null
},
<<<<<<< HEAD
"squash": false,
"approvals_before_merge": null
=======
"squash": false
>>>>>>> upstream/master
}
]
```
......@@ -241,8 +245,12 @@ Parameters:
"human_time_estimate": null,
"human_total_time_spent": null
},
<<<<<<< HEAD
"squash": false,
"approvals_before_merge": null
=======
"squash": false
>>>>>>> upstream/master
}
]
```
......@@ -353,8 +361,12 @@ Parameters:
"human_time_estimate": null,
"human_total_time_spent": null
},
<<<<<<< HEAD
"squash": false,
"approvals_before_merge": null
=======
"squash": false
>>>>>>> upstream/master
}
]
```
......@@ -797,8 +809,12 @@ order for it to take effect:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
<<<<<<< HEAD
"diverged_commits_count": 2,
"approvals_before_merge": null
=======
"diverged_commits_count": 2
>>>>>>> upstream/master
}
```
......@@ -925,8 +941,12 @@ Must include at least one non-required attribute from above.
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
<<<<<<< HEAD
"diverged_commits_count": 2,
"approvals_before_merge": null
=======
"diverged_commits_count": 2
>>>>>>> upstream/master
}
```
......@@ -1069,8 +1089,12 @@ Parameters:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
<<<<<<< HEAD
"diverged_commits_count": 2,
"approvals_before_merge": null
=======
"diverged_commits_count": 2
>>>>>>> upstream/master
}
```
......@@ -1185,8 +1209,12 @@ Parameters:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
<<<<<<< HEAD
"diverged_commits_count": 2,
"approvals_before_merge": null
=======
"diverged_commits_count": 2
>>>>>>> upstream/master
}
```
......@@ -1385,8 +1413,12 @@ Example response:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
<<<<<<< HEAD
"diverged_commits_count": 2,
"approvals_before_merge": null
=======
"diverged_commits_count": 2
>>>>>>> upstream/master
}
```
......@@ -1507,8 +1539,12 @@ Example response:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
<<<<<<< HEAD
"diverged_commits_count": 2,
"approvals_before_merge": null
=======
"diverged_commits_count": 2
>>>>>>> upstream/master
}
```
......
......@@ -563,7 +563,7 @@ Parameters:
## List SSH keys for user
Get a list of a specified user's SSH keys. Available only for admin
Get a list of a specified user's SSH keys.
```
GET /users/:id/keys
......
......@@ -60,7 +60,7 @@ people.
The current team labels are:
- ~Configuration
- ~Configure
- ~"CI/CD"
- ~Create
- ~Distribution
......
......@@ -2,18 +2,18 @@
Two-factor Authentication (2FA) provides an additional level of security to your
GitLab account. Once enabled, in addition to supplying your username and
password to login, you'll be prompted for a code generated by an application on
your phone.
password to login, you'll be prompted for a code generated by your one time password
authenticator. For example, a password manager on one of your devices.
By enabling 2FA, the only way someone other than you can log into your account
is to know your username and password *and* have access to your phone.
is to know your username and password *and* have access to your one time password secret.
## Overview
> **Note:**
When you enable 2FA, don't forget to back up your recovery codes.
In addition to a phone application, GitLab supports U2F (universal 2nd factor) devices as
In addition to one time authenticators (TOTP), GitLab supports U2F (universal 2nd factor) devices as
the second factor of authentication. Once enabled, in addition to supplying your username and
password to login, you'll be prompted to activate your U2F device (usually by pressing
a button on it), and it will perform secure authentication on your behalf.
......@@ -24,10 +24,10 @@ from other browsers.
## Enabling 2FA
There are two ways to enable two-factor authentication: via a mobile application
There are two ways to enable two-factor authentication: via a one time password authenticator
or a U2F device.
### Enable 2FA via mobile application
### Enable 2FA via one time password authenticator
**In GitLab:**
......@@ -82,7 +82,7 @@ Click on **Register U2F Device** to complete the process.
> **Note:**
Recovery codes are not generated for U2F devices.
Should you ever lose access to your phone, you can use one of the ten provided
Should you ever lose access to your one time password authenticator, you can use one of the ten provided
backup codes to login to your account. We suggest copying or printing them for
storage in a safe place. **Each code can be used only once** to log in to your
account.
......@@ -98,7 +98,7 @@ be presented with a second prompt, depending on which type of 2FA you've enabled
### Log in via mobile application
Enter the pin from your phone's application or a recovery code to log in.
Enter the pin from your one time password authenticator's application or a recovery code to log in.
![Two-Factor Authentication on sign in via OTP](img/2fa_auth.png)
......
......@@ -216,7 +216,7 @@ twice, which can lead to confusion during deployments.
| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) |
| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with [Rubix](https://github.com/amit1rrr/rubix). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
## Getting the external IP address
......
......@@ -29,7 +29,7 @@ directly in a filesystem level.
1. Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions you can
follow [this article](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html).
1. Download SubGit from https://subgit.com/download/.
1. Download SubGit from <https://subgit.com/download/>.
1. Unpack the downloaded SubGit zip archive to the `/opt` directory. The `subgit`
command will be available at `/opt/subgit-VERSION/bin/subgit`.
......@@ -71,7 +71,7 @@ edit $GIT_REPO_PATH/subgit/config
```
For more information regarding the SubGit configuration options, refer to
[SubGit's documentation](https://subgit.com/documentation.html) website.
[SubGit's documentation](https://subgit.com/documentation/) website.
### Initial translation
......@@ -97,7 +97,7 @@ subgit import $GIT_REPO_PATH
### SubGit licensing
Running SubGit in a mirror mode requires a
[registration](https://subgit.com/pricing.html). Registration is free for open
[registration](https://subgit.com/pricing/). Registration is free for open
source, academic and startup projects.
We're currently working on deeper GitLab/SubGit integration. You may track our
......@@ -179,5 +179,6 @@ git push --tags origin
```
## Contribute to this guide
We welcome all contributions that would expand this guide with instructions on
how to migrate from SVN and other version control systems.
......@@ -262,7 +262,7 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
desc 'Get the SSH keys of a specified user. Available only for admins.' do
desc 'Get the SSH keys of a specified user.' do
success Entities::SSHKey
end
params do
......@@ -271,10 +271,8 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
get ':id/keys' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
not_found!('User') unless user && can?(current_user, :read_user, user)
present paginate(user.keys), with: Entities::SSHKey
end
......
......@@ -13,6 +13,7 @@ module Banzai
# Returns a Project, or nil if the reference can't be found
def parent_from_ref(ref)
return context[:project] || context[:group] unless ref
return context[:project] if context[:project]&.full_path == ref
Project.find_by_full_path(ref)
end
......
......@@ -48,7 +48,7 @@ module Banzai
include_ancestor_groups: true,
only_group_labels: true }
else
{ project_id: parent.id,
{ project: parent,
include_ancestor_groups: true }
end
......
......@@ -689,9 +689,6 @@ rollout 100%:
helm version --client
tiller -version
helm init --client-only
helm plugin install https://github.com/adamreese/helm-local
curl -L -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl"
chmod +x /usr/bin/kubectl
kubectl version --client
......@@ -800,9 +797,9 @@ rollout 100%:
function initialize_tiller() {
echo "Checking Tiller..."
helm local start
helm local status
export HELM_HOST=":44134"
tiller -listen ${HELM_HOST} -alsologtostderr > /dev/null 2>&1 &
echo "Tiller is listening on ${HELM_HOST}"
if ! helm version --debug; then
echo "Failed to init Tiller."
......
......@@ -220,7 +220,7 @@ module Gitlab
result
end
SERVER_FEATURE_FLAGS = %w[gogit_findcommit git_v2].freeze
SERVER_FEATURE_FLAGS = %w[gogit_findcommit].freeze
def self.server_feature_flags
SERVER_FEATURE_FLAGS.map do |f|
......
......@@ -8674,9 +8674,12 @@ msgstr ""
msgid "You do not have any subscriptions yet"
msgstr ""
<<<<<<< HEAD
msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
msgstr ""
=======
>>>>>>> upstream/master
msgid "You don't have any applications"
msgstr ""
......
......@@ -257,6 +257,9 @@ module QA
autoload :Dropzone, 'qa/page/component/dropzone'
autoload :GroupsFilter, 'qa/page/component/groups_filter'
autoload :Select2, 'qa/page/component/select2'
module Issuable
autoload :Common, 'qa/page/component/issuable/common'
end
end
end
......
......@@ -31,6 +31,7 @@ module QA
page.set_api_url(@cluster.api_url)
page.set_ca_certificate(@cluster.ca_certificate)
page.set_token(@cluster.token)
page.check_rbac! if @cluster.rbac
page.add_cluster!
end
......
# frozen_string_literal: true
module QA
module Page
module Component
module Issuable
module Common
def self.included(base)
base.view 'app/assets/javascripts/issue_show/components/title.vue' do
element :edit_button
end
base.view 'app/assets/javascripts/issue_show/components/fields/title.vue' do
element :title_input
end
base.view 'app/assets/javascripts/issue_show/components/fields/description.vue' do
element :description_textarea
end
base.view 'app/assets/javascripts/issue_show/components/edit_actions.vue' do
element :save_button
element :delete_button
end
base.view 'app/assets/javascripts/issue_show/components/edit_actions.vue' do
element :save_button
element :delete_button
end
end
end
end
end
end
end
......@@ -5,6 +5,8 @@ module QA
module Project
module Issue
class Show < Page::Base
include Page::Component::Issuable::Common
view 'app/views/projects/issues/show.html.haml' do
element :issue_details, '.issue-details'
element :title, '.title'
......
......@@ -10,6 +10,7 @@ module QA
element :ca_certificate, 'text_area :ca_cert'
element :token, 'text_field :token'
element :add_cluster_button, "submit s_('ClusterIntegration|Add Kubernetes cluster')"
element :rbac_checkbox
end
def set_cluster_name(name)
......@@ -31,6 +32,10 @@ module QA
def add_cluster!
click_on 'Add Kubernetes cluster'
end
def check_rbac!
check_element :rbac_checkbox
end
end
end
end
......
require 'securerandom'
require 'mkmf'
require 'pathname'
module QA
module Service
class KubernetesCluster
include Service::Shellout
attr_reader :api_url, :ca_certificate, :token
attr_reader :api_url, :ca_certificate, :token, :rbac
def initialize(rbac: false)
@rbac = rbac
end
def cluster_name
@cluster_name ||= "qa-cluster-#{SecureRandom.hex(4)}-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}"
......@@ -19,7 +24,7 @@ module QA
shell <<~CMD.tr("\n", ' ')
gcloud container clusters
create #{cluster_name}
--enable-legacy-authorization
#{auth_options}
--zone #{Runtime::Env.gcloud_zone}
&& gcloud container clusters
get-credentials
......@@ -28,8 +33,21 @@ module QA
CMD
@api_url = `kubectl config view --minify -o jsonpath='{.clusters[].cluster.server}'`
@ca_certificate = Base64.decode64(`kubectl get secrets -o jsonpath="{.items[0].data['ca\\.crt']}"`)
@token = Base64.decode64(`kubectl get secrets -o jsonpath='{.items[0].data.token}'`)
if rbac
create_service_account
secrets = JSON.parse(`kubectl get secrets -o json`)
gitlab_account = secrets['items'].find do |item|
item['metadata']['annotations']['kubernetes.io/service-account.name'] == 'gitlab-account'
end
@ca_certificate = Base64.decode64(gitlab_account['data']['ca.crt'])
@token = Base64.decode64(gitlab_account['data']['token'])
else
@ca_certificate = Base64.decode64(`kubectl get secrets -o jsonpath="{.items[0].data['ca\\.crt']}"`)
@token = Base64.decode64(`kubectl get secrets -o jsonpath='{.items[0].data.token}'`)
end
self
end
......@@ -44,6 +62,42 @@ module QA
private
def create_service_account
shell('kubectl create -f -', stdin_data: service_account)
shell('kubectl create -f -', stdin_data: service_account_role_binding)
end
def service_account
<<~YAML
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitlab-account
namespace: default
YAML
end
def service_account_role_binding
<<~YAML
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: gitlab-account-binding
subjects:
- kind: ServiceAccount
name: gitlab-account
namespace: default
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
YAML
end
def auth_options
"--enable-legacy-authorization" unless rbac
end
def validate_dependencies
find_executable('gcloud') || raise("You must first install `gcloud` executable to run these tests.")
find_executable('kubectl') || raise("You must first install `kubectl` executable to run these tests.")
......
......@@ -11,10 +11,12 @@ module QA
# TODO, make it possible to use generic QA framework classes
# as a library - gitlab-org/gitlab-qa#94
#
def shell(command)
def shell(command, stdin_data: nil)
puts "Executing `#{command}`"
Open3.popen2e(*command) do |_in, out, wait|
Open3.popen2e(*command) do |stdin, out, wait|
stdin.puts(stdin_data) if stdin_data
stdin.close if stdin_data
out.each { |line| puts line }
if wait.value.exited? && wait.value.exitstatus.nonzero?
......
......@@ -9,59 +9,63 @@ module QA
@cluster&.remove!
end
it 'user creates a new project and runs auto devops' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
[true, false].each do |rbac|
context "when rbac is #{rbac ? 'enabled' : 'disabled'}" do
it 'user creates a new project and runs auto devops' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |p|
p.name = 'project-with-autodevops'
p.description = 'Project with Auto Devops'
end
project = Factory::Resource::Project.fabricate! do |p|
p.name = 'project-with-autodevops'
p.description = 'Project with Auto Devops'
end
# Disable code_quality check in Auto DevOps pipeline as it takes
# too long and times out the test
Factory::Resource::SecretVariable.fabricate! do |resource|
resource.project = project
resource.key = 'CODE_QUALITY_DISABLED'
resource.value = '1'
end
# Disable code_quality check in Auto DevOps pipeline as it takes
# too long and times out the test
Factory::Resource::SecretVariable.fabricate! do |resource|
resource.project = project
resource.key = 'CODE_QUALITY_DISABLED'
resource.value = '1'
end
# Create Auto Devops compatible repo
Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.directory = Pathname
.new(__dir__)
.join('../../../../../fixtures/auto_devops_rack')
push.commit_message = 'Create Auto DevOps compatible rack application'
end
# Create Auto Devops compatible repo
Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.directory = Pathname
.new(__dir__)
.join('../../../../../fixtures/auto_devops_rack')
push.commit_message = 'Create Auto DevOps compatible rack application'
end
Page::Project::Show.act { wait_for_push }
Page::Project::Show.act { wait_for_push }
# Create and connect K8s cluster
@cluster = Service::KubernetesCluster.new.create!
kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster|
cluster.project = project
cluster.cluster = @cluster
cluster.install_helm_tiller = true
cluster.install_ingress = true
cluster.install_prometheus = true
cluster.install_runner = true
end
# Create and connect K8s cluster
@cluster = Service::KubernetesCluster.new(rbac: rbac).create!
kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster|
cluster.project = project
cluster.cluster = @cluster
cluster.install_helm_tiller = true
cluster.install_ingress = true
cluster.install_prometheus = true
cluster.install_runner = true
end
project.visit!
Page::Menu::Side.act { click_ci_cd_settings }
Page::Project::Settings::CICD.perform do |p|
p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io")
end
project.visit!
Page::Menu::Side.act { click_ci_cd_settings }
Page::Project::Settings::CICD.perform do |p|
p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io")
end
project.visit!
Page::Menu::Side.act { click_ci_cd_pipelines }
Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
project.visit!
Page::Menu::Side.act { click_ci_cd_pipelines }
Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to have_build('build', status: :success, wait: 600)
expect(pipeline).to have_build('test', status: :success, wait: 600)
expect(pipeline).to have_build('production', status: :success, wait: 1200)
Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to have_build('build', status: :success, wait: 600)
expect(pipeline).to have_build('test', status: :success, wait: 600)
expect(pipeline).to have_build('production', status: :success, wait: 1200)
end
end
end
end
end
......
......@@ -637,6 +637,18 @@ describe Projects::IssuesController do
project_id: project,
id: id
end
it 'avoids (most) N+1s loading labels' do
label = create(:label, project: project).to_reference
labels = create_list(:label, 10, project: project).map(&:to_reference)
issue = create(:issue, project: project, description: 'Test issue')
control_count = ActiveRecord::QueryRecorder.new { issue.update(description: [issue.description, label].join(' ')) }.count
# Follow-up to get rid of this `2 * label.count` requirement: https://gitlab.com/gitlab-org/gitlab-ce/issues/52230
expect { issue.update(description: [issue.description, labels].join(' ')) }
.not_to exceed_query_limit(control_count + 2 * labels.count)
end
end
describe 'GET #realtime_changes' do
......
......@@ -42,7 +42,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
it 'allows registering a new device with a name' do
visit profile_account_path
manage_two_factor_authentication
expect(page).to have_content("You've already enabled two-factor authentication using mobile")
expect(page).to have_content("You've already enabled two-factor authentication using one time password authenticators")
u2f_device = register_u2f_device
......@@ -70,7 +70,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
it 'allows deleting a device' do
visit profile_account_path
manage_two_factor_authentication
expect(page).to have_content("You've already enabled two-factor authentication using mobile")
expect(page).to have_content("You've already enabled two-factor authentication using one time password authenticators")
first_u2f_device = register_u2f_device
second_u2f_device = register_u2f_device(name: 'My other device')
......
......@@ -171,6 +171,14 @@ describe IssuesFinder do
end
end
context 'filtering by any milestone' do
let(:params) { { milestone_title: Milestone::Any.title } }
it 'returns issues with any assigned milestone' do
expect(issues).to contain_exactly(issue1)
end
end
context 'filtering by upcoming milestone' do
let(:params) { { milestone_title: Milestone::Upcoming.name } }
......
......@@ -9,6 +9,8 @@ import getDiffWithCommit from '../mock_data/diff_with_commit';
const TEST_AUTHOR_NAME = 'test';
const TEST_AUTHOR_EMAIL = 'test+test@gitlab.com';
const TEST_AUTHOR_GRAVATAR = `${TEST_HOST}/avatar/test?s=36`;
const TEST_SIGNATURE_HTML = '<a>Legit commit</a>';
const TEST_PIPELINE_STATUS_PATH = `${TEST_HOST}/pipeline/status`;
const getTitleElement = vm => vm.$el.querySelector('.commit-row-message.item-title');
const getDescElement = vm => vm.$el.querySelector('pre.commit-row-description');
......@@ -16,6 +18,7 @@ const getDescExpandElement = vm => vm.$el.querySelector('.commit-content .text-e
const getShaElement = vm => vm.$el.querySelector('.commit-sha-group');
const getAvatarElement = vm => vm.$el.querySelector('.user-avatar-link');
const getCommitterElement = vm => vm.$el.querySelector('.commiter');
const getCommitActionsElement = vm => vm.$el.querySelector('.commit-actions');
describe('diffs/components/commit_widget', () => {
const Component = Vue.extend(CommitItem);
......@@ -125,4 +128,36 @@ describe('diffs/components/commit_widget', () => {
expect(nameElement).toHaveText(TEST_AUTHOR_NAME);
});
});
describe('with signature', () => {
beforeEach(done => {
vm.commit.signatureHtml = TEST_SIGNATURE_HTML;
vm.$nextTick()
.then(done)
.catch(done.fail);
});
it('renders signature html', () => {
const actionsElement = getCommitActionsElement(vm);
expect(actionsElement).toContainHtml(TEST_SIGNATURE_HTML);
});
});
describe('with pipeline status', () => {
beforeEach(done => {
vm.commit.pipelineStatusPath = TEST_PIPELINE_STATUS_PATH;
vm.$nextTick()
.then(done)
.catch(done.fail);
});
it('renders pipeline status', () => {
const actionsElement = getCommitActionsElement(vm);
expect(actionsElement).toContainElement('.ci-status-link');
});
});
});
......@@ -184,6 +184,7 @@ describe('Job App ', () => {
});
});
<<<<<<< HEAD
// ee-only start
describe('runners limit - ee', () => {
describe('with used quota', () => {
......@@ -217,6 +218,8 @@ describe('Job App ', () => {
// ee-only end
=======
>>>>>>> upstream/master
describe('environments block', () => {
it('renders environment block when job has environment', () => {
store.dispatch(
......
require 'spec_helper'
describe Banzai::CrossProjectReference do
include described_class
let(:including_class) { Class.new.include(described_class).new }
before do
allow(including_class).to receive(:context).and_return({})
allow(including_class).to receive(:parent_from_ref).and_call_original
end
describe '#parent_from_ref' do
context 'when no project was referenced' do
it 'returns the project from context' do
project = double
allow(self).to receive(:context).and_return({ project: project })
allow(including_class).to receive(:context).and_return({ project: project })
expect(parent_from_ref(nil)).to eq project
expect(including_class.parent_from_ref(nil)).to eq project
end
end
......@@ -18,15 +23,15 @@ describe Banzai::CrossProjectReference do
it 'returns the group from context' do
group = double
allow(self).to receive(:context).and_return({ group: group })
allow(including_class).to receive(:context).and_return({ group: group })
expect(parent_from_ref(nil)).to eq group
expect(including_class.parent_from_ref(nil)).to eq group
end
end
context 'when referenced project does not exist' do
it 'returns nil' do
expect(parent_from_ref('invalid/reference')).to be_nil
expect(including_class.parent_from_ref('invalid/reference')).to be_nil
end
end
......@@ -37,7 +42,7 @@ describe Banzai::CrossProjectReference do
expect(Project).to receive(:find_by_full_path)
.with('cross/reference').and_return(project2)
expect(parent_from_ref('cross/reference')).to eq project2
expect(including_class.parent_from_ref('cross/reference')).to eq project2
end
end
end
......
......@@ -60,6 +60,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
allow(project.repository).to receive(:commit).with(commit1.id.reverse)
allow(project.repository).to receive(:commit).with(commit2.id)
expect(reference_filter(act).to_html).to eq exp
end
......
......@@ -56,6 +56,7 @@ describe API::Issues do
let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
let(:no_milestone_title) { URI.escape(Milestone::None.title) }
let(:any_milestone_title) { URI.escape(Milestone::Any.title) }
before(:all) do
project.add_reporter(user)
......@@ -815,6 +816,15 @@ describe API::Issues do
expect(json_response.first['id']).to eq(confidential_issue.id)
end
it 'returns an array of issues with any milestone' do
get api("#{base_url}/issues?milestone=#{any_milestone_title}", user)
response_ids = json_response.map { |issue| issue['id'] }
expect_paginated_array_response(size: 2)
expect(response_ids).to contain_exactly(closed_issue.id, issue.id)
end
it 'sorts by created_at descending by default' do
get api("#{base_url}/issues", user)
......
......@@ -818,35 +818,25 @@ describe API::Users do
end
describe 'GET /user/:id/keys' do
before do
admin
end
it 'returns 404 for non-existing user' do
user_id = not_existing_user_id
context 'when unauthenticated' do
it 'returns authentication error' do
get api("/users/#{user.id}/keys")
expect(response).to have_gitlab_http_status(401)
end
end
get api("/users/#{user_id}/keys")
context 'when authenticated' do
it 'returns 404 for non-existing user' do
get api('/users/999999/keys', admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
it 'returns array of ssh keys' do
user.keys << key
user.save
it 'returns array of ssh keys' do
user.keys << key
user.save
get api("/users/#{user.id}/keys", admin)
get api("/users/#{user.id}/keys")
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(key.title)
end
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(key.title)
end
end
......
require 'spec_helper'
describe CommitEntity do
SIGNATURE_HTML = 'TEST'.freeze
let(:entity) do
described_class.new(commit, request: request)
end
let(:request) { double('request') }
let(:project) { create(:project, :repository) }
let(:commit) { project.commit }
......@@ -12,7 +13,11 @@ describe CommitEntity do
subject { entity.as_json }
before do
render = double('render')
allow(render).to receive(:call).and_return(SIGNATURE_HTML)
allow(request).to receive(:project).and_return(project)
allow(request).to receive(:render).and_return(render)
end
context 'when commit author is a user' do
......@@ -61,7 +66,7 @@ describe CommitEntity do
context 'when type is "full"' do
let(:entity) do
described_class.new(commit, request: request, type: :full)
described_class.new(commit, request: request, type: :full, pipeline_ref: project.default_branch, pipeline_project: project)
end
it 'exposes extra properties' do
......@@ -70,6 +75,25 @@ describe CommitEntity do
expect(subject.fetch(:description_html)).not_to be_nil
expect(subject.fetch(:title_html)).not_to be_nil
end
context 'when commit has signature' do
let(:commit) { project.commit(TestEnv::BRANCH_SHA['signed-commits']) }
it 'exposes "signature_html"' do
expect(request.render).to receive(:call)
expect(subject.fetch(:signature_html)).to be SIGNATURE_HTML
end
end
context 'when commit has pipeline' do
before do
create(:ci_pipeline, project: project, sha: commit.id)
end
it 'exposes "pipeline_status_path"' do
expect(subject.fetch(:pipeline_status_path)).not_to be_nil
end
end
end
context 'when commit_url_params is set' do
......
......@@ -10,27 +10,50 @@ describe NotificationRecipientService do
let(:issue) { create(:issue, project: project, assignees: [assignee]) }
let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id) }
def create_watcher
watcher = create(:user)
create(:notification_setting, source: project, user: watcher, level: :watch)
shared_examples 'no N+1 queries' do
it 'avoids N+1 queries', :request_store do
create_user
other_projects.each do |other_project|
create(:notification_setting, source: other_project, user: watcher, level: :watch)
service.build_new_note_recipients(note)
control_count = ActiveRecord::QueryRecorder.new do
service.build_new_note_recipients(note)
end
create_user
expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count)
end
end
it 'avoids N+1 queries', :request_store do
create_watcher
context 'when there are multiple watchers' do
def create_user
watcher = create(:user)
create(:notification_setting, source: project, user: watcher, level: :watch)
other_projects.each do |other_project|
create(:notification_setting, source: other_project, user: watcher, level: :watch)
end
end
service.build_new_note_recipients(note)
include_examples 'no N+1 queries'
end
control_count = ActiveRecord::QueryRecorder.new do
service.build_new_note_recipients(note)
context 'when there are multiple subscribers' do
def create_user
subscriber = create(:user)
issue.subscriptions.create(user: subscriber, project: project, subscribed: true)
end
create_watcher
include_examples 'no N+1 queries'
expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count)
context 'when the project is private' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
include_examples 'no N+1 queries'
end
end
end
end
......@@ -36,6 +36,11 @@ require_relative '../ee/spec/spec_helper'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
# Requires helpers, and shared contexts/examples first since they're used in other support files
# Load these first since they may be required by other helpers
require Rails.root.join("spec/support/helpers/git_helpers.rb")
# Then the rest
Dir[Rails.root.join("spec/support/helpers/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f }
......
......@@ -13,6 +13,10 @@ auth:
singleuser:
defaultUrl: "/lab"
lifecycleHooks:
postStart:
exec:
command: ["git", "clone", "https://gitlab.com/gitlab-org/nurtch-demo.git", "DevOps-Runbook-Demo"]
ingress:
enabled: true
......
......@@ -29,18 +29,7 @@
semver "^5.4.1"
source-map "^0.5.0"
"@babel/generator@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0.tgz#1efd58bffa951dc846449e58ce3a1d7f02d393aa"
integrity sha512-/BM2vupkpbZXq22l1ALO7MqXJZH2k8bKVv8Y+pABFnzWdztDB/ZLveP5At21vLz5c2YtSE6p7j2FZEsqafMz5Q==
dependencies:
"@babel/types" "^7.0.0"
jsesc "^2.5.1"
lodash "^4.17.10"
source-map "^0.5.0"
trim-right "^1.0.1"
"@babel/generator@^7.1.2":
"@babel/generator@^7.0.0", "@babel/generator@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.2.tgz#fde75c072575ce7abbd97322e8fef5bae67e4630"
integrity sha512-70A9HWLS/1RHk3Ck8tNHKxOoKQuSKocYgwDN85Pyl/RBduss6AKxUR7RIZ/lzduQMSYfWEM4DDBu6A+XGbkFig==
......@@ -224,12 +213,7 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.0.tgz#a7cd42cb3c12aec52e24375189a47b39759b783e"
integrity sha512-SmjnXCuPAlai75AFtzv+KCBcJ3sDDWbIn+WytKw1k+wAtEy6phqI2RqKh/zAnw53i1NR8su3Ep/UoqaKcimuLg==
"@babel/parser@^7.1.2":
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.2.tgz#85c5c47af6d244fab77bce6b9bd830e38c978409"
integrity sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ==
......@@ -599,7 +583,7 @@
js-levenshtein "^1.1.3"
semver "^5.3.0"
"@babel/template@^7.0.0", "@babel/template@^7.1.2":
"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==
......@@ -608,15 +592,6 @@
"@babel/parser" "^7.1.2"
"@babel/types" "^7.1.2"
"@babel/template@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.0.tgz#58cc9572e1bfe24fe1537fdf99d839d53e517e22"
integrity sha512-yZ948B/pJrwWGY6VxG6XRFsVTee3IQ7bihq9zFpM00Vydu6z5Xwg0C3J644kxI9WOTzd+62xcIsQ+AT1MGhqhA==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/parser" "^7.1.0"
"@babel/types" "^7.0.0"
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.0.tgz#503ec6669387efd182c3888c4eec07bcc45d91b2"
......@@ -632,16 +607,7 @@
globals "^11.1.0"
lodash "^4.17.10"
"@babel/types@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0.tgz#6e191793d3c854d19c6749989e3bc55f0e962118"
integrity sha512-5tPDap4bGKTLPtci2SUl/B7Gv8RnuJFuQoWx26RJobS0fFrz4reUA3JnwIM+HVHEmWE0C1mzKhDtTp8NsWY02Q==
dependencies:
esutils "^2.0.2"
lodash "^4.17.10"
to-fast-properties "^2.0.0"
"@babel/types@^7.1.2":
"@babel/types@^7.0.0", "@babel/types@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.2.tgz#183e7952cf6691628afdc2e2b90d03240bac80c0"
integrity sha512-pb1I05sZEKiSlMUV9UReaqsCPUpgbHHHu2n1piRm7JkuBkm6QxcaIzKu6FMnMtCbih/cEYTR+RGYYC96Yk9HAg==
......@@ -1199,10 +1165,9 @@ babel-loader@^8.0.4:
babel-messages@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz#875d6bc9be761c58a2ae3feee5dc4895d8c7f921"
resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=
dependencies:
babel-plugin-syntax-object-rest-spread "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-istanbul@^5.1.0:
......@@ -1219,11 +1184,6 @@ babel-plugin-rewire@^1.2.0:
resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.2.0.tgz#822562d72ed2c84e47c0f95ee232c920853e9d89"
integrity sha512-JBZxczHw3tScS+djy6JPLMjblchGhLI89ep15H3SyjujIzlxo5nr6Yjo7AXotdeVczeBmWs0tF8PgJWDdgzAkQ==
babel-plugin-syntax-object-rest-spread@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=
babel-polyfill@6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d"
......@@ -2685,12 +2645,12 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
depd@1.1.1, depd@~1.1.1:
depd@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=
depd@~1.1.2:
depd@~1.1.1, depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
......@@ -5220,14 +5180,7 @@ lz-string@^1.4.4:
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=
make-dir@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b"
integrity sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==
dependencies:
pify "^3.0.0"
make-dir@^1.3.0:
make-dir@^1.0.0, make-dir@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
......@@ -6620,12 +6573,7 @@ regenerate-unicode-properties@^7.0.0:
dependencies:
regenerate "^1.4.0"
regenerate@^1.2.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
integrity sha1-0ZQcZ7rUN+G+dkM63Vs4X5WxkmA=
regenerate@^1.4.0:
regenerate@^1.2.1, regenerate@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
......@@ -6790,20 +6738,13 @@ resolve@1.1.x:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
resolve@^1.3.2:
resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0:
version "1.8.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==
dependencies:
path-parse "^1.0.5"
resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0:
version "1.7.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
integrity sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==
dependencies:
path-parse "^1.0.5"
responselike@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
......
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