Commit 49070883 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ee into zj-object-store-artifacts

parents 8aa22e8b cdf212d5
......@@ -75,7 +75,7 @@
</script>
<template>
<div class="col-lg-9 col-lg-offset-3 append-bottom-default deploy-keys">
<div class="append-bottom-default deploy-keys">
<loading-icon
v-if="isLoading && !hasKeys"
size="2"
......
......@@ -58,6 +58,7 @@ import UsersSelect from './users_select';
import RefSelectDropdown from './ref_select_dropdown';
import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob';
import initSettingsPanels from './settings_panels';
// EE-only
import ApproversSelect from './approvers_select';
......@@ -403,6 +404,8 @@ import AuditLogs from './audit_logs';
// Initialize Protected Tag Settings
new ProtectedTagCreate();
new ProtectedTagEditList();
// Initialize expandable settings panels
initSettingsPanels();
break;
case 'projects:ci_cd:show':
new gl.ProjectVariables();
......
function expandSection($section) {
$section.find('.js-settings-toggle').text('Close');
$section.find('.settings-content').addClass('expanded').off('scroll').scrollTop(0);
}
function closeSection($section) {
$section.find('.js-settings-toggle').text('Expand');
$section.find('.settings-content').removeClass('expanded').on('scroll', () => expandSection($section));
}
function toggleSection($section) {
const $content = $section.find('.settings-content');
$content.removeClass('no-animate');
if ($content.hasClass('expanded')) {
closeSection($section);
} else {
expandSection($section);
}
}
export default function initSettingsPanels() {
$('.settings').each((i, elm) => {
const $section = $(elm);
$section.on('click', '.js-settings-toggle', () => toggleSection($section));
$section.find('.settings-content:not(.expanded)').on('scroll', () => expandSection($section));
});
}
......@@ -117,6 +117,14 @@
}
}
.user-settings-pipeline-quota {
margin-top: $gl-padding;
.pipeline-quota {
border-top: none;
}
}
table.pipeline-project-metrics tr td {
padding: $gl-padding;
}
......
@keyframes expandMaxHeight {
0% {
max-height: 0;
}
99% {
max-height: 100vh;
}
100% {
max-height: none;
}
}
@keyframes collapseMaxHeight {
0% {
max-height: 100vh;
}
100% {
max-height: 0;
}
}
.settings {
overflow: hidden;
border-bottom: 1px solid $gray-darker;
&:first-of-type {
margin-top: 10px;
}
}
.settings-header {
position: relative;
padding: 20px 110px 10px 0;
h4 {
margin-top: 0;
}
button {
position: absolute;
top: 20px;
right: 6px;
min-width: 80px;
}
}
.settings-content {
max-height: 1px;
overflow-y: scroll;
margin-right: -20px;
padding-right: 130px;
animation: collapseMaxHeight 300ms ease-out;
&.expanded {
max-height: none;
overflow-y: visible;
animation: expandMaxHeight 300ms ease-in;
}
&.no-animate {
animation: none;
}
@media(max-width: $screen-sm-max) {
padding-right: 20px;
}
&::before {
content: ' ';
display: block;
height: 1px;
overflow: hidden;
margin-bottom: 4px;
}
&::after {
content: ' ';
display: block;
height: 1px;
overflow: hidden;
margin-top: 20px;
}
}
.settings-list-icon {
color: $gl-text-color-secondary;
font-size: $settings-icon-size;
......
class Profiles::PipelineQuotaController < Profiles::ApplicationController
def index
@namespace = current_user.namespace
@projects = @namespace.projects.with_shared_runners_limit_enabled.page(params[:page])
end
end
module Projects
class IssueLinksController < ApplicationController
before_action :authorize_admin_issue_link!, only: [:create, :destroy]
def index
render json: issues
end
def create
create_params = params.slice(:issue_references)
result = IssueLinks::CreateService.new(issue, current_user, create_params).execute
render json: { message: result[:message], issues: issues }, status: result[:http_status]
end
def destroy
issue_link = IssueLink.find(params[:id])
return render_403 unless can?(current_user, :admin_issue_link, issue_link.target.project)
IssueLinks::DestroyService.new(issue_link, current_user).execute
render json: { issues: issues }
end
private
def issues
IssueLinks::ListService.new(issue, current_user).execute
end
def authorize_admin_issue_link!
render_403 unless can?(current_user, :admin_issue_link, @project)
end
def issue
@issue ||=
IssuesFinder.new(current_user, project_id: @project.id)
.execute
.find_by!(iid: params[:issue_id])
end
end
end
......@@ -16,6 +16,7 @@
# label_name: string
# sort: string
# non_archived: boolean
# feature_availability_check: boolean (default: true)
# iids: integer[]
#
class IssuableFinder
......@@ -25,11 +26,15 @@ class IssuableFinder
ARRAY_PARAMS = { label_name: [], iids: [] }.freeze
VALID_PARAMS = (SCALAR_PARAMS + [ARRAY_PARAMS]).freeze
DEFAULT_PARAMS = {
feature_availability_check: true
}.freeze
attr_accessor :current_user, :params
def initialize(current_user, params = {})
@current_user = current_user
@params = params
@params = DEFAULT_PARAMS.merge(params).with_indifferent_access
end
def execute
......@@ -126,7 +131,20 @@ class IssuableFinder
ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute
end
@projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil)
# Querying through feature availability for an user is expensive
# (i.e. https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1719#note_31406525),
# and there are cases which a project level access check should be enough.
# In any case, `feature_availability_check` param should be kept with `true`
# by default.
#
projects =
if params[:feature_availability_check]
projects.with_feature_available_for_user(klass, current_user)
else
projects
end
@projects = projects.reorder(nil)
end
def search
......
module EE
module GroupsHelper
def group_shared_runner_limits_quota(group)
used = group.shared_runners_minutes.to_i
module NamespaceHelper
def namespace_shared_runner_limits_quota(namespace)
used = namespace.shared_runners_minutes.to_i
if group.shared_runners_minutes_limit_enabled?
limit = group.actual_shared_runners_minutes_limit
status = group.shared_runners_minutes_used? ? 'over_quota' : 'under_quota'
if namespace.shared_runners_minutes_limit_enabled?
limit = namespace.actual_shared_runners_minutes_limit
status = namespace.shared_runners_minutes_used? ? 'over_quota' : 'under_quota'
else
limit = 'Unlimited'
status = 'disabled'
......@@ -16,14 +16,14 @@ module EE
end
end
def group_shared_runner_limits_percent_used(group)
return 0 unless group.shared_runners_minutes_limit_enabled?
def namespace_shared_runner_limits_percent_used(namespace)
return 0 unless namespace.shared_runners_minutes_limit_enabled?
100 * group.shared_runners_minutes.to_i / group.actual_shared_runners_minutes_limit
100 * namespace.shared_runners_minutes.to_i / namespace.actual_shared_runners_minutes_limit
end
def group_shared_runner_limits_progress_bar(group)
percent = [group_shared_runner_limits_percent_used(group), 100].min
def namespace_shared_runner_limits_progress_bar(namespace)
percent = [namespace_shared_runner_limits_percent_used(namespace), 100].min
status =
if percent == 100
......
......@@ -52,7 +52,8 @@ module Ci
trigger: 3,
schedule: 4,
api: 5,
external: 6
external: 6,
pipeline: 7
}
state_machine :status, initial: :created do
......@@ -376,7 +377,8 @@ module Ci
def predefined_variables
[
{ key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
{ key: 'CI_PIPELINE_ID', value: id.to_s, public: true },
{ key: 'CI_PIPELINE_SOURCE', value: source.to_s, public: true }
]
end
......
class Geo::BaseRegistry < ActiveRecord::Base
self.abstract_class = true
if Gitlab::Geo.configured? && (Gitlab::Geo.secondary? || Rails.env.test?)
if Gitlab::Geo.secondary_role_enabled?
establish_connection Rails.configuration.geo_database
end
end
......@@ -21,9 +21,7 @@ class GeoNode < ActiveRecord::Base
validates :encrypted_secret_access_key, presence: true
after_initialize :build_dependents
after_save :refresh_bulk_notify_worker_status
after_save :expire_cache!
after_destroy :refresh_bulk_notify_worker_status
after_destroy :expire_cache!
before_validation :update_dependents_attributes
......@@ -130,10 +128,6 @@ class GeoNode < ActiveRecord::Base
{ protocol: schema, host: host, port: port, script_name: relative_url }
end
def refresh_bulk_notify_worker_status
Gitlab::Geo.configure_cron_jobs!
end
def build_dependents
unless persisted?
self.build_geo_node_key if geo_node_key.nil?
......
class IssueLink < ActiveRecord::Base
belongs_to :source, class_name: 'Issue'
belongs_to :target, class_name: 'Issue'
validates :source, presence: true
validates :target, presence: true
validates :source, uniqueness: { scope: :target_id, message: 'is already related' }
validate :check_self_relation
private
def check_self_relation
return unless source && target
if source == target
errors.add(:source, 'cannot be related to itself')
end
end
end
......@@ -7,12 +7,14 @@ class License < ActiveRecord::Base
AUDITOR_USER_FEATURE = 'GitLab_Auditor_User'.freeze
SERVICE_DESK_FEATURE = 'GitLab_ServiceDesk'.freeze
OBJECT_STORAGE_FEATURE = 'GitLab_ObjectStorage'.freeze
RELATED_ISSUES_FEATURE = 'RelatedIssues'.freeze
FEATURE_CODES = {
geo: GEO_FEATURE,
auditor_user: AUDITOR_USER_FEATURE,
service_desk: SERVICE_DESK_FEATURE,
object_storage: OBJECT_STORAGE_FEATURE,
related_issues: RELATED_ISSUES_FEATURE,
# Features that make sense to Namespace:
deploy_board: DEPLOY_BOARD_FEATURE,
file_lock: FILE_LOCK_FEATURE
......@@ -24,7 +26,7 @@ class License < ActiveRecord::Base
EARLY_ADOPTER_PLAN = 'early_adopter'.freeze
EES_FEATURES = [
# ..
{ RELATED_ISSUES_FEATURE => 1 }
].freeze
EEP_FEATURES = [
......
......@@ -3,7 +3,7 @@ class SystemNoteMetadata < ActiveRecord::Base
commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved opened closed merged
outdated
approved unapproved
approved unapproved relate unrelate
].freeze
validates :note, presence: true
......
......@@ -22,6 +22,11 @@ module EE
cannot! :create_note
cannot! :read_project
end
unless project.feature_available?(:related_issues)
cannot! :read_issue_link
cannot! :admin_issue_link
end
end
end
end
......@@ -55,6 +55,9 @@ class ProjectPolicy < BasePolicy
can! :read_pipeline_schedule
can! :read_build
end
# EE-only
can! :read_issue_link
end
def reporter_access!
......@@ -79,6 +82,9 @@ class ProjectPolicy < BasePolicy
if project.feature_available?(:deploy_board) || Rails.env.development?
can! :read_deploy_board
end
# EE-only
can! :admin_issue_link
end
# Permissions given when an user is team member of a project
......@@ -321,5 +327,8 @@ class ProjectPolicy < BasePolicy
# NOTE: may be overridden by IssuePolicy
can! :read_issue
# EE-only
can! :read_issue_link
end
end
......@@ -2,7 +2,7 @@ module Ci
class CreatePipelineService < BaseService
attr_reader :pipeline
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, mirror_update: false)
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, mirror_update: false, &block)
@pipeline = Ci::Pipeline.new(
source: source,
project: project,
......@@ -51,7 +51,7 @@ module Ci
return error('No builds for this pipeline.')
end
_create_pipeline
_create_pipeline(&block)
end
private
......@@ -60,6 +60,8 @@ module Ci
Ci::Pipeline.transaction do
update_merge_requests_head_pipeline if pipeline.save
yield(pipeline) if block_given?
Ci::CreatePipelineBuildsService
.new(project, current_user)
.execute(pipeline)
......
module Ci
class PipelineTriggerService < BaseService
def execute
if trigger_from_token
create_pipeline_from_trigger(trigger_from_token)
elsif job_from_token
create_pipeline_from_job(job_from_token)
end
end
private
def create_pipeline_from_trigger(trigger)
# this check is to not leak the presence of the project if user cannot read it
return unless trigger.project == project
trigger_request = trigger.trigger_requests.create(variables: params[:variables])
pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: params[:ref])
.execute(:trigger, ignore_skip_ci: true, trigger_request: trigger_request)
if pipeline.persisted?
success(pipeline: pipeline)
else
error(pipeline.errors.messages, 400)
end
end
def create_pipeline_from_job(job)
# this check is to not leak the presence of the project if user cannot read it
return unless can?(job.user, :read_project, project)
return error("400 Job has to be running", 400) unless job.running?
return error("400 Variables not supported", 400) if params[:variables].any?
pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref]).
execute(:pipeline, ignore_skip_ci: true) do |pipeline|
job.sourced_pipelines.create!(
source_pipeline: job.pipeline,
source_project: job.project,
pipeline: pipeline,
project: project)
end
if pipeline.persisted?
success(pipeline: pipeline)
else
error(pipeline.errors.messages, 400)
end
end
def trigger_from_token
return @trigger if defined?(@trigger)
@trigger = Ci::Trigger.find_by_token(params[:token].to_s)
end
def job_from_token
return @job if defined?(@job)
@job = Ci::Build.find_by_token(params[:token].to_s)
end
end
end
module IssueLinks
class CreateService < BaseService
def initialize(issue, user, params)
@issue, @current_user, @params = issue, user, params.dup
end
def execute
if referenced_issues.blank?
return error('No Issue found for given reference', 401)
end
create_issue_links
success
end
private
def create_issue_links
referenced_issues.each do |referenced_issue|
create_notes(referenced_issue) if relate_issues(referenced_issue)
end
end
def relate_issues(referenced_issue)
IssueLink.create(source: @issue, target: referenced_issue)
end
def create_notes(referenced_issue)
SystemNoteService.relate_issue(@issue, referenced_issue, current_user)
SystemNoteService.relate_issue(referenced_issue, @issue, current_user)
end
def referenced_issues
@referenced_issues ||= begin
issue_references = params[:issue_references]
text = issue_references.join(' ')
extractor = Gitlab::ReferenceExtractor.new(@issue.project, @current_user)
extractor.analyze(text)
extractor.issues.select do |issue|
can?(current_user, :admin_issue_link, issue)
end
end
end
end
end
module IssueLinks
class DestroyService < BaseService
def initialize(issue_link, user)
@issue_link = issue_link
@current_user = user
@issue = issue_link.source
@referenced_issue = issue_link.target
end
def execute
remove_relation
create_notes
success(message: 'Relation was removed')
end
private
def remove_relation
@issue_link.destroy!
end
def create_notes
SystemNoteService.unrelate_issue(@issue, @referenced_issue, current_user)
SystemNoteService.unrelate_issue(@referenced_issue, @issue, current_user)
end
end
end
module IssueLinks
class ListService
include Gitlab::Routing
def initialize(issue, user)
@issue, @current_user, @project = issue, user, issue.project
end
def execute
issues.map do |referenced_issue|
{
id: referenced_issue.id,
iid: referenced_issue.iid,
title: referenced_issue.title,
state: referenced_issue.state,
project_path: referenced_issue.project.path,
namespace_full_path: referenced_issue.project.namespace.full_path,
path: namespace_project_issue_path(referenced_issue.project.namespace, referenced_issue.project, referenced_issue.iid),
destroy_relation_path: destroy_relation_path(referenced_issue)
}
end
end
private
def issues
related_issues = Issue
.select(['issues.*', 'issue_links.id AS issue_links_id'])
.joins("INNER JOIN issue_links ON
(issue_links.source_id = issues.id AND issue_links.target_id = #{@issue.id})
OR
(issue_links.target_id = issues.id AND issue_links.source_id = #{@issue.id})")
.preload(project: :namespace)
.reorder('issue_links_id')
Ability.issues_readable_by_user(related_issues, @current_user)
end
def destroy_relation_path(issue)
# Make sure the user can admin both the current issue AND the
# referenced issue projects in order to return the removal link.
if can_destroy_issue_link_on_current_project? && can_destroy_issue_link?(issue.project)
namespace_project_issue_link_path(@project.namespace,
@issue.project,
@issue.iid,
issue.issue_links_id)
end
end
def can_destroy_issue_link_on_current_project?
return @can_destroy_on_current_project if defined?(@can_destroy_on_current_project)
@can_destroy_on_current_project = can_destroy_issue_link?(@project)
end
def can_destroy_issue_link?(project)
Ability.allowed?(@current_user, :admin_issue_link, project)
end
end
end
......@@ -41,6 +41,7 @@ module Issues
'Due Date' => -> (issue) { issue.due_date&.to_s(:csv) },
'Created At (UTC)' => -> (issue) { issue.created_at&.to_s(:csv) },
'Updated At (UTC)' => -> (issue) { issue.updated_at&.to_s(:csv) },
'Closed At (UTC)' => -> (issue) { issue.closed_at&.to_s(:csv) },
'Milestone' => -> (issue) { issue.milestone&.title },
'Labels' => -> (issue) { @labels[issue.id].sort.join(',').presence }
}
......
......@@ -552,6 +552,38 @@ module SystemNoteService
create_note(NoteSummary.new(noteable, project, author, body, action: 'moved'))
end
#
# noteable - Noteable object
# noteable_ref - Referenced noteable object
# user - User performing reference
#
# Example Note text:
#
# "marked this issue as related to gitlab-ce#9001"
#
# Returns the created Note object
def relate_issue(noteable, noteable_ref, user)
body = "marked this issue as related to #{noteable_ref.to_reference(noteable.project)}"
create_note(NoteSummary.new(noteable, noteable.project, user, body, action: 'relate'))
end
#
# noteable - Noteable object
# noteable_ref - Referenced noteable object
# user - User performing reference
#
# Example Note text:
#
# "removed the relation with gitlab-ce#9001"
#
# Returns the created Note object
def unrelate_issue(noteable, noteable_ref, user)
body = "removed the relation with #{noteable_ref.to_reference(noteable.project)}"
create_note(NoteSummary.new(noteable, noteable.project, user, body, action: 'unrelate'))
end
# Called when the merge request is approved by user
#
# noteable - Noteable object
......
......@@ -9,44 +9,5 @@
%strong= @group.name
group
.pipeline-quota.container-fluid
.row
.col-sm-6
%strong
- last_reset = @group.shared_runners_seconds_last_reset
- if last_reset
Usage since
= last_reset.strftime('%b %d, %Y')
- else
Current period usage
%div
= group_shared_runner_limits_quota(@group)
minutes
.col-sm-6.right
- if @group.shared_runners_minutes_limit_enabled?
#{group_shared_runner_limits_percent_used(@group)}% used
- else
Unlimited
= group_shared_runner_limits_progress_bar(@group)
%table.table.pipeline-project-metrics
%thead
%tr
%th Project
%th Minutes
%tbody
- @projects.each do |project|
%tr
%td
.avatar-container.s20.hidden-xs
= project_icon(project, alt: '', class: 'avatar project-avatar s20')
%strong= link_to project.name, project
%td
= project.shared_runners_minutes
- if @projects.blank?
%tr
%td{ colspan: 2 }
.nothing-here-block This group has no projects which use shared runners
= paginate @projects, theme: "gitlab"
= render "namespaces/pipelines_quota/list",
locals: { namespace: @group, projects: @projects }
......@@ -51,3 +51,7 @@
= link_to audit_log_profile_path, title: 'Authentication log' do
%span
Authentication log
= nav_link(path: 'profiles#pipeline_quota') do
= link_to profile_pipeline_quota_path, title: 'Pipeline quota' do
%span
Pipeline quota
......@@ -3,5 +3,5 @@
%li
%span.light Pipeline minutes quota:
%strong
= group_shared_runner_limits_quota(namespace)
= namespace_shared_runner_limits_quota(namespace)
= link_to icon('question-circle'), help_page_path("user/admin_area/settings/continuous_integration", anchor: "shared-runners-build-minutes-quota"), target: '_blank'
- namespace = locals.fetch(:namespace)
- projects = locals.fetch(:projects)
.pipeline-quota.container-fluid
.row
.col-sm-6
%strong
- last_reset = namespace.shared_runners_seconds_last_reset
- if last_reset
Usage since
= last_reset.strftime('%b %d, %Y')
- else
Current period usage
%div
= namespace_shared_runner_limits_quota(namespace)
minutes
.col-sm-6.right
- if namespace.shared_runners_minutes_limit_enabled?
#{namespace_shared_runner_limits_percent_used(namespace)}% used
- else
Unlimited
= namespace_shared_runner_limits_progress_bar(namespace)
%table.table.pipeline-project-metrics
%thead
%tr
%th Project
%th Minutes
%tbody
- projects.each do |project|
%tr
%td
.avatar-container.s20.hidden-xs
= project_icon(project, alt: '', class: 'avatar project-avatar s20')
%strong= link_to project.name, project
%td
= project.shared_runners_minutes
- if projects.blank?
%tr
%td{ colspan: 2 }
.nothing-here-block This group has no projects which use shared runners
= paginate projects, theme: "gitlab"
- page_title 'Personal pipelines quota'
= render 'profiles/head'
.user-settings-pipeline-quota.row
.profile-settings-sidebar.col-lg-3
%h4
Personal pipelines quota
= link_to icon('question-circle'), help_page_path("user/admin_area/settings/continuous_integration", anchor: "shared-runners-build-minutes-quota"), target: '_blank'
%p.light
Monthly build minutes usage across shared Runners
.col-lg-9
= render "namespaces/pipelines_quota/list",
locals: { namespace: @namespace, projects: @projects }
......@@ -2,7 +2,7 @@
= form_errors(@deploy_keys.new_key)
.form-group
= f.label :title, class: "label-light"
= f.text_field :title, class: 'form-control', autofocus: true, required: true
= f.text_field :title, class: 'form-control', required: true
.form-group
= f.label :key, class: "label-light"
= f.text_area :key, class: "form-control", rows: 5, required: true
......
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
- expanded = Rails.env.test?
%section.settings
.settings-header
%h4
Deploy Keys
%button.btn.js-settings-toggle
= expanded ? 'Close' : 'Expand'
%p
Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
.col-lg-9
.settings-content.no-animate{ class: ('expanded' if expanded) }
%h5.prepend-top-0
Create a new deploy key for this project
= render @deploy_keys.form_partial_path
.col-lg-9.col-lg-offset-3
%hr
#js-deploy-keys{ data: { endpoint: namespace_project_deploy_keys_path } }
.row
= form_errors(@project)
.row.prepend-top-default.append-bottom-default
= form_for @project, url: namespace_project_mirror_path(@project.namespace, @project) do |f|
.col-lg-3
%h4.prepend-top-0
- expanded = Rails.env.test?
%section.settings.project-mirror-settings
.settings-header
%h4
Pull from a remote repository
%p.light
%button.btn.js-settings-toggle
= expanded ? 'Close' : 'Expand'
%p
Set up your project to automatically have its branches, tags, and commits
updated from an upstream repository every hour.
= link_to 'Read more', help_page_path('workflow/repository_mirroring', anchor: 'pulling-from-a-remote-repository'), target: '_blank'
.col-lg-9
%h5.prepend-top-0
.settings-content.no-animate{ class: ('expanded' if expanded) }
= form_for @project, url: namespace_project_mirror_path(@project.namespace, @project) do |f|
%div
= form_errors(@project)
%h5
Set up mirror repository
= render "shared/mirror_update_button"
- if @project.mirror_last_update_failed?
......@@ -43,16 +46,22 @@
They need to have at least master access to this project.
- if @project.builds_enabled?
= render "shared/mirror_trigger_builds_setting", f: f
.col-sm-12
%hr
.col-lg-3
%h4.prepend-top-0
= f.submit 'Save changes', class: 'btn btn-create', name: 'update_remote_mirror'
%section.settings
.settings-header
%h4
Push to a remote repository
%p.light
%button.btn.js-settings-toggle
= expanded ? 'Close' : 'Expand'
%p
Set up the remote repository that you want to update with the content of the current repository
every time someone pushes to it.
= link_to 'Read more', help_page_path('workflow/repository_mirroring', anchor: 'pushing-to-a-remote-repository'), target: '_blank'
.col-lg-9
.settings-content.no-animate{ class: ('expanded' if expanded) }
= form_for @project, url: namespace_project_mirror_path(@project.namespace, @project) do |f|
%div
= form_errors(@project)
= render "shared/remote_mirror_update_button", remote_mirror: @remote_mirror
- if @remote_mirror.last_error.present?
.panel.panel-danger
......@@ -76,4 +85,3 @@
= rm_form.text_field :url, class: "form-control", placeholder: 'https://username:password@gitlab.company.com/group/project.git'
= render "projects/mirrors/instructions"
= f.submit 'Save changes', class: 'btn btn-create', name: 'update_remote_mirror'
%hr
- expanded = Rails.env.test?
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('protected_branches')
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
%section.settings
.settings-header
%h4
Protected Branches
%p Keep stable branches secure and force developers to use merge requests.
%p.prepend-top-20
%button.btn.js-settings-toggle
= expanded ? 'Close' : 'Expand'
%p
Keep stable branches secure and force developers to use merge requests.
.settings-content.no-animate{ class: ('expanded' if expanded) }
%p
By default, protected branches are designed to:
%ul
%li prevent their creation, if not already created, from everybody except Masters
%li prevent pushes from everybody except Masters
%li prevent <strong>anyone</strong> from force pushing to the branch
%li prevent <strong>anyone</strong> from deleting the branch
%p.append-bottom-0 Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}.
.col-lg-9
%p Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}.
- if can? current_user, :admin_project, @project
= render 'projects/protected_branches/create_protected_branch'
......
- expanded = Rails.env.test?
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('protected_tags')
.row.prepend-top-default.append-bottom-default.js-protected-tags-container{ data: { "groups-autocomplete" => "#{autocomplete_project_groups_path(format: :json)}", "users-autocomplete" => "#{autocomplete_users_path(format: :json)}" } }
.col-lg-3
%h4.prepend-top-0
%section.settings.js-protected-tags-container{ data: { "groups-autocomplete" => "#{autocomplete_project_groups_path(format: :json)}", "users-autocomplete" => "#{autocomplete_users_path(format: :json)}" } }
.settings-header
%h4
Protected Tags
%p.prepend-top-20
%button.btn.js-settings-toggle
= expanded ? 'Close' : 'Expand'
%p
Limit access to creating and updating tags.
.settings-content.no-animate{ class: ('expanded' if expanded) }
%p
By default, protected tags are designed to:
%ul
%li Prevent tag creation by everybody except Masters
%li Prevent <strong>anyone</strong> from updating the tag
%li Prevent <strong>anyone</strong> from deleting the tag
%p.append-bottom-0 Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags"), class: "underlined-link"}.
.col-lg-9
%p Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags"), class: "underlined-link"}.
- if can? current_user, :admin_project, @project
= render 'projects/protected_tags/create_protected_tag'
......
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
- expanded = Rails.env.test?
%section.settings
.settings-header
%h4
Push Rules
%p.light
%button.btn.js-settings-toggle
= expanded ? 'Close' : 'Expand'
%p
Push Rules outline what is accepted for this project.
.col-lg-9
%h5.prepend-top-0
.settings-content.no-animate{ class: ('expanded' if expanded) }
%h5
Add new push rule
= form_for [@project.namespace.becomes(Namespace), @project, @push_rule] do |f|
= form_errors(@push_rule)
......
- page_title "Repository"
- @content_class = "limit-container-width" unless fluid_layout
= render "projects/settings/head"
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('deploy_keys')
= render @deploy_keys
= render "projects/push_rules/index"
= render "projects/mirrors/show"
= render "projects/protected_branches/index"
= render "projects/protected_tags/index"
= render @deploy_keys
......@@ -22,7 +22,7 @@ class GeoFileDownloadDispatchWorker
# files, excluding ones in progress.
# 5. Quit when we have scheduled all downloads or exceeded an hour.
def perform
return unless Gitlab::Geo.configured?
return unless Gitlab::Geo.secondary_role_enabled?
return unless Gitlab::Geo.secondary?
@start_time = Time.now
......@@ -153,7 +153,7 @@ class GeoFileDownloadDispatchWorker
def node_enabled?
# Only check every minute to avoid polling the DB excessively
unless @last_enabled_check.present? && (Time.now - @last_enabled_check > 1.minute)
unless @last_enabled_check.present? && @last_enabled_check > 1.minute.ago
@last_enabled_check = Time.now
@current_node_enabled = nil
end
......
......@@ -7,7 +7,7 @@ class GeoRepositorySyncWorker
LAST_SYNC_INTERVAL = 24.hours
def perform
return unless Gitlab::Geo.configured?
return unless Gitlab::Geo.secondary_role_enabled?
return unless Gitlab::Geo.primary_node.present?
start_time = Time.now
......@@ -20,7 +20,7 @@ class GeoRepositorySyncWorker
project_ids.each do |project_id|
begin
break if over_time?(start_time)
break unless Gitlab::Geo.current_node_enabled?
break unless node_enabled?
# We try to obtain a lease here for the entire sync process because we
# want to sync the repositories continuously at a controlled rate
......@@ -73,6 +73,16 @@ class GeoRepositorySyncWorker
Time.now - start_time >= RUN_TIME
end
def node_enabled?
# Only check every minute to avoid polling the DB excessively
unless @last_enabled_check.present? && @last_enabled_check > 1.minute.ago
@last_enabled_check = Time.now
@current_node_enabled = nil
end
@current_node_enabled ||= Gitlab::Geo.current_node_enabled?
end
def try_obtain_lease
lease = Gitlab::ExclusiveLease.new(lease_key, timeout: lease_timeout).try_obtain
......
---
title: Allows manually adding bi-directional relationships between issues in the issue page (EES feature)
merge_request:
author:
---
title: Geo - Properly set tracking database connection and cron jobs on secondary nodes
merge_request:
author:
---
title: Allow to Trigger Pipeline using CI Job Token
merge_request:
author:
---
title: Allow to view Personal pipelines quota
merge_request:
author:
---
title: "[Elasticsearch] Improve code search for camel case"
merge_request:
author:
---
title: Add closed_at field to issue CSV export
merge_request:
author:
---
title: Fix incorrect ETag cache key when relative instance URL is used
merge_request: 11964
author:
......@@ -630,6 +630,12 @@ production: &base
# host: localhost
# port: 3808
## GitLab Geo settings (EE-only)
geo_primary_role:
enabled: false
geo_secondary_role:
enabled: false
#
# 5. Extra customization
# ==========================
......@@ -713,6 +719,10 @@ test:
user_filter: ''
group_base: 'ou=groups,dc=example,dc=com'
admin_group: ''
geo_primary_role:
enabled: true
geo_secondary_role:
enabled: true
staging:
<<: *base
......@@ -350,6 +350,10 @@ Settings.pages['external_https'] ||= false unless Settings.pages['external_http
# Geo
#
Settings.gitlab['geo_status_timeout'] ||= 10
Settings['geo_primary_role'] ||= Settingslogic.new({})
Settings.geo_primary_role['enabled'] = false if Settings.geo_primary_role['enabled'].nil?
Settings['geo_secondary_role'] ||= Settingslogic.new({})
Settings.geo_secondary_role['enabled'] = false if Settings.geo_secondary_role['enabled'].nil?
#
# Git LFS
......
if File.exist?(Rails.root.join('config/database_geo.yml'))
if Gitlab::Geo.secondary_role_enabled?
Rails.application.configure do
config.geo_database = config_for(:database_geo)
end
......
......@@ -3,6 +3,11 @@
en:
hello: "Hello world"
activerecord:
attributes:
issue_link:
source: Source issue
target: Target issue
errors:
messages:
label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one."
......
......@@ -47,5 +47,9 @@ resource :profile, only: [:show, :update] do
end
resources :u2f_registrations, only: [:destroy]
## EE-specific
resources :pipeline_quota, only: [:index]
## EE-specific
end
end
......@@ -311,6 +311,8 @@ constraints(ProjectUrlConstrainer.new) do
post :bulk_update
post :export_csv
end
resources :issue_links, only: [:index, :create, :destroy], as: 'links', path: 'links'
end
resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do
......
......@@ -31,6 +31,7 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d
get :contributed, as: :contributed_projects
get :snippets
get :exists
get :pipelines_quota
get '/', to: redirect('/%{username}'), as: nil
end
......
class CreateIssueLinksTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
create_table :issue_links do |t|
t.integer :source_id, null: false, index: true
t.integer :target_id, null: false, index: true
t.timestamps null: true
end
add_index :issue_links, [:source_id, :target_id], unique: true
add_concurrent_foreign_key :issue_links, :issues, column: :source_id
add_concurrent_foreign_key :issue_links, :issues, column: :target_id
end
def down
drop_table :issue_links
end
end
......@@ -593,6 +593,17 @@ ActiveRecord::Schema.define(version: 20170602003304) do
add_index "issue_assignees", ["issue_id", "user_id"], name: "index_issue_assignees_on_issue_id_and_user_id", unique: true, using: :btree
add_index "issue_assignees", ["user_id"], name: "index_issue_assignees_on_user_id", using: :btree
create_table "issue_links", force: :cascade do |t|
t.integer "source_id", null: false
t.integer "target_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "issue_links", ["source_id", "target_id"], name: "index_issue_links_on_source_id_and_target_id", unique: true, using: :btree
add_index "issue_links", ["source_id"], name: "index_issue_links_on_source_id", using: :btree
add_index "issue_links", ["target_id"], name: "index_issue_links_on_target_id", using: :btree
create_table "issue_metrics", force: :cascade do |t|
t.integer "issue_id", null: false
t.datetime "first_mentioned_in_commit_at"
......@@ -1710,6 +1721,8 @@ ActiveRecord::Schema.define(version: 20170602003304) do
add_foreign_key "container_repositories", "projects"
add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade
add_foreign_key "issue_assignees", "users", name: "fk_5e0c8d9154", on_delete: :cascade
add_foreign_key "issue_links", "issues", column: "source_id", name: "fk_c900194ff2", on_delete: :cascade
add_foreign_key "issue_links", "issues", column: "target_id", name: "fk_e71bb44f1f", on_delete: :cascade
add_foreign_key "issue_metrics", "issues", on_delete: :cascade
add_foreign_key "label_priorities", "labels", on_delete: :cascade
add_foreign_key "label_priorities", "projects", on_delete: :cascade
......
......@@ -4,7 +4,8 @@
- [Introduced][ci-229] in GitLab CE 7.14.
- GitLab 8.12 has a completely redesigned job permissions system. Read all
about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#job-triggers).
- GitLab 9.0 introduced a trigger ownership to solve permission problems.
- GitLab 9.0 introduced a trigger ownership to solve permission problems,
- GitLab 9.3 introduced an ability to use CI Job Token to trigger dependent pipelines,
Triggers can be used to force a rebuild of a specific `ref` (branch or tag)
with an API call.
......@@ -161,6 +162,25 @@ probably not the wisest idea, so you might want to use a
[secure variable](../variables/README.md#user-defined-variables-secure-variables)
for that purpose._
---
Since GitLab 9.3 you can trigger a new pipeline using a CI_JOB_TOKEN.
This method currently doesn't support Variables.
The support for them will be included in 9.4 of GitLab.
This way of triggering creates a dependent pipeline relation visible on the Pipeline Graph.
```yaml
build_docs:
stage: deploy
script:
- "curl --request POST --form "token=$CI_JOB_TOKEN" --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
only:
- tags
```
Pipelines triggered that way do expose a special variable: `CI_PIPELINE_SOURCE=pipeline`.
### Making use of trigger variables
Using trigger variables can be proven useful for a variety of reasons.
......
......@@ -53,6 +53,7 @@ future GitLab releases.**
| **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used |
| **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags |
| **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally |
| **CI_PIPELINE_SOURCE** | 9.3 | all | The variable indicates how the pipeline was triggered, possible options are: push, web, trigger, schedule, api, pipeline |
| **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] |
| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run |
| **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally |
......
......@@ -7,7 +7,7 @@ feature tests with Capybara for e2e (end-to-end) integration testing.
Unit and feature tests need to be written for all new features.
Most of the time, you should use rspec for your feature tests.
There are cases where the behaviour you are testing is not worth the time spent running the full application,
for example, if you are testing styling, animation or small actions that don't involve the backend,
for example, if you are testing styling, animation, edge cases or small actions that don't involve the backend,
you should write an integration test using Jasmine.
![Testing priority triangle](img/testing_triangle.png)
......
......@@ -264,6 +264,22 @@ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations, the upgrade is complete!
### 13. Elasticsearch index update (if you currently use Elasticsearch)
In 9.3 release we changed the index mapping to improve partial word matching. Please re-create your index by using one of two ways listed below:
1. Re-create the index. The following command is acceptable for not very big GitLab instances (storage size no more than few gigabytes).
```
# Omnibus installations
sudo gitlab-rake gitlab:elastic:index
# Installations from source
bundle exec rake gitlab:elastic:index
```
1. For very big GitLab instances we recommend following [Add GitLab's data to the Elasticsearch index](../integration/elasticsearch.md#add-gitlabs-data-to-the-elasticsearch-index).
## Things went south? Revert to previous version (9.2)
### 1. Revert the code to the previous version
......
......@@ -35,7 +35,6 @@ module API
get 'status' do
authenticate_by_gitlab_geo_node_token!
require_node_to_be_secondary!
require_node_to_have_tracking_db!
present GeoNodeStatus.new(id: Gitlab::Geo.current_node.id), with: Entities::GeoNodeStatus
end
......@@ -110,10 +109,6 @@ module API
def require_node_to_be_secondary!
forbidden! 'Geo node is not secondary node.' unless Gitlab::Geo.current_node&.secondary?
end
def require_node_to_have_tracking_db!
not_found! 'Geo node does not have its tracking database enabled.' unless Gitlab::Geo.configured?
end
end
end
end
......@@ -11,28 +11,26 @@ module API
end
params do
requires :ref, type: String, desc: 'The commit sha or name of a branch or tag'
requires :token, type: String, desc: 'The unique token of trigger'
requires :token, type: String, desc: 'The unique token of trigger or job token'
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do
project = find_project(params[:id])
trigger = Ci::Trigger.find_by_token(params[:token].to_s)
not_found! unless project && trigger
unauthorized! unless trigger.project == project
# validate variables
variables = params[:variables].to_h
unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
params[:variables] = params[:variables].to_h
unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) }
render_api_error!('variables needs to be a map of key-valued strings', 400)
end
# create request and trigger builds
trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
if trigger_request
present trigger_request.pipeline, with: Entities::Pipeline
project = find_project(params[:id])
not_found! unless project
result = Ci::PipelineTriggerService.new(project, nil, params).execute
not_found! unless result
if result[:http_status]
render_api_error!(result[:message], result[:http_status])
else
errors = 'No pipeline created'
render_api_error!(errors, 400)
present result[:pipeline], with: Entities::Pipeline
end
end
......
......@@ -33,7 +33,7 @@ module Elasticsearch
code_analyzer: {
type: 'custom',
tokenizer: 'standard',
filter: %w(code lowercase asciifolding),
filter: %w(code edgeNGram_filter lowercase asciifolding),
char_filter: ["code_mapping"]
},
code_search_analyzer: {
......@@ -61,8 +61,14 @@ module Elasticsearch
preserve_original: 1,
patterns: [
"(\\p{Ll}+|\\p{Lu}\\p{Ll}+|\\p{Lu}+)",
"(\\d+)"
"(\\d+)",
"(?=([\\p{Lu}]+[\\p{L}]+))"
]
},
edgeNGram_filter: {
type: 'edgeNGram',
min_gram: 2,
max_gram: 40
}
},
char_filter: {
......
......@@ -6,12 +6,13 @@ module Gitlab
end
def call(env)
route = Gitlab::EtagCaching::Router.match(env)
request = Rack::Request.new(env)
route = Gitlab::EtagCaching::Router.match(request)
return @app.call(env) unless route
track_event(:etag_caching_middleware_used, route)
etag, cached_value_present = get_etag(env)
etag, cached_value_present = get_etag(request)
if_none_match = env['HTTP_IF_NONE_MATCH']
if if_none_match == etag
......@@ -27,8 +28,8 @@ module Gitlab
private
def get_etag(env)
cache_key = env['PATH_INFO']
def get_etag(request)
cache_key = request.path
store = Gitlab::EtagCaching::Store.new
current_value = store.get(cache_key)
cached_value_present = current_value.present?
......
......@@ -53,8 +53,8 @@ module Gitlab
)
].freeze
def self.match(env)
ROUTES.find { |route| route.regexp.match(env['PATH_INFO']) }
def self.match(request)
ROUTES.find { |route| route.regexp.match(request.path_info) }
end
end
end
......
......@@ -42,8 +42,12 @@ module Gitlab
Gitlab::Geo.current_node.reload.enabled?
end
def self.configured?
Rails.configuration.respond_to?(:geo_database)
def self.primary_role_enabled?
Gitlab.config.geo_primary_role['enabled']
end
def self.secondary_role_enabled?
Gitlab.config.geo_secondary_role['enabled']
end
def self.license_allows?
......@@ -94,9 +98,9 @@ module Gitlab
end
def self.configure_cron_jobs!
if self.primary?
if self.primary_role_enabled?
self.configure_primary_jobs!
elsif self.secondary?
elsif self.secondary_role_enabled?
self.configure_secondary_jobs!
else
self.disable_all_jobs!
......
......@@ -3,7 +3,8 @@ module Gitlab
class HealthCheck
def self.perform_checks
return '' unless Gitlab::Geo.secondary?
return 'The Geo database configuration file is missing.' unless Gitlab::Geo.configured?
return 'The Geo secondary role is disabled.' unless Gitlab::Geo.secondary_role_enabled?
return 'The Geo database configuration file is missing.' unless self.geo_database_configured?
return 'The Geo node has a database that is not configured for streaming replication with the primary node.' unless self.database_secondary?
database_version = self.get_database_version.to_i
......@@ -57,6 +58,10 @@ module Gitlab
.first
.fetch('pg_is_in_recovery') == 't'
end
def self.geo_database_configured?
Rails.configuration.respond_to?(:geo_database)
end
end
end
end
......@@ -284,13 +284,18 @@ describe Projects::MergeRequestsController do
context 'number of queries' do
it 'verifies number of queries' do
RequestStore.begin!
# pre-create objects
merge_request
recorded = ActiveRecord::QueryRecorder.new { go(format: :json) }
expect(recorded.count).to be_within(10).of(100)
expect(recorded.count).to be_within(1).of(31)
expect(recorded.cached_count).to eq(0)
RequestStore.end!
RequestStore.clear!
end
end
end
......
FactoryGirl.define do
factory :issue_link do
source factory: :issue
target factory: :issue
end
end
require 'spec_helper'
feature 'Profile > Pipeline Quota', feature: true do
let(:user) { create(:user) }
let(:namespace) { create(:namespace, owner: user) }
let!(:project) { create(:empty_project, namespace: namespace, shared_runners_enabled: true) }
before do
login_with(user)
end
it 'is linked within the profile page' do
visit profile_path
page.within('.layout-nav') do
expect(page).to have_selector(:link_or_button, 'Pipeline quota')
end
end
context 'with no quota' do
let(:namespace) { create(:namespace, :with_build_minutes, owner: user) }
it 'shows correct group quota info' do
visit profile_pipeline_quota_path
page.within('.pipeline-quota') do
expect(page).to have_content("400 / Unlimited minutes")
expect(page).to have_selector('.progress-bar-success')
end
end
end
context 'with no projects using shared runners' do
let(:namespace) { create(:namespace, :with_not_used_build_minutes_limit, owner: user) }
let!(:project) { create(:empty_project, namespace: namespace, shared_runners_enabled: false) }
it 'shows correct group quota info' do
visit profile_pipeline_quota_path
page.within('.pipeline-quota') do
expect(page).to have_content("300 / Unlimited minutes")
expect(page).to have_selector('.progress-bar-success')
end
page.within('.pipeline-project-metrics') do
expect(page).to have_content('This group has no projects which use shared runners')
end
end
end
context 'minutes under quota' do
let(:namespace) { create(:namespace, :with_not_used_build_minutes_limit, owner: user) }
it 'shows correct group quota info' do
visit profile_pipeline_quota_path
page.within('.pipeline-quota') do
expect(page).to have_content("300 / 500 minutes")
expect(page).to have_content("60% used")
expect(page).to have_selector('.progress-bar-success')
end
end
end
context 'minutes over quota' do
let(:namespace) { create(:namespace, :with_used_build_minutes_limit, owner: user) }
let!(:other_project) { create(:empty_project, namespace: namespace, shared_runners_enabled: false) }
it 'shows correct group quota and projects info' do
visit profile_pipeline_quota_path
page.within('.pipeline-quota') do
expect(page).to have_content("1000 / 500 minutes")
expect(page).to have_content("200% used")
expect(page).to have_selector('.progress-bar-danger')
end
page.within('.pipeline-project-metrics') do
expect(page).to have_content(project.name)
expect(page).not_to have_content(other_project.name)
end
end
end
end
......@@ -41,7 +41,7 @@ describe 'Project settings > [EE] repository', feature: true do
end
it 'sets mirror user' do
page.within('.edit_project') do
page.within('.project-mirror-settings') do
select2(user2.id, from: '#project_mirror_user_id')
click_button('Save changes')
......
......@@ -288,6 +288,18 @@ describe IssuesFinder do
expect(issues.count).to eq 0
end
it 'returns disabled issues if feature_availability_check param set to false' do
[project1, project2].each do |project|
project.project_feature.update!(issues_access_level: ProjectFeature::DISABLED)
end
issues = described_class
.new(search_user, params.reverse_merge(scope: scope, state: 'opened', feature_availability_check: false))
.execute
expect(issues.count).to eq 3
end
end
end
......
......@@ -378,8 +378,8 @@ describe Gitlab::Elastic::SearchResults, lib: true do
results = described_class.new(user, 'def', limit_project_ids)
blobs = results.objects('blobs')
expect(blobs.first["_source"]["blob"]["content"]).to include("def")
expect(results.blobs_count).to eq 5
expect(blobs.first['_source']['blob']['content']).to include('def')
expect(results.blobs_count).to eq 7
end
it 'finds blobs from public projects only' do
......@@ -388,10 +388,11 @@ describe Gitlab::Elastic::SearchResults, lib: true do
Gitlab::Elastic::Helper.refresh_index
results = described_class.new(user, 'def', [project_1.id])
expect(results.blobs_count).to eq 5
expect(results.blobs_count).to eq 7
results = described_class.new(user, 'def', [project_1.id, project_2.id])
expect(results.blobs_count).to eq 10
expect(results.blobs_count).to eq 14
end
it 'returns zero when blobs are not found' do
......@@ -399,6 +400,45 @@ describe Gitlab::Elastic::SearchResults, lib: true do
expect(results.blobs_count).to eq 0
end
context 'Searches CamelCased methods' do
before do
project_1.repository.create_file(
user,
'test.txt',
' function writeStringToFile(){} ',
message: 'added test file',
branch_name: 'master')
project_1.repository.index_blobs
Gitlab::Elastic::Helper.refresh_index
end
def search_for(term)
blobs = described_class.new(user, term, [project_1.id]).objects('blobs')
blobs.map do |blob|
blob['_source']['blob']['path']
end
end
it 'find by first word' do
expect(search_for('write')).to include('test.txt')
end
it 'find by first two words' do
expect(search_for('writeString')).to include('test.txt')
end
it 'find by last two words' do
expect(search_for('ToFile')).to include('test.txt')
end
it 'find by exact match' do
expect(search_for('writeStringToFile')).to include('test.txt')
end
end
end
describe 'Wikis' do
......@@ -415,7 +455,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do
it 'finds wiki blobs' do
blobs = results.objects('wiki_blobs')
expect(blobs.first["_source"]["blob"]["content"]).to include("term")
expect(blobs.first['_source']['blob']['content']).to include("term")
expect(results.wiki_blobs_count).to eq 1
end
......@@ -423,7 +463,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do
project_1.add_guest(user)
blobs = results.objects('wiki_blobs')
expect(blobs.first["_source"]["blob"]["content"]).to include("term")
expect(blobs.first['_source']['blob']['content']).to include("term")
expect(results.wiki_blobs_count).to eq 1
end
......
......@@ -164,6 +164,25 @@ describe Gitlab::EtagCaching::Middleware do
end
end
context 'when GitLab instance is using a relative URL' do
before do
mock_app_response
end
it 'uses full path as cache key' do
env = {
'PATH_INFO' => enabled_path,
'SCRIPT_NAME' => '/relative-gitlab'
}
expect_any_instance_of(Gitlab::EtagCaching::Store)
.to receive(:get).with("/relative-gitlab#{enabled_path}")
.and_return(nil)
middleware.call(env)
end
end
def mock_app_response
allow(app).to receive(:call).and_return([app_status_code, {}, ['body']])
end
......
......@@ -2,115 +2,115 @@ require 'spec_helper'
describe Gitlab::EtagCaching::Router do
it 'matches issue notes endpoint' do
env = build_env(
request = build_request(
'/my-group/and-subgroup/here-comes-the-project/noteable/issue/1/notes'
)
result = described_class.match(env)
result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'issue_notes'
end
it 'matches issue title endpoint' do
env = build_env(
request = build_request(
'/my-group/my-project/issues/123/realtime_changes'
)
result = described_class.match(env)
result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'issue_title'
end
it 'matches project pipelines endpoint' do
env = build_env(
request = build_request(
'/my-group/my-project/pipelines.json'
)
result = described_class.match(env)
result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'project_pipelines'
end
it 'matches commit pipelines endpoint' do
env = build_env(
request = build_request(
'/my-group/my-project/commit/aa8260d253a53f73f6c26c734c72fdd600f6e6d4/pipelines.json'
)
result = described_class.match(env)
result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'commit_pipelines'
end
it 'matches new merge request pipelines endpoint' do
env = build_env(
request = build_request(
'/my-group/my-project/merge_requests/new.json'
)
result = described_class.match(env)
result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'new_merge_request_pipelines'
end
it 'matches merge request pipelines endpoint' do
env = build_env(
request = build_request(
'/my-group/my-project/merge_requests/234/pipelines.json'
)
result = described_class.match(env)
result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'merge_request_pipelines'
end
it 'matches build endpoint' do
env = build_env(
request = build_request(
'/my-group/my-project/builds/234.json'
)
result = described_class.match(env)
result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'project_build'
end
it 'does not match blob with confusing name' do
env = build_env(
request = build_request(
'/my-group/my-project/blob/master/pipelines.json'
)
result = described_class.match(env)
result = described_class.match(request)
expect(result).to be_blank
end
it 'matches the environments path' do
env = build_env(
request = build_request(
'/my-group/my-project/environments.json'
)
result = described_class.match(env)
result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'environments'
end
it 'matches pipeline#show endpoint' do
env = build_env(
request = build_request(
'/my-group/my-project/pipelines/2.json'
)
result = described_class.match(env)
result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'project_pipeline'
end
def build_env(path)
{ 'PATH_INFO' => path }
def build_request(path)
double(path_info: path)
end
end
......@@ -16,6 +16,13 @@ describe Gitlab::Geo::HealthCheck do
expect(subject.perform_checks).to be_blank
end
it 'returns an error when secondary role is disabled' do
allow(Gitlab::Geo).to receive(:secondary?) { true }
allow(Gitlab::Geo).to receive(:secondary_role_enabled?).and_return(false)
expect(subject.perform_checks).not_to be_blank
end
it 'returns an error when database is not configured for streaming replication' do
allow(Gitlab::Geo).to receive(:secondary?) { true }
allow(Gitlab::Database).to receive(:postgresql?) { true }
......
......@@ -127,7 +127,9 @@ describe Gitlab::Geo, lib: true do
end
it 'activates cron jobs for primary' do
allow(described_class).to receive(:primary?).and_return(true)
allow(described_class).to receive(:primary_role_enabled?).and_return(true)
allow(described_class).to receive(:secondary_role_enabled?).and_return(false)
described_class.configure_cron_jobs!
expect(described_class.bulk_notify_job).to be_enabled
......@@ -136,7 +138,9 @@ describe Gitlab::Geo, lib: true do
end
it 'activates cron jobs for secondary' do
allow(described_class).to receive(:secondary?).and_return(true)
allow(described_class).to receive(:primary_role_enabled?).and_return(false)
allow(described_class).to receive(:secondary_role_enabled?).and_return(true)
described_class.configure_cron_jobs!
expect(described_class.bulk_notify_job).not_to be_enabled
......@@ -145,6 +149,9 @@ describe Gitlab::Geo, lib: true do
end
it 'deactivates all jobs when Geo is not active' do
allow(described_class).to receive(:primary_role_enabled?).and_return(false)
allow(described_class).to receive(:secondary_role_enabled?).and_return(false)
described_class.configure_cron_jobs!
expect(described_class.bulk_notify_job).not_to be_enabled
......
require 'spec_helper'
describe IssueLink do
describe 'Associations' do
it { is_expected.to belong_to(:source).class_name('Issue') }
it { is_expected.to belong_to(:target).class_name('Issue') }
end
describe 'Validation' do
subject { create :issue_link }
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:target) }
it do
is_expected.to validate_uniqueness_of(:source)
.scoped_to(:target_id)
.with_message(/already related/)
end
context 'self relation' do
let(:issue) { create :issue }
context 'cannot be validated' do
it 'does not invalidate object with self relation error' do
issue_link = build :issue_link, source: issue, target: nil
issue_link.valid?
expect(issue_link.errors[:source]).to be_empty
end
end
context 'can be invalidated' do
it 'invalidates object' do
issue_link = build :issue_link, source: issue, target: issue
expect(issue_link).to be_invalid
expect(issue_link.errors[:source]).to include('cannot be related to itself')
end
end
end
end
end
......@@ -10,10 +10,14 @@ describe ProjectPolicy, models: true do
let(:admin) { create(:admin) }
let(:project) { create(:empty_project, :public, namespace: owner.namespace) }
before do
allow_any_instance_of(License).to receive(:feature_available?) { true }
end
let(:guest_permissions) do
%i[
read_project read_board read_list read_wiki read_issue read_label
read_milestone read_project_snippet read_project_member
read_issue_link read_milestone read_project_snippet read_project_member
read_note create_project create_issue create_note
upload_file
]
......@@ -22,7 +26,7 @@ describe ProjectPolicy, models: true do
let(:reporter_permissions) do
%i[
download_code fork_project create_project_snippet update_issue
admin_issue admin_label admin_list read_commit_status read_build
admin_issue admin_label admin_issue_link admin_list read_commit_status read_build
read_container_image read_pipeline read_environment read_deployment
read_merge_request download_wiki_code
]
......@@ -71,7 +75,7 @@ describe ProjectPolicy, models: true do
let(:auditor_permissions) do
%i[
download_code download_wiki_code read_project read_board read_list
read_wiki read_issue read_label read_milestone read_project_snippet
read_wiki read_issue read_label read_issue_link read_milestone read_project_snippet
read_project_member read_note read_cycle_analytics read_pipeline
read_build read_commit_status read_container_image read_environment
read_deployment read_merge_request read_pages
......
......@@ -307,14 +307,6 @@ describe API::Geo, api: true do
expect(response).to have_http_status(200)
expect(response).to match_response_schema('geo_node_status')
end
it 'responds with a 404 when the tracking database is disabled' do
allow(Gitlab::Geo).to receive(:configured?).and_return(false)
get api('/geo/status'), nil, request.headers
expect(response).to have_http_status(404)
end
end
context 'when requesting primary node with valid auth header' do
......
......@@ -36,12 +36,6 @@ describe API::Triggers do
expect(response).to have_http_status(404)
end
it 'returns unauthorized if token is for different project' do
post api("/projects/#{project2.id}/trigger/pipeline"), options.merge(ref: 'master')
expect(response).to have_http_status(401)
end
end
context 'Have a commit' do
......@@ -61,7 +55,7 @@ describe API::Triggers do
post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'other-branch')
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('No pipeline created')
expect(json_response['message']).to eq('base' => ["Reference not found"])
end
context 'Validates variables' do
......@@ -93,6 +87,12 @@ describe API::Triggers do
end
context 'when triggering a pipeline from a trigger token' do
it 'does not leak the presence of project when token is for different project' do
post api("/projects/#{project2.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
expect(response).to have_http_status(404)
end
it 'creates builds from the ref given in the URL, not in the body' do
expect do
post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
......@@ -113,6 +113,93 @@ describe API::Triggers do
end
end
end
context 'when triggering a pipeline from a job token' do
let(:other_job) { create(:ci_build, :running, user: other_user) }
let(:params) { { ref: 'refs/heads/other-branch' } }
subject do
post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{other_job.token}"), params
end
context 'without user' do
let(:other_user) { nil }
it 'does not leak the presence of project when using valid token' do
subject
expect(response).to have_http_status(404)
end
end
context 'for unreleated user' do
let(:other_user) { create(:user) }
it 'does not leak the presence of project when using valid token' do
subject
expect(response).to have_http_status(404)
end
end
context 'for related user' do
let(:other_user) { create(:user) }
context 'with reporter permissions' do
before do
project.add_reporter(other_user)
end
it 'forbids to create a pipeline' do
subject
expect(response).to have_http_status(400)
expect(json_response['message']).to eq("base" => ["Insufficient permissions to create a new pipeline"])
end
end
context 'with developer permissions' do
before do
project.add_developer(other_user)
end
it 'creates a new pipeline' do
expect { subject }.to change(Ci::Pipeline, :count)
expect(response).to have_http_status(201)
expect(Ci::Pipeline.last.source).to eq('pipeline')
expect(Ci::Pipeline.last.triggered_by_pipeline).not_to be_nil
end
context 'when build is complete' do
before do
other_job.success
end
it 'does not create a pipeline' do
subject
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('400 Job has to be running')
end
end
context 'when variables are defined' do
let(:params) do
{ ref: 'refs/heads/other-branch',
variables: { 'KEY' => 'VALUE' } }
end
it 'forbids to create a pipeline' do
subject
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('400 Variables not supported')
end
end
end
end
end
end
describe 'GET /projects/:id/triggers' do
......
require 'rails_helper'
describe Projects::IssueLinksController do
let(:user) { create :user }
let(:project) { create(:project_empty_repo) }
let(:issue) { create :issue, project: project }
before do
allow_any_instance_of(License).to receive(:feature_available?) { false }
allow_any_instance_of(License).to receive(:feature_available?).with(:related_issues) { true }
end
describe 'GET /*namespace_id/:project_id/issues/:issue_id/links' do
let(:issue_b) { create :issue, project: project }
let!(:issue_link) { create :issue_link, source: issue, target: issue_b }
before do
project.team << [user, :guest]
login_as user
end
it 'returns JSON response' do
list_service_response = IssueLinks::ListService.new(issue, user).execute
get namespace_project_issue_links_path(issue_links_params)
expect(response).to have_http_status(200)
expect(json_response).to eq(list_service_response.as_json)
end
end
describe 'POST /*namespace_id/:project_id/issues/:issue_id/links' do
let(:issue_b) { create :issue, project: project }
before do
project.team << [user, user_role]
login_as user
end
context 'with success' do
let(:user_role) { :developer }
let(:issue_references) { [issue_b.to_reference] }
it 'returns success JSON' do
post namespace_project_issue_links_path(issue_links_params(issue_references: issue_references))
list_service_response = IssueLinks::ListService.new(issue, user).execute
expect(response).to have_http_status(200)
expect(json_response).to eq('message' => nil,
'issues' => list_service_response.as_json)
end
end
context 'with failure' do
context 'when unauthorized' do
let(:user_role) { :guest }
let(:issue_references) { [issue_b.to_reference] }
it 'returns 403' do
post namespace_project_issue_links_path(issue_links_params(issue_references: issue_references))
expect(response).to have_http_status(403)
end
end
context 'when failing service result' do
let(:user_role) { :developer }
let(:issue_references) { ['#999'] }
it 'returns failure JSON' do
post namespace_project_issue_links_path(issue_links_params(issue_references: issue_references))
list_service_response = IssueLinks::ListService.new(issue, user).execute
expect(response).to have_http_status(401)
expect(json_response).to eq('message' => 'No Issue found for given reference', 'issues' => list_service_response.as_json)
end
end
end
end
describe 'DELETE /*namespace_id/:project_id/issues/:issue_id/link/:id' do
let(:issue_link) { create :issue_link, target: referenced_issue }
before do
project.team << [user, user_role]
login_as user
end
context 'when unauthorized' do
context 'when no authorization on current project' do
let(:referenced_issue) { create :issue, project: project }
let(:user_role) { :guest }
it 'returns 403' do
delete namespace_project_issue_link_path(issue_links_params(id: issue_link.id))
expect(response).to have_http_status(403)
end
end
context 'when no authorization on the related issue project' do
# unauthorized project issue
let(:referenced_issue) { create :issue }
let(:user_role) { :developer }
it 'returns 403' do
delete namespace_project_issue_link_path(issue_links_params(id: issue_link.id))
expect(response).to have_http_status(403)
end
end
end
context 'when authorized' do
let(:referenced_issue) { create :issue, project: project }
let(:user_role) { :developer }
it 'returns success JSON' do
delete namespace_project_issue_link_path(issue_links_params(id: issue_link.id))
list_service_response = IssueLinks::ListService.new(issue, user).execute
expect(json_response).to eq('issues' => list_service_response.as_json)
end
end
end
def issue_links_params(opts = {})
opts.reverse_merge(namespace_id: issue.project.namespace,
project_id: issue.project,
issue_id: issue,
format: :json)
end
end
require 'spec_helper'
describe IssueLinks::CreateService, service: true do
describe '#execute' do
let(:namespace) { create :namespace }
let(:project) { create :empty_project, namespace: namespace }
let(:issue) { create :issue, project: project }
let(:user) { create :user }
let(:params) do
{}
end
before do
allow_any_instance_of(License).to receive(:feature_available?) { false }
allow_any_instance_of(License).to receive(:feature_available?).with(:related_issues) { true }
project.team << [user, :developer]
end
subject { described_class.new(issue, user, params).execute }
context 'when the reference list is empty' do
let(:params) do
{ issue_references: [] }
end
it 'returns error' do
is_expected.to eq(message: 'No Issue found for given reference', status: :error, http_status: 401)
end
end
context 'when Issue not found' do
let(:params) do
{ issue_references: ['#999'] }
end
it 'returns error' do
is_expected.to eq(message: 'No Issue found for given reference', status: :error, http_status: 401)
end
it 'no relationship is created' do
expect { subject }.not_to change(IssueLink, :count)
end
end
context 'when user has no permission to target project Issue' do
let(:target_issue) { create :issue }
let(:params) do
{ issue_references: [target_issue.to_reference(project)] }
end
it 'returns error' do
target_issue.project.add_guest(user)
is_expected.to eq(message: 'No Issue found for given reference', status: :error, http_status: 401)
end
it 'no relationship is created' do
expect { subject }.not_to change(IssueLink, :count)
end
end
context 'when there is an issue to relate' do
let(:issue_a) { create :issue, project: project }
let(:another_project) { create :empty_project, namespace: project.namespace }
let(:another_project_issue) { create :issue, project: another_project }
let(:issue_a_ref) { issue_a.to_reference }
let(:another_project_issue_ref) { another_project_issue.to_reference(project) }
let(:params) do
{ issue_references: [issue_a_ref, another_project_issue_ref] }
end
before do
another_project.team << [user, :developer]
end
it 'creates relationships' do
expect { subject }.to change(IssueLink, :count).from(0).to(2)
expect(IssueLink.find_by!(target: issue_a)).to have_attributes(source: issue)
expect(IssueLink.find_by!(target: another_project_issue)).to have_attributes(source: issue)
end
it 'returns success status' do
is_expected.to eq(status: :success)
end
it 'creates notes' do
# First two-way relation notes
expect(SystemNoteService).to receive(:relate_issue)
.with(issue, issue_a, user)
expect(SystemNoteService).to receive(:relate_issue)
.with(issue_a, issue, user)
# Second two-way relation notes
expect(SystemNoteService).to receive(:relate_issue)
.with(issue, another_project_issue, user)
expect(SystemNoteService).to receive(:relate_issue)
.with(another_project_issue, issue, user)
subject
end
end
context 'when reference of any already related issue is present' do
let(:issue_a) { create :issue, project: project }
let(:issue_b) { create :issue, project: project }
before do
create :issue_link, source: issue, target: issue_a
end
let(:params) do
{ issue_references: [issue_b.to_reference, issue_a.to_reference] }
end
it 'returns success status' do
is_expected.to eq(status: :success)
end
it 'valid relations are created' do
expect { subject }.to change(IssueLink, :count).from(1).to(2)
expect(IssueLink.find_by!(target: issue_b)).to have_attributes(source: issue)
end
end
end
end
require 'spec_helper'
describe IssueLinks::DestroyService, service: true do
describe '#execute' do
let(:user) { create :user }
let!(:issue_link) { create :issue_link }
subject { described_class.new(issue_link, user).execute }
it 'removes related issue' do
expect { subject }.to change(IssueLink, :count).from(1).to(0)
end
it 'creates notes' do
# Two-way notes creation
expect(SystemNoteService).to receive(:unrelate_issue)
.with(issue_link.source, issue_link.target, user)
expect(SystemNoteService).to receive(:unrelate_issue)
.with(issue_link.target, issue_link.source, user)
subject
end
it 'returns success message' do
is_expected.to eq(message: 'Relation was removed', status: :success)
end
end
end
require 'spec_helper'
describe IssueLinks::ListService, service: true do
let(:user) { create :user }
let(:project) { create(:project_empty_repo, :private) }
let(:issue) { create :issue, project: project }
let(:user_role) { :developer }
before do
allow_any_instance_of(License).to receive(:feature_available?) { false }
allow_any_instance_of(License).to receive(:feature_available?).with(:related_issues) { true }
project.team << [user, user_role]
end
describe '#execute' do
subject { described_class.new(issue, user).execute }
context 'user can see all issues' do
let(:issue_b) { create :issue, project: project }
let(:issue_c) { create :issue, project: project }
let(:issue_d) { create :issue, project: project }
let!(:issue_link_c) do
create(:issue_link, source: issue_d,
target: issue)
end
let!(:issue_link_b) do
create(:issue_link, source: issue,
target: issue_c)
end
let!(:issue_link_a) do
create(:issue_link, source: issue,
target: issue_b)
end
it 'ensures no N+1 queries are made' do
control_count = ActiveRecord::QueryRecorder.new { subject }.count
project = create :empty_project, :public
issue_x = create :issue, project: project
issue_y = create :issue, project: project
issue_z = create :issue, project: project
create :issue_link, source: issue_x, target: issue_y
create :issue_link, source: issue_x, target: issue_z
create :issue_link, source: issue_y, target: issue_z
expect { subject }.not_to exceed_query_limit(control_count)
end
it 'returns related issues JSON' do
expect(subject.size).to eq(3)
expect(subject).to include(include(id: issue_b.id,
iid: issue_b.iid,
title: issue_b.title,
state: issue_b.state,
path: "/#{project.full_path}/issues/#{issue_b.iid}",
project_path: issue_b.project.path,
namespace_full_path: issue_b.project.namespace.full_path,
destroy_relation_path: "/#{project.full_path}/issues/#{issue.iid}/links/#{issue_link_a.id}"))
expect(subject).to include(include(id: issue_c.id,
iid: issue_c.iid,
title: issue_c.title,
state: issue_c.state,
path: "/#{project.full_path}/issues/#{issue_c.iid}",
project_path: issue_c.project.path,
namespace_full_path: issue_c.project.namespace.full_path,
destroy_relation_path: "/#{project.full_path}/issues/#{issue.iid}/links/#{issue_link_b.id}"))
expect(subject).to include(include(id: issue_d.id,
iid: issue_d.iid,
title: issue_d.title,
state: issue_d.state,
path: "/#{project.full_path}/issues/#{issue_d.iid}",
project_path: issue_d.project.path,
namespace_full_path: issue_d.project.namespace.full_path,
destroy_relation_path: "/#{project.full_path}/issues/#{issue.iid}/links/#{issue_link_c.id}"))
end
end
context 'referencing a public project issue' do
let(:public_project) { create :empty_project, :public }
let(:issue_b) { create :issue, project: public_project }
let!(:issue_link) do
create(:issue_link, source: issue, target: issue_b)
end
it 'presents issue' do
expect(subject.size).to eq(1)
end
end
context 'referencing issue with removed relationships' do
context 'when referenced a deleted issue' do
let(:issue_b) { create :issue, project: project }
let!(:issue_link) do
create(:issue_link, source: issue, target: issue_b)
end
it 'ignores issue' do
issue_b.destroy!
is_expected.to eq([])
end
end
context 'when referenced an issue with deleted project' do
let(:issue_b) { create :issue, project: project }
let!(:issue_link) do
create(:issue_link, source: issue, target: issue_b)
end
it 'ignores issue' do
project.destroy!
is_expected.to eq([])
end
end
context 'when referenced an issue with deleted namespace' do
let(:issue_b) { create :issue, project: project }
let!(:issue_link) do
create(:issue_link, source: issue, target: issue_b)
end
it 'ignores issue' do
project.namespace.destroy!
is_expected.to eq([])
end
end
end
context 'user cannot see relations' do
context 'when user cannot see the referenced issue' do
let!(:issue_link) do
create(:issue_link, source: issue)
end
it 'returns an empty list' do
is_expected.to eq([])
end
end
context 'when user cannot see the issue that referenced' do
let!(:issue_link) do
create(:issue_link, target: issue)
end
it 'returns an empty list' do
is_expected.to eq([])
end
end
end
context 'remove relations' do
let!(:issue_link) do
create(:issue_link, source: issue, target: referenced_issue)
end
context 'user can admin related issues just on target project' do
let(:user_role) { :guest }
let(:target_project) { create :empty_project }
let(:referenced_issue) { create :issue, project: target_project }
it 'returns no destroy relation path' do
target_project.add_developer(user)
expect(subject.first[:destroy_relation_path]).to be_nil
end
end
context 'user can admin related issues just on source project' do
let(:user_role) { :developer }
let(:target_project) { create :empty_project }
let(:referenced_issue) { create :issue, project: target_project }
it 'returns no destroy relation path' do
target_project.add_guest(user)
expect(subject.first[:destroy_relation_path]).to be_nil
end
end
context 'when user can admin related issues on both projects' do
let(:referenced_issue) { create :issue, project: project }
it 'returns related issue destroy relation path' do
expect(subject.first[:destroy_relation_path])
.to eq("/#{project.full_path}/issues/#{issue.iid}/links/#{issue_link.id}")
end
end
end
end
end
......@@ -39,6 +39,7 @@ describe Issues::ExportCsvService, services: true do
due_date: DateTime.new(2014, 3, 2),
created_at: DateTime.new(2015, 4, 3, 2, 1, 0),
updated_at: DateTime.new(2016, 5, 4, 3, 2, 1),
closed_at: DateTime.new(2017, 6, 5, 4, 3, 2),
labels: [feature_label, idea_label])
end
......@@ -101,6 +102,10 @@ describe Issues::ExportCsvService, services: true do
specify 'updated_at' do
expect(csv[0]['Updated At (UTC)']).to eq '2016-05-04 03:02:01'
end
specify 'closed_at' do
expect(csv[0]['Closed At (UTC)']).to eq '2017-06-05 04:03:02'
end
end
context 'with minimal details' do
......
......@@ -899,6 +899,38 @@ describe SystemNoteService, services: true do
end
end
describe '.relate_issue' do
let(:noteable_ref) { create(:issue) }
subject { described_class.relate_issue(noteable, noteable_ref, author) }
it_behaves_like 'a system note' do
let(:action) { 'relate' }
end
context 'when issue marks another as related' do
it 'sets the note text' do
expect(subject.note).to eq "marked this issue as related to #{noteable_ref.to_reference(project)}"
end
end
end
describe '.unrelate_issue' do
let(:noteable_ref) { create(:issue) }
subject { described_class.unrelate_issue(noteable, noteable_ref, author) }
it_behaves_like 'a system note' do
let(:action) { 'unrelate' }
end
context 'when issue relation is removed' do
it 'sets the note text' do
expect(subject.note).to eq "removed the relation with #{noteable_ref.to_reference(project)}"
end
end
end
describe '.approve_mr' do
let(:noteable) { create(:merge_request, source_project: project) }
subject { described_class.approve_mr(noteable, author) }
......
......@@ -13,10 +13,10 @@ describe GeoFileDownloadDispatchWorker do
subject { described_class.new }
describe '#perform' do
it 'does not schedule anything when tracking DB is not available' do
it 'does not schedule anything when secondary role is disabled' do
create(:lfs_object, :with_file)
allow(Rails.configuration).to receive(:respond_to?).with(:geo_database) { false }
allow(Gitlab::Geo).to receive(:secondary_role_enabled?) { false }
expect(GeoFileDownloadWorker).not_to receive(:perform_async)
......
......@@ -52,8 +52,8 @@ describe GeoRepositorySyncWorker do
subject.perform
end
it 'does not perform Geo::RepositorySyncService when tracking DB is not available' do
allow(Rails.configuration).to receive(:respond_to?).with(:geo_database) { false }
it 'does not perform Geo::RepositorySyncService when secondary role is disabled' do
allow(Gitlab::Geo).to receive(:secondary_role_enabled?) { false }
expect(Geo::RepositorySyncService).not_to receive(:new)
......
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