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: ...@@ -769,7 +769,7 @@ static-analysis:
docs lint: docs lint:
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-qa <<: *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 stage: test
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
...@@ -779,8 +779,8 @@ docs lint: ...@@ -779,8 +779,8 @@ docs lint:
script: script:
- scripts/lint-doc.sh - scripts/lint-doc.sh
- scripts/lint-changelog-yaml - scripts/lint-changelog-yaml
- mv doc/ /nanoc/content/ - mv doc/ /tmp/gitlab-docs/content/
- cd /nanoc - cd /tmp/gitlab-docs
# Build HTML from Markdown # Build HTML from Markdown
- bundle exec nanoc - bundle exec nanoc
# Check the internal links # Check the internal links
......
...@@ -307,6 +307,7 @@ gem 'peek-mysql2', '~> 1.1.0', group: :mysql ...@@ -307,6 +307,7 @@ gem 'peek-mysql2', '~> 1.1.0', group: :mysql
gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-pg', '~> 1.3.0', group: :postgres
gem 'peek-rblineprof', '~> 0.2.0' gem 'peek-rblineprof', '~> 0.2.0'
gem 'peek-redis', '~> 1.2.0' gem 'peek-redis', '~> 1.2.0'
gem 'gitlab-sidekiq-fetcher', require: 'sidekiq-reliable-fetch'
# Metrics # Metrics
group :metrics do group :metrics do
......
...@@ -326,6 +326,8 @@ GEM ...@@ -326,6 +326,8 @@ GEM
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab-license (1.0.0) gitlab-license (1.0.0)
gitlab-markup (1.6.4) gitlab-markup (1.6.4)
gitlab-sidekiq-fetcher (0.3.0)
sidekiq (~> 5)
gitlab-styles (2.4.1) gitlab-styles (2.4.1)
rubocop (~> 0.54.0) rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0) rubocop-gitlab-security (~> 0.1.0)
...@@ -1066,6 +1068,7 @@ DEPENDENCIES ...@@ -1066,6 +1068,7 @@ DEPENDENCIES
gitlab-gollum-lib (~> 4.2) gitlab-gollum-lib (~> 4.2)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
gitlab-markup (~> 1.6.4) gitlab-markup (~> 1.6.4)
gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4) gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2) gon (~> 6.2)
......
...@@ -329,6 +329,8 @@ GEM ...@@ -329,6 +329,8 @@ GEM
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab-license (1.0.0) gitlab-license (1.0.0)
gitlab-markup (1.6.4) gitlab-markup (1.6.4)
gitlab-sidekiq-fetcher (0.3.0)
sidekiq (~> 5)
gitlab-styles (2.4.1) gitlab-styles (2.4.1)
rubocop (~> 0.54.0) rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0) rubocop-gitlab-security (~> 0.1.0)
...@@ -1075,6 +1077,7 @@ DEPENDENCIES ...@@ -1075,6 +1077,7 @@ DEPENDENCIES
gitlab-gollum-lib (~> 4.2) gitlab-gollum-lib (~> 4.2)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
gitlab-markup (~> 1.6.4) gitlab-markup (~> 1.6.4)
gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4) gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2) gon (~> 6.2)
......
...@@ -5,6 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue'; ...@@ -5,6 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CIIcon from '~/vue_shared/components/ci_icon.vue'; import CIIcon from '~/vue_shared/components/ci_icon.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
/** /**
* CommitItem * CommitItem
...@@ -29,6 +30,7 @@ export default { ...@@ -29,6 +30,7 @@ export default {
ClipboardButton, ClipboardButton,
CIIcon, CIIcon,
TimeAgoTooltip, TimeAgoTooltip,
CommitPipelineStatus,
}, },
props: { props: {
commit: { commit: {
...@@ -102,6 +104,14 @@ export default { ...@@ -102,6 +104,14 @@ export default {
></pre> ></pre>
</div> </div>
<div class="commit-actions flex-row d-none d-sm-flex"> <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="commit-sha-group">
<div <div
class="label label-monospace" class="label label-monospace"
......
...@@ -469,7 +469,9 @@ export default { ...@@ -469,7 +469,9 @@ export default {
class="gl-responsive-table-row" class="gl-responsive-table-row"
role="row"> role="row">
<div <div
class="table-section section-wrap section-15" v-tooltip
:title="model.name"
class="table-section section-wrap section-15 text-truncate"
role="gridcell" role="gridcell"
> >
<div <div
...@@ -500,9 +502,7 @@ export default { ...@@ -500,9 +502,7 @@ export default {
v-if="!model.isFolder" v-if="!model.isFolder"
class="environment-name table-mobile-content"> class="environment-name table-mobile-content">
<a <a
v-tooltip
:href="environmentPath" :href="environmentPath"
:title="model.name"
> >
{{ model.name }} {{ model.name }}
</a> </a>
......
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
<button <button
:class="{ disabled: formState.updateLoading || !isSubmitEnabled }" :class="{ disabled: formState.updateLoading || !isSubmitEnabled }"
: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" type="submit"
@click.prevent="updateIssuable"> @click.prevent="updateIssuable">
Save changes Save changes
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
v-if="shouldShowDeleteButton" v-if="shouldShowDeleteButton"
:class="{ disabled: deleteLoading }" :class="{ disabled: deleteLoading }"
: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" type="button"
@click="deleteIssuable"> @click="deleteIssuable">
Delete Delete
......
...@@ -61,7 +61,8 @@ ...@@ -61,7 +61,8 @@
ref="textarea" ref="textarea"
slot="textarea" slot="textarea"
v-model="formState.description" 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" data-supports-quick-actions="false"
aria-label="Description" aria-label="Description"
placeholder="Write a comment or drag your files here…" placeholder="Write a comment or drag your files here…"
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<input <input
id="issuable-title" id="issuable-title"
v-model="formState.title" v-model="formState.title"
class="form-control" class="form-control qa-title-input"
type="text" type="text"
placeholder="Title" placeholder="Title"
aria-label="Title" aria-label="Title"
......
...@@ -79,7 +79,8 @@ export default { ...@@ -79,7 +79,8 @@ export default {
v-if="showInlineEditButton && canUpdate" v-if="showInlineEditButton && canUpdate"
v-tooltip v-tooltip
type="button" 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" title="Edit title and description"
data-placement="bottom" data-placement="bottom"
data-container="body" data-container="body"
......
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
import { mapGetters, mapState } from 'vuex'; import { mapGetters, mapState } from 'vuex';
import CiHeader from '~/vue_shared/components/header_ci_component.vue'; import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import Callout from '~/vue_shared/components/callout.vue'; import Callout from '~/vue_shared/components/callout.vue';
<<<<<<< HEAD
// ee-only start // ee-only start
import SharedRunner from 'ee/jobs/components/shared_runner_limit_block.vue'; import SharedRunner from 'ee/jobs/components/shared_runner_limit_block.vue';
// ee-only end // ee-only end
=======
>>>>>>> upstream/master
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import EnvironmentsBlock from './environments_block.vue'; import EnvironmentsBlock from './environments_block.vue';
import ErasedBlock from './erased_block.vue'; import ErasedBlock from './erased_block.vue';
...@@ -37,7 +40,10 @@ ...@@ -37,7 +40,10 @@
'jobHasStarted', 'jobHasStarted',
'hasEnvironment', 'hasEnvironment',
'isJobStuck', 'isJobStuck',
<<<<<<< HEAD
'shouldRenderSharedRunnerLimitWarning', 'shouldRenderSharedRunnerLimitWarning',
=======
>>>>>>> upstream/master
'hasTrace', 'hasTrace',
'emptyStateIllustration', 'emptyStateIllustration',
]), ]),
......
...@@ -48,11 +48,14 @@ export const emptyStateIllustration = state => ...@@ -48,11 +48,14 @@ export const emptyStateIllustration = state =>
export const isJobStuck = state => export const isJobStuck = state =>
state.job.status.group === 'pending' && state.job.status.group === 'pending' &&
(!_.isEmpty(state.job.runners) && state.job.runners.available === false); (!_.isEmpty(state.job.runners) && state.job.runners.available === false);
<<<<<<< HEAD
// ee-only start // ee-only start
export const shouldRenderSharedRunnerLimitWarning = state => export const shouldRenderSharedRunnerLimitWarning = state =>
state.job.runners && state.job.runners.quota && state.job.runners.quota.used; state.job.runners && state.job.runners.quota && state.job.runners.quota.used;
// ee-only end // ee-only end
=======
>>>>>>> upstream/master
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -365,7 +365,7 @@ Please check your network connection and try again.`; ...@@ -365,7 +365,7 @@ Please check your network connection and try again.`;
:disabled="isSubmitting" :disabled="isSubmitting"
name="note[note]" name="note[note]"
class="note-textarea js-vue-comment-form js-note-text 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" data-supports-quick-actions="true"
aria-label="Description" aria-label="Description"
placeholder="Write a comment or drag your files here…" placeholder="Write a comment or drag your files here…"
...@@ -380,7 +380,8 @@ js-gfm-input js-autosize markdown-area js-vue-textarea" ...@@ -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"> append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown">
<button <button
:disabled="isSubmitButtonDisabled" :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" type="submit"
@click.prevent="handleSave()"> @click.prevent="handleSave()">
{{ __(commentButtonTitle) }} {{ __(commentButtonTitle) }}
......
...@@ -79,10 +79,13 @@ export default class Todos { ...@@ -79,10 +79,13 @@ export default class Todos {
.then(({ data }) => { .then(({ data }) => {
this.updateRowState(target); this.updateRowState(target);
this.updateBadges(data); 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 row = target.closest('li');
const restoreBtn = row.querySelector('.js-undo-todo'); const restoreBtn = row.querySelector('.js-undo-todo');
const doneBtn = row.querySelector('.js-done-todo'); const doneBtn = row.querySelector('.js-done-todo');
...@@ -91,7 +94,10 @@ export default class Todos { ...@@ -91,7 +94,10 @@ export default class Todos {
target.removeAttribute('disabled'); target.removeAttribute('disabled');
target.classList.remove('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'); row.classList.add('done-reversible');
restoreBtn.classList.remove('hidden'); restoreBtn.classList.remove('hidden');
} else if (target === restoreBtn) { } else if (target === restoreBtn) {
......
...@@ -592,7 +592,7 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -592,7 +592,7 @@ function UsersSelect(currentUser, els, options = {}) {
if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) { if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
var trimmed = query.term.trim(); var trimmed = query.term.trim();
emailUser = { emailUser = {
name: "Invite \"" + query.term + "\" by email", name: "Invite \"" + trimmed + "\" by email",
username: trimmed, username: trimmed,
id: trimmed, id: trimmed,
invite: true invite: true
......
...@@ -20,20 +20,24 @@ ...@@ -20,20 +20,24 @@
display: inline-block; display: inline-block;
overflow-x: auto; overflow-x: auto;
border: 0; border: 0;
border-color: $gray-100; border-color: $gl-gray-100;
@supports (width: fit-content) { @supports (width: fit-content) {
display: block; display: block;
width: fit-content; width: fit-content;
} }
tbody {
background-color: $white-light;
}
tr { tr {
th { th {
border-bottom: solid 2px $gray-100; border-bottom: solid 2px $gl-gray-100;
} }
td { td {
border-color: $gray-100; border-color: $gl-gray-100;
} }
} }
} }
......
...@@ -18,3 +18,4 @@ $success: $green-500; ...@@ -18,3 +18,4 @@ $success: $green-500;
$info: $blue-500; $info: $blue-500;
$warning: $orange-500; $warning: $orange-500;
$danger: $red-500; $danger: $red-500;
$zindex-modal-backdrop: 1040;
@import 'framework/variables'; @import 'framework/variables';
@import 'framework/variables_overrides';
@import 'peek/views/rblineprof'; @import 'peek/views/rblineprof';
#js-peek { #js-peek {
...@@ -6,7 +7,7 @@ ...@@ -6,7 +7,7 @@
left: 0; left: 0;
top: 0; top: 0;
width: 100%; width: 100%;
z-index: 1039; z-index: #{$zindex-modal-backdrop + 1};
height: $performance-bar-height; height: $performance-bar-height;
background: $black; background: $black;
......
...@@ -25,7 +25,13 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -25,7 +25,13 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
@diffs.write_cache @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 end
def define_diff_vars def define_diff_vars
......
...@@ -428,6 +428,10 @@ class IssuableFinder ...@@ -428,6 +428,10 @@ class IssuableFinder
params[:milestone_title] == Milestone::Upcoming.name params[:milestone_title] == Milestone::Upcoming.name
end end
def filter_by_any_milestone?
params[:milestone_title] == Milestone::Any.title
end
def filter_by_started_milestone? def filter_by_started_milestone?
params[:milestone_title] == Milestone::Started.name params[:milestone_title] == Milestone::Started.name
end end
...@@ -437,6 +441,8 @@ class IssuableFinder ...@@ -437,6 +441,8 @@ class IssuableFinder
if milestones? if milestones?
if filter_by_no_milestone? if filter_by_no_milestone?
items = items.left_joins_milestones.where(milestone_id: [-1, nil]) items = items.left_joins_milestones.where(milestone_id: [-1, nil])
elsif filter_by_any_milestone?
items = items.any_milestone
elsif filter_by_upcoming_milestone? elsif filter_by_upcoming_milestone?
upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items)) upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items))
items = items.left_joins_milestones.where(milestone_id: upcoming_ids) items = items.left_joins_milestones.where(milestone_id: upcoming_ids)
......
...@@ -129,7 +129,7 @@ class LabelsFinder < UnionFinder ...@@ -129,7 +129,7 @@ class LabelsFinder < UnionFinder
end end
def project? def project?
params[:project_id].present? params[:project].present? || params[:project_id].present?
end end
def projects? def projects?
...@@ -152,7 +152,7 @@ class LabelsFinder < UnionFinder ...@@ -152,7 +152,7 @@ class LabelsFinder < UnionFinder
return @project if defined?(@project) return @project if defined?(@project)
if 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) @project = nil unless authorized_to_read_labels?(@project)
else else
@project = nil @project = nil
......
...@@ -319,7 +319,11 @@ class Commit ...@@ -319,7 +319,11 @@ class Commit
def status(ref = nil) def status(ref = nil)
return @statuses[ref] if @statuses.key?(ref) 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 end
def set_status_for_ref(ref, status) def set_status_for_ref(ref, status)
......
...@@ -78,6 +78,7 @@ module Issuable ...@@ -78,6 +78,7 @@ module Issuable
scope :recent, -> { reorder(id: :desc) } scope :recent, -> { reorder(id: :desc) }
scope :of_projects, ->(ids) { where(project_id: ids) } scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_milestones, ->(ids) { where(milestone_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 :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
scope :opened, -> { with_state(:opened) } scope :opened, -> { with_state(:opened) }
scope :only_opened, -> { with_state(:opened) } scope :only_opened, -> { with_state(:opened) }
......
...@@ -31,9 +31,11 @@ module Subscribable ...@@ -31,9 +31,11 @@ module Subscribable
end end
def subscribers(project) def subscribers(project)
subscriptions_available(project) relation = subscriptions_available(project)
.where(subscribed: true) .where(subscribed: true)
.map(&:user) .select(:user_id)
User.where(id: relation)
end end
def toggle_subscription(user, project = nil) def toggle_subscription(user, project = nil)
......
...@@ -391,7 +391,11 @@ class ProjectPolicy < BasePolicy ...@@ -391,7 +391,11 @@ class ProjectPolicy < BasePolicy
greedy_load_subject ||= !@user.persisted? greedy_load_subject ||= !@user.persisted?
if greedy_load_subject 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 else
# otherwise we just make a specific query for # otherwise we just make a specific query for
# this particular user. # this particular user.
......
# frozen_string_literal: true # frozen_string_literal: true
class BuildDetailsEntity < JobEntity class BuildDetailsEntity < JobEntity
<<<<<<< HEAD
prepend ::EE::BuildDetailEntity prepend ::EE::BuildDetailEntity
=======
>>>>>>> upstream/master
expose :coverage, :erased_at, :duration expose :coverage, :erased_at, :duration
expose :tag_list, as: :tags expose :tag_list, as: :tags
expose :has_trace?, as: :has_trace expose :has_trace?, as: :has_trace
......
...@@ -25,4 +25,25 @@ class CommitEntity < API::Entities::Commit ...@@ -25,4 +25,25 @@ class CommitEntity < API::Entities::Commit
expose :title_html, if: { type: :full } do |commit| expose :title_html, if: { type: :full } do |commit|
markdown_field(commit, :title) markdown_field(commit, :title)
end 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 end
...@@ -18,7 +18,9 @@ class DiffsEntity < Grape::Entity ...@@ -18,7 +18,9 @@ class DiffsEntity < Grape::Entity
expose :commit do |diffs, options| expose :commit do |diffs, options|
CommitEntity.represent options[:commit], options.merge( CommitEntity.represent options[:commit], options.merge(
type: :full, 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 end
......
- add_to_breadcrumbs "Projects", admin_projects_path - add_to_breadcrumbs "Projects", admin_projects_path
- breadcrumb_title @project.full_name - breadcrumb_title @project.full_name
- page_title @project.full_name, "Projects" - page_title @project.full_name, "Projects"
- @content_class = "admin-projects"
%h3.page-title %h3.page-title
Project: #{@project.full_name} Project: #{@project.full_name}
= link_to edit_project_path(@project), class: "btn btn-nr float-right" do = link_to edit_project_path(@project), class: "btn btn-nr float-right" do
......
%p.slead %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 time each to regain access to your account. Please save them in a safe place, or you
%b will %b will
lose access to your account. lose access to your account.
......
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
.row.prepend-top-default .row.prepend-top-default
.col-lg-4 .col-lg-4
%h4.prepend-top-0 %h4.prepend-top-0
Register Two-Factor Authentication App Register Two-Factor Authenticator
%p %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 .col-lg-8
- if current_user.two_factor_otp_enabled? - if current_user.two_factor_otp_enabled?
%p %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 %p
If you lose your recovery codes you can generate new ones, invalidating all previous codes. If you lose your recovery codes you can generate new ones, invalidating all previous codes.
%div %div
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
.form-group .form-group
.form-check .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' = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
.form-text.text-muted .form-text.text-muted
= s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') = 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| ...@@ -40,6 +40,10 @@ Sidekiq.configure_server do |config|
ActiveRecord::Base.clear_all_connections! ActiveRecord::Base.clear_all_connections!
end end
if Feature.enabled?(:gitlab_sidekiq_reliable_fetcher)
Sidekiq::ReliableFetcher.setup_reliable_fetch!(config)
end
# Sidekiq-cron: load recurring jobs from gitlab.yml # Sidekiq-cron: load recurring jobs from gitlab.yml
# UGLY Hack to get nested hash from settingslogic # UGLY Hack to get nested hash from settingslogic
cron_jobs = JSON.parse(Gitlab.config.cron_jobs.to_json) cron_jobs = JSON.parse(Gitlab.config.cron_jobs.to_json)
...@@ -61,10 +65,10 @@ Sidekiq.configure_server do |config| ...@@ -61,10 +65,10 @@ Sidekiq.configure_server do |config|
Gitlab::SidekiqVersioning.install! Gitlab::SidekiqVersioning.install!
config = Gitlab::Database.config || db_config = Gitlab::Database.config ||
Rails.application.config.database_configuration[Rails.env] Rails.application.config.database_configuration[Rails.env]
config['pool'] = Sidekiq.options[:concurrency] db_config['pool'] = Sidekiq.options[:concurrency]
ActiveRecord::Base.establish_connection(config) 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')}") Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
# EE only # EE only
......
...@@ -37,7 +37,7 @@ GET /issues?my_reaction_emoji=star ...@@ -37,7 +37,7 @@ GET /issues?my_reaction_emoji=star
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `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 | | `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)_ | | `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)_ | | `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)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
......
...@@ -117,8 +117,12 @@ Parameters: ...@@ -117,8 +117,12 @@ Parameters:
"human_time_estimate": null, "human_time_estimate": null,
"human_total_time_spent": null "human_total_time_spent": null
}, },
<<<<<<< HEAD
"squash": false, "squash": false,
"approvals_before_merge": null "approvals_before_merge": null
=======
"squash": false
>>>>>>> upstream/master
} }
] ]
``` ```
...@@ -241,8 +245,12 @@ Parameters: ...@@ -241,8 +245,12 @@ Parameters:
"human_time_estimate": null, "human_time_estimate": null,
"human_total_time_spent": null "human_total_time_spent": null
}, },
<<<<<<< HEAD
"squash": false, "squash": false,
"approvals_before_merge": null "approvals_before_merge": null
=======
"squash": false
>>>>>>> upstream/master
} }
] ]
``` ```
...@@ -353,8 +361,12 @@ Parameters: ...@@ -353,8 +361,12 @@ Parameters:
"human_time_estimate": null, "human_time_estimate": null,
"human_total_time_spent": null "human_total_time_spent": null
}, },
<<<<<<< HEAD
"squash": false, "squash": false,
"approvals_before_merge": null "approvals_before_merge": null
=======
"squash": false
>>>>>>> upstream/master
} }
] ]
``` ```
...@@ -797,8 +809,12 @@ order for it to take effect: ...@@ -797,8 +809,12 @@ order for it to take effect:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
}, },
<<<<<<< HEAD
"diverged_commits_count": 2, "diverged_commits_count": 2,
"approvals_before_merge": null "approvals_before_merge": null
=======
"diverged_commits_count": 2
>>>>>>> upstream/master
} }
``` ```
...@@ -925,8 +941,12 @@ Must include at least one non-required attribute from above. ...@@ -925,8 +941,12 @@ Must include at least one non-required attribute from above.
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
}, },
<<<<<<< HEAD
"diverged_commits_count": 2, "diverged_commits_count": 2,
"approvals_before_merge": null "approvals_before_merge": null
=======
"diverged_commits_count": 2
>>>>>>> upstream/master
} }
``` ```
...@@ -1069,8 +1089,12 @@ Parameters: ...@@ -1069,8 +1089,12 @@ Parameters:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
}, },
<<<<<<< HEAD
"diverged_commits_count": 2, "diverged_commits_count": 2,
"approvals_before_merge": null "approvals_before_merge": null
=======
"diverged_commits_count": 2
>>>>>>> upstream/master
} }
``` ```
...@@ -1185,8 +1209,12 @@ Parameters: ...@@ -1185,8 +1209,12 @@ Parameters:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
}, },
<<<<<<< HEAD
"diverged_commits_count": 2, "diverged_commits_count": 2,
"approvals_before_merge": null "approvals_before_merge": null
=======
"diverged_commits_count": 2
>>>>>>> upstream/master
} }
``` ```
...@@ -1385,8 +1413,12 @@ Example response: ...@@ -1385,8 +1413,12 @@ Example response:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
}, },
<<<<<<< HEAD
"diverged_commits_count": 2, "diverged_commits_count": 2,
"approvals_before_merge": null "approvals_before_merge": null
=======
"diverged_commits_count": 2
>>>>>>> upstream/master
} }
``` ```
...@@ -1507,8 +1539,12 @@ Example response: ...@@ -1507,8 +1539,12 @@ Example response:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
}, },
<<<<<<< HEAD
"diverged_commits_count": 2, "diverged_commits_count": 2,
"approvals_before_merge": null "approvals_before_merge": null
=======
"diverged_commits_count": 2
>>>>>>> upstream/master
} }
``` ```
......
...@@ -563,7 +563,7 @@ Parameters: ...@@ -563,7 +563,7 @@ Parameters:
## List SSH keys for user ## 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 GET /users/:id/keys
......
...@@ -60,7 +60,7 @@ people. ...@@ -60,7 +60,7 @@ people.
The current team labels are: The current team labels are:
- ~Configuration - ~Configure
- ~"CI/CD" - ~"CI/CD"
- ~Create - ~Create
- ~Distribution - ~Distribution
......
...@@ -2,18 +2,18 @@ ...@@ -2,18 +2,18 @@
Two-factor Authentication (2FA) provides an additional level of security to your Two-factor Authentication (2FA) provides an additional level of security to your
GitLab account. Once enabled, in addition to supplying your username and 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 password to login, you'll be prompted for a code generated by your one time password
your phone. 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 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 ## Overview
> **Note:** > **Note:**
When you enable 2FA, don't forget to back up your recovery codes. 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 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 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. a button on it), and it will perform secure authentication on your behalf.
...@@ -24,10 +24,10 @@ from other browsers. ...@@ -24,10 +24,10 @@ from other browsers.
## Enabling 2FA ## 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. or a U2F device.
### Enable 2FA via mobile application ### Enable 2FA via one time password authenticator
**In GitLab:** **In GitLab:**
...@@ -82,7 +82,7 @@ Click on **Register U2F Device** to complete the process. ...@@ -82,7 +82,7 @@ Click on **Register U2F Device** to complete the process.
> **Note:** > **Note:**
Recovery codes are not generated for U2F devices. 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 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 storage in a safe place. **Each code can be used only once** to log in to your
account. account.
...@@ -98,7 +98,7 @@ be presented with a second prompt, depending on which type of 2FA you've enabled ...@@ -98,7 +98,7 @@ be presented with a second prompt, depending on which type of 2FA you've enabled
### Log in via mobile application ### 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) ![Two-Factor Authentication on sign in via OTP](img/2fa_auth.png)
......
...@@ -216,7 +216,7 @@ twice, which can lead to confusion during deployments. ...@@ -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) | | [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) | | [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) | | [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 ## Getting the external IP address
......
...@@ -29,7 +29,7 @@ directly in a filesystem level. ...@@ -29,7 +29,7 @@ directly in a filesystem level.
1. Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions you can 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). 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` 1. Unpack the downloaded SubGit zip archive to the `/opt` directory. The `subgit`
command will be available at `/opt/subgit-VERSION/bin/subgit`. command will be available at `/opt/subgit-VERSION/bin/subgit`.
...@@ -71,7 +71,7 @@ edit $GIT_REPO_PATH/subgit/config ...@@ -71,7 +71,7 @@ edit $GIT_REPO_PATH/subgit/config
``` ```
For more information regarding the SubGit configuration options, refer to 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 ### Initial translation
...@@ -97,7 +97,7 @@ subgit import $GIT_REPO_PATH ...@@ -97,7 +97,7 @@ subgit import $GIT_REPO_PATH
### SubGit licensing ### SubGit licensing
Running SubGit in a mirror mode requires a 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. source, academic and startup projects.
We're currently working on deeper GitLab/SubGit integration. You may track our We're currently working on deeper GitLab/SubGit integration. You may track our
...@@ -179,5 +179,6 @@ git push --tags origin ...@@ -179,5 +179,6 @@ git push --tags origin
``` ```
## Contribute to this guide ## Contribute to this guide
We welcome all contributions that would expand this guide with instructions on We welcome all contributions that would expand this guide with instructions on
how to migrate from SVN and other version control systems. how to migrate from SVN and other version control systems.
...@@ -262,7 +262,7 @@ module API ...@@ -262,7 +262,7 @@ module API
end end
# rubocop: enable CodeReuse/ActiveRecord # 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 success Entities::SSHKey
end end
params do params do
...@@ -271,10 +271,8 @@ module API ...@@ -271,10 +271,8 @@ module API
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get ':id/keys' do get ':id/keys' do
authenticated_as_admin!
user = User.find_by(id: params[:id]) 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 present paginate(user.keys), with: Entities::SSHKey
end end
......
...@@ -13,6 +13,7 @@ module Banzai ...@@ -13,6 +13,7 @@ module Banzai
# Returns a Project, or nil if the reference can't be found # Returns a Project, or nil if the reference can't be found
def parent_from_ref(ref) def parent_from_ref(ref)
return context[:project] || context[:group] unless ref return context[:project] || context[:group] unless ref
return context[:project] if context[:project]&.full_path == ref
Project.find_by_full_path(ref) Project.find_by_full_path(ref)
end end
......
...@@ -48,7 +48,7 @@ module Banzai ...@@ -48,7 +48,7 @@ module Banzai
include_ancestor_groups: true, include_ancestor_groups: true,
only_group_labels: true } only_group_labels: true }
else else
{ project_id: parent.id, { project: parent,
include_ancestor_groups: true } include_ancestor_groups: true }
end end
......
...@@ -689,9 +689,6 @@ rollout 100%: ...@@ -689,9 +689,6 @@ rollout 100%:
helm version --client helm version --client
tiller -version 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" 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 chmod +x /usr/bin/kubectl
kubectl version --client kubectl version --client
...@@ -800,9 +797,9 @@ rollout 100%: ...@@ -800,9 +797,9 @@ rollout 100%:
function initialize_tiller() { function initialize_tiller() {
echo "Checking Tiller..." echo "Checking Tiller..."
helm local start
helm local status
export HELM_HOST=":44134" 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 if ! helm version --debug; then
echo "Failed to init Tiller." echo "Failed to init Tiller."
......
...@@ -220,7 +220,7 @@ module Gitlab ...@@ -220,7 +220,7 @@ module Gitlab
result result
end end
SERVER_FEATURE_FLAGS = %w[gogit_findcommit git_v2].freeze SERVER_FEATURE_FLAGS = %w[gogit_findcommit].freeze
def self.server_feature_flags def self.server_feature_flags
SERVER_FEATURE_FLAGS.map do |f| SERVER_FEATURE_FLAGS.map do |f|
......
...@@ -8674,9 +8674,12 @@ msgstr "" ...@@ -8674,9 +8674,12 @@ msgstr ""
msgid "You do not have any subscriptions yet" msgid "You do not have any subscriptions yet"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "You do not have the correct permissions to override the settings from the LDAP group sync." msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
msgstr "" msgstr ""
=======
>>>>>>> upstream/master
msgid "You don't have any applications" msgid "You don't have any applications"
msgstr "" msgstr ""
......
...@@ -257,6 +257,9 @@ module QA ...@@ -257,6 +257,9 @@ module QA
autoload :Dropzone, 'qa/page/component/dropzone' autoload :Dropzone, 'qa/page/component/dropzone'
autoload :GroupsFilter, 'qa/page/component/groups_filter' autoload :GroupsFilter, 'qa/page/component/groups_filter'
autoload :Select2, 'qa/page/component/select2' autoload :Select2, 'qa/page/component/select2'
module Issuable
autoload :Common, 'qa/page/component/issuable/common'
end
end end
end end
......
...@@ -31,6 +31,7 @@ module QA ...@@ -31,6 +31,7 @@ module QA
page.set_api_url(@cluster.api_url) page.set_api_url(@cluster.api_url)
page.set_ca_certificate(@cluster.ca_certificate) page.set_ca_certificate(@cluster.ca_certificate)
page.set_token(@cluster.token) page.set_token(@cluster.token)
page.check_rbac! if @cluster.rbac
page.add_cluster! page.add_cluster!
end 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 ...@@ -5,6 +5,8 @@ module QA
module Project module Project
module Issue module Issue
class Show < Page::Base class Show < Page::Base
include Page::Component::Issuable::Common
view 'app/views/projects/issues/show.html.haml' do view 'app/views/projects/issues/show.html.haml' do
element :issue_details, '.issue-details' element :issue_details, '.issue-details'
element :title, '.title' element :title, '.title'
......
...@@ -10,6 +10,7 @@ module QA ...@@ -10,6 +10,7 @@ module QA
element :ca_certificate, 'text_area :ca_cert' element :ca_certificate, 'text_area :ca_cert'
element :token, 'text_field :token' element :token, 'text_field :token'
element :add_cluster_button, "submit s_('ClusterIntegration|Add Kubernetes cluster')" element :add_cluster_button, "submit s_('ClusterIntegration|Add Kubernetes cluster')"
element :rbac_checkbox
end end
def set_cluster_name(name) def set_cluster_name(name)
...@@ -31,6 +32,10 @@ module QA ...@@ -31,6 +32,10 @@ module QA
def add_cluster! def add_cluster!
click_on 'Add Kubernetes cluster' click_on 'Add Kubernetes cluster'
end end
def check_rbac!
check_element :rbac_checkbox
end
end end
end end
end end
......
require 'securerandom' require 'securerandom'
require 'mkmf' require 'mkmf'
require 'pathname'
module QA module QA
module Service module Service
class KubernetesCluster class KubernetesCluster
include Service::Shellout 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 def cluster_name
@cluster_name ||= "qa-cluster-#{SecureRandom.hex(4)}-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}" @cluster_name ||= "qa-cluster-#{SecureRandom.hex(4)}-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}"
...@@ -19,7 +24,7 @@ module QA ...@@ -19,7 +24,7 @@ module QA
shell <<~CMD.tr("\n", ' ') shell <<~CMD.tr("\n", ' ')
gcloud container clusters gcloud container clusters
create #{cluster_name} create #{cluster_name}
--enable-legacy-authorization #{auth_options}
--zone #{Runtime::Env.gcloud_zone} --zone #{Runtime::Env.gcloud_zone}
&& gcloud container clusters && gcloud container clusters
get-credentials get-credentials
...@@ -28,8 +33,21 @@ module QA ...@@ -28,8 +33,21 @@ module QA
CMD CMD
@api_url = `kubectl config view --minify -o jsonpath='{.clusters[].cluster.server}'` @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']}"`) if rbac
@token = Base64.decode64(`kubectl get secrets -o jsonpath='{.items[0].data.token}'`) 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 self
end end
...@@ -44,6 +62,42 @@ module QA ...@@ -44,6 +62,42 @@ module QA
private 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 def validate_dependencies
find_executable('gcloud') || raise("You must first install `gcloud` executable to run these tests.") 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.") find_executable('kubectl') || raise("You must first install `kubectl` executable to run these tests.")
......
...@@ -11,10 +11,12 @@ module QA ...@@ -11,10 +11,12 @@ module QA
# TODO, make it possible to use generic QA framework classes # TODO, make it possible to use generic QA framework classes
# as a library - gitlab-org/gitlab-qa#94 # as a library - gitlab-org/gitlab-qa#94
# #
def shell(command) def shell(command, stdin_data: nil)
puts "Executing `#{command}`" 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 } out.each { |line| puts line }
if wait.value.exited? && wait.value.exitstatus.nonzero? if wait.value.exited? && wait.value.exitstatus.nonzero?
......
...@@ -9,59 +9,63 @@ module QA ...@@ -9,59 +9,63 @@ module QA
@cluster&.remove! @cluster&.remove!
end end
it 'user creates a new project and runs auto devops' do [true, false].each do |rbac|
Runtime::Browser.visit(:gitlab, Page::Main::Login) context "when rbac is #{rbac ? 'enabled' : 'disabled'}" do
Page::Main::Login.act { sign_in_using_credentials } 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| project = Factory::Resource::Project.fabricate! do |p|
p.name = 'project-with-autodevops' p.name = 'project-with-autodevops'
p.description = 'Project with Auto Devops' p.description = 'Project with Auto Devops'
end end
# Disable code_quality check in Auto DevOps pipeline as it takes # Disable code_quality check in Auto DevOps pipeline as it takes
# too long and times out the test # too long and times out the test
Factory::Resource::SecretVariable.fabricate! do |resource| Factory::Resource::SecretVariable.fabricate! do |resource|
resource.project = project resource.project = project
resource.key = 'CODE_QUALITY_DISABLED' resource.key = 'CODE_QUALITY_DISABLED'
resource.value = '1' resource.value = '1'
end end
# Create Auto Devops compatible repo # Create Auto Devops compatible repo
Factory::Repository::ProjectPush.fabricate! do |push| Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project push.project = project
push.directory = Pathname push.directory = Pathname
.new(__dir__) .new(__dir__)
.join('../../../../../fixtures/auto_devops_rack') .join('../../../../../fixtures/auto_devops_rack')
push.commit_message = 'Create Auto DevOps compatible rack application' push.commit_message = 'Create Auto DevOps compatible rack application'
end end
Page::Project::Show.act { wait_for_push } Page::Project::Show.act { wait_for_push }
# Create and connect K8s cluster # Create and connect K8s cluster
@cluster = Service::KubernetesCluster.new.create! @cluster = Service::KubernetesCluster.new(rbac: rbac).create!
kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster| kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster|
cluster.project = project cluster.project = project
cluster.cluster = @cluster cluster.cluster = @cluster
cluster.install_helm_tiller = true cluster.install_helm_tiller = true
cluster.install_ingress = true cluster.install_ingress = true
cluster.install_prometheus = true cluster.install_prometheus = true
cluster.install_runner = true cluster.install_runner = true
end end
project.visit! project.visit!
Page::Menu::Side.act { click_ci_cd_settings } Page::Menu::Side.act { click_ci_cd_settings }
Page::Project::Settings::CICD.perform do |p| Page::Project::Settings::CICD.perform do |p|
p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io") p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io")
end end
project.visit! project.visit!
Page::Menu::Side.act { click_ci_cd_pipelines } Page::Menu::Side.act { click_ci_cd_pipelines }
Page::Project::Pipeline::Index.act { go_to_latest_pipeline } Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to have_build('build', status: :success, wait: 600) 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('test', status: :success, wait: 600)
expect(pipeline).to have_build('production', status: :success, wait: 1200) expect(pipeline).to have_build('production', status: :success, wait: 1200)
end
end
end end
end end
end end
......
...@@ -637,6 +637,18 @@ describe Projects::IssuesController do ...@@ -637,6 +637,18 @@ describe Projects::IssuesController do
project_id: project, project_id: project,
id: id id: id
end 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 end
describe 'GET #realtime_changes' do describe 'GET #realtime_changes' do
......
...@@ -42,7 +42,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js 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 it 'allows registering a new device with a name' do
visit profile_account_path visit profile_account_path
manage_two_factor_authentication 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 u2f_device = register_u2f_device
...@@ -70,7 +70,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do ...@@ -70,7 +70,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
it 'allows deleting a device' do it 'allows deleting a device' do
visit profile_account_path visit profile_account_path
manage_two_factor_authentication 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 first_u2f_device = register_u2f_device
second_u2f_device = register_u2f_device(name: 'My other device') second_u2f_device = register_u2f_device(name: 'My other device')
......
...@@ -171,6 +171,14 @@ describe IssuesFinder do ...@@ -171,6 +171,14 @@ describe IssuesFinder do
end end
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 context 'filtering by upcoming milestone' do
let(:params) { { milestone_title: Milestone::Upcoming.name } } let(:params) { { milestone_title: Milestone::Upcoming.name } }
......
...@@ -9,6 +9,8 @@ import getDiffWithCommit from '../mock_data/diff_with_commit'; ...@@ -9,6 +9,8 @@ import getDiffWithCommit from '../mock_data/diff_with_commit';
const TEST_AUTHOR_NAME = 'test'; const TEST_AUTHOR_NAME = 'test';
const TEST_AUTHOR_EMAIL = 'test+test@gitlab.com'; const TEST_AUTHOR_EMAIL = 'test+test@gitlab.com';
const TEST_AUTHOR_GRAVATAR = `${TEST_HOST}/avatar/test?s=36`; 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 getTitleElement = vm => vm.$el.querySelector('.commit-row-message.item-title');
const getDescElement = vm => vm.$el.querySelector('pre.commit-row-description'); const getDescElement = vm => vm.$el.querySelector('pre.commit-row-description');
...@@ -16,6 +18,7 @@ const getDescExpandElement = vm => vm.$el.querySelector('.commit-content .text-e ...@@ -16,6 +18,7 @@ const getDescExpandElement = vm => vm.$el.querySelector('.commit-content .text-e
const getShaElement = vm => vm.$el.querySelector('.commit-sha-group'); const getShaElement = vm => vm.$el.querySelector('.commit-sha-group');
const getAvatarElement = vm => vm.$el.querySelector('.user-avatar-link'); const getAvatarElement = vm => vm.$el.querySelector('.user-avatar-link');
const getCommitterElement = vm => vm.$el.querySelector('.commiter'); const getCommitterElement = vm => vm.$el.querySelector('.commiter');
const getCommitActionsElement = vm => vm.$el.querySelector('.commit-actions');
describe('diffs/components/commit_widget', () => { describe('diffs/components/commit_widget', () => {
const Component = Vue.extend(CommitItem); const Component = Vue.extend(CommitItem);
...@@ -125,4 +128,36 @@ describe('diffs/components/commit_widget', () => { ...@@ -125,4 +128,36 @@ describe('diffs/components/commit_widget', () => {
expect(nameElement).toHaveText(TEST_AUTHOR_NAME); 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 ', () => { ...@@ -184,6 +184,7 @@ describe('Job App ', () => {
}); });
}); });
<<<<<<< HEAD
// ee-only start // ee-only start
describe('runners limit - ee', () => { describe('runners limit - ee', () => {
describe('with used quota', () => { describe('with used quota', () => {
...@@ -217,6 +218,8 @@ describe('Job App ', () => { ...@@ -217,6 +218,8 @@ describe('Job App ', () => {
// ee-only end // ee-only end
=======
>>>>>>> upstream/master
describe('environments block', () => { describe('environments block', () => {
it('renders environment block when job has environment', () => { it('renders environment block when job has environment', () => {
store.dispatch( store.dispatch(
......
require 'spec_helper' require 'spec_helper'
describe Banzai::CrossProjectReference do 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 describe '#parent_from_ref' do
context 'when no project was referenced' do context 'when no project was referenced' do
it 'returns the project from context' do it 'returns the project from context' do
project = double 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
end end
...@@ -18,15 +23,15 @@ describe Banzai::CrossProjectReference do ...@@ -18,15 +23,15 @@ describe Banzai::CrossProjectReference do
it 'returns the group from context' do it 'returns the group from context' do
group = double 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
end end
context 'when referenced project does not exist' do context 'when referenced project does not exist' do
it 'returns nil' 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
end end
...@@ -37,7 +42,7 @@ describe Banzai::CrossProjectReference do ...@@ -37,7 +42,7 @@ describe Banzai::CrossProjectReference do
expect(Project).to receive(:find_by_full_path) expect(Project).to receive(:find_by_full_path)
.with('cross/reference').and_return(project2) .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 end
end end
......
...@@ -60,6 +60,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do ...@@ -60,6 +60,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
exp = act = "See #{commit1.id.reverse}...#{commit2.id}" 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(commit1.id.reverse)
allow(project.repository).to receive(:commit).with(commit2.id)
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
......
...@@ -56,6 +56,7 @@ describe API::Issues do ...@@ -56,6 +56,7 @@ describe API::Issues do
let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
let(:no_milestone_title) { URI.escape(Milestone::None.title) } let(:no_milestone_title) { URI.escape(Milestone::None.title) }
let(:any_milestone_title) { URI.escape(Milestone::Any.title) }
before(:all) do before(:all) do
project.add_reporter(user) project.add_reporter(user)
...@@ -815,6 +816,15 @@ describe API::Issues do ...@@ -815,6 +816,15 @@ describe API::Issues do
expect(json_response.first['id']).to eq(confidential_issue.id) expect(json_response.first['id']).to eq(confidential_issue.id)
end 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 it 'sorts by created_at descending by default' do
get api("#{base_url}/issues", user) get api("#{base_url}/issues", user)
......
...@@ -818,35 +818,25 @@ describe API::Users do ...@@ -818,35 +818,25 @@ describe API::Users do
end end
describe 'GET /user/:id/keys' do describe 'GET /user/:id/keys' do
before do it 'returns 404 for non-existing user' do
admin user_id = not_existing_user_id
end
context 'when unauthenticated' do get api("/users/#{user_id}/keys")
it 'returns authentication error' do
get api("/users/#{user.id}/keys")
expect(response).to have_gitlab_http_status(401)
end
end
context 'when authenticated' do expect(response).to have_gitlab_http_status(404)
it 'returns 404 for non-existing user' do expect(json_response['message']).to eq('404 User Not Found')
get api('/users/999999/keys', admin) 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 it 'returns array of ssh keys' do
user.keys << key user.keys << key
user.save 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 have_gitlab_http_status(200)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(key.title) expect(json_response.first['title']).to eq(key.title)
end
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe CommitEntity do describe CommitEntity do
SIGNATURE_HTML = 'TEST'.freeze
let(:entity) do let(:entity) do
described_class.new(commit, request: request) described_class.new(commit, request: request)
end end
let(:request) { double('request') } let(:request) { double('request') }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:commit) { project.commit } let(:commit) { project.commit }
...@@ -12,7 +13,11 @@ describe CommitEntity do ...@@ -12,7 +13,11 @@ describe CommitEntity do
subject { entity.as_json } subject { entity.as_json }
before do 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(:project).and_return(project)
allow(request).to receive(:render).and_return(render)
end end
context 'when commit author is a user' do context 'when commit author is a user' do
...@@ -61,7 +66,7 @@ describe CommitEntity do ...@@ -61,7 +66,7 @@ describe CommitEntity do
context 'when type is "full"' do context 'when type is "full"' do
let(:entity) 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 end
it 'exposes extra properties' do it 'exposes extra properties' do
...@@ -70,6 +75,25 @@ describe CommitEntity do ...@@ -70,6 +75,25 @@ describe CommitEntity do
expect(subject.fetch(:description_html)).not_to be_nil expect(subject.fetch(:description_html)).not_to be_nil
expect(subject.fetch(:title_html)).not_to be_nil expect(subject.fetch(:title_html)).not_to be_nil
end 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 end
context 'when commit_url_params is set' do context 'when commit_url_params is set' do
......
...@@ -10,27 +10,50 @@ describe NotificationRecipientService do ...@@ -10,27 +10,50 @@ describe NotificationRecipientService do
let(:issue) { create(:issue, project: project, assignees: [assignee]) } let(:issue) { create(:issue, project: project, assignees: [assignee]) }
let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id) } let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id) }
def create_watcher shared_examples 'no N+1 queries' do
watcher = create(:user) it 'avoids N+1 queries', :request_store do
create(:notification_setting, source: project, user: watcher, level: :watch) create_user
other_projects.each do |other_project| service.build_new_note_recipients(note)
create(:notification_setting, source: other_project, user: watcher, level: :watch)
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
end end
it 'avoids N+1 queries', :request_store do context 'when there are multiple watchers' do
create_watcher 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 context 'when there are multiple subscribers' do
service.build_new_note_recipients(note) def create_user
subscriber = create(:user)
issue.subscriptions.create(user: subscriber, project: project, subscribed: true)
end 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 end
end end
...@@ -36,6 +36,11 @@ require_relative '../ee/spec/spec_helper' ...@@ -36,6 +36,11 @@ require_relative '../ee/spec/spec_helper'
# Requires supporting ruby files with custom matchers and macros, etc, # Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories. # in spec/support/ and its subdirectories.
# Requires helpers, and shared contexts/examples first since they're used in other support files # 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/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_contexts/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f }
......
...@@ -13,6 +13,10 @@ auth: ...@@ -13,6 +13,10 @@ auth:
singleuser: singleuser:
defaultUrl: "/lab" defaultUrl: "/lab"
lifecycleHooks:
postStart:
exec:
command: ["git", "clone", "https://gitlab.com/gitlab-org/nurtch-demo.git", "DevOps-Runbook-Demo"]
ingress: ingress:
enabled: true enabled: true
......
...@@ -29,18 +29,7 @@ ...@@ -29,18 +29,7 @@
semver "^5.4.1" semver "^5.4.1"
source-map "^0.5.0" source-map "^0.5.0"
"@babel/generator@^7.0.0": "@babel/generator@^7.0.0", "@babel/generator@^7.1.2":
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":
version "7.1.2" version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.2.tgz#fde75c072575ce7abbd97322e8fef5bae67e4630" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.2.tgz#fde75c072575ce7abbd97322e8fef5bae67e4630"
integrity sha512-70A9HWLS/1RHk3Ck8tNHKxOoKQuSKocYgwDN85Pyl/RBduss6AKxUR7RIZ/lzduQMSYfWEM4DDBu6A+XGbkFig== integrity sha512-70A9HWLS/1RHk3Ck8tNHKxOoKQuSKocYgwDN85Pyl/RBduss6AKxUR7RIZ/lzduQMSYfWEM4DDBu6A+XGbkFig==
...@@ -224,12 +213,7 @@ ...@@ -224,12 +213,7 @@
esutils "^2.0.2" esutils "^2.0.2"
js-tokens "^4.0.0" js-tokens "^4.0.0"
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0": "@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.2":
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":
version "7.1.2" version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.2.tgz#85c5c47af6d244fab77bce6b9bd830e38c978409" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.2.tgz#85c5c47af6d244fab77bce6b9bd830e38c978409"
integrity sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ== integrity sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ==
...@@ -599,7 +583,7 @@ ...@@ -599,7 +583,7 @@
js-levenshtein "^1.1.3" js-levenshtein "^1.1.3"
semver "^5.3.0" 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" version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag== integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==
...@@ -608,15 +592,6 @@ ...@@ -608,15 +592,6 @@
"@babel/parser" "^7.1.2" "@babel/parser" "^7.1.2"
"@babel/types" "^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": "@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0":
version "7.1.0" version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.0.tgz#503ec6669387efd182c3888c4eec07bcc45d91b2" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.0.tgz#503ec6669387efd182c3888c4eec07bcc45d91b2"
...@@ -632,16 +607,7 @@ ...@@ -632,16 +607,7 @@
globals "^11.1.0" globals "^11.1.0"
lodash "^4.17.10" lodash "^4.17.10"
"@babel/types@^7.0.0": "@babel/types@^7.0.0", "@babel/types@^7.1.2":
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":
version "7.1.2" version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.2.tgz#183e7952cf6691628afdc2e2b90d03240bac80c0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.2.tgz#183e7952cf6691628afdc2e2b90d03240bac80c0"
integrity sha512-pb1I05sZEKiSlMUV9UReaqsCPUpgbHHHu2n1piRm7JkuBkm6QxcaIzKu6FMnMtCbih/cEYTR+RGYYC96Yk9HAg== integrity sha512-pb1I05sZEKiSlMUV9UReaqsCPUpgbHHHu2n1piRm7JkuBkm6QxcaIzKu6FMnMtCbih/cEYTR+RGYYC96Yk9HAg==
...@@ -1199,10 +1165,9 @@ babel-loader@^8.0.4: ...@@ -1199,10 +1165,9 @@ babel-loader@^8.0.4:
babel-messages@^6.23.0: babel-messages@^6.23.0:
version "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= integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=
dependencies: dependencies:
babel-plugin-syntax-object-rest-spread "^6.8.0"
babel-runtime "^6.22.0" babel-runtime "^6.22.0"
babel-plugin-istanbul@^5.1.0: babel-plugin-istanbul@^5.1.0:
...@@ -1219,11 +1184,6 @@ babel-plugin-rewire@^1.2.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" resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.2.0.tgz#822562d72ed2c84e47c0f95ee232c920853e9d89"
integrity sha512-JBZxczHw3tScS+djy6JPLMjblchGhLI89ep15H3SyjujIzlxo5nr6Yjo7AXotdeVczeBmWs0tF8PgJWDdgzAkQ== 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: babel-polyfill@6.23.0:
version "6.23.0" version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d" resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d"
...@@ -2685,12 +2645,12 @@ delegates@^1.0.0: ...@@ -2685,12 +2645,12 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
depd@1.1.1, depd@~1.1.1: depd@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k= integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=
depd@~1.1.2: depd@~1.1.1, depd@~1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
...@@ -5220,14 +5180,7 @@ lz-string@^1.4.4: ...@@ -5220,14 +5180,7 @@ lz-string@^1.4.4:
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=
make-dir@^1.0.0: make-dir@^1.0.0, make-dir@^1.3.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:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
...@@ -6620,12 +6573,7 @@ regenerate-unicode-properties@^7.0.0: ...@@ -6620,12 +6573,7 @@ regenerate-unicode-properties@^7.0.0:
dependencies: dependencies:
regenerate "^1.4.0" regenerate "^1.4.0"
regenerate@^1.2.1: regenerate@^1.2.1, regenerate@^1.4.0:
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:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
...@@ -6790,20 +6738,13 @@ resolve@1.1.x: ...@@ -6790,20 +6738,13 @@ resolve@1.1.x:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= 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" version "1.8.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==
dependencies: dependencies:
path-parse "^1.0.5" 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: responselike@1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" 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