Commit bd62294d authored by GitLab Bot's avatar GitLab Bot

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

# Conflicts:
#	app/assets/javascripts/jobs/components/job_app.vue
#	doc/api/groups.md
#	doc/user/group/index.md
#	spec/models/project_spec.rb

[ci skip]
parents f99e2977 a310a638
11.4.0-pre 11.5.0-pre
...@@ -5,9 +5,12 @@ ...@@ -5,9 +5,12 @@
import bp from '~/breakpoints'; import bp from '~/breakpoints';
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 createStore from '../store'; import createStore from '../store';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import EnvironmentsBlock from './environments_block.vue'; import EnvironmentsBlock from './environments_block.vue';
...@@ -29,7 +32,10 @@ ...@@ -29,7 +32,10 @@
Log, Log,
LogTopBar, LogTopBar,
StuckBlock, StuckBlock,
<<<<<<< HEAD
SharedRunner, SharedRunner,
=======
>>>>>>> upstream/master
Sidebar, Sidebar,
}, },
props: { props: {
...@@ -82,7 +88,10 @@ ...@@ -82,7 +88,10 @@
'shouldRenderTriggeredLabel', 'shouldRenderTriggeredLabel',
'hasEnvironment', 'hasEnvironment',
'isJobStuck', 'isJobStuck',
<<<<<<< HEAD
'shouldRenderSharedRunnerLimitWarning', 'shouldRenderSharedRunnerLimitWarning',
=======
>>>>>>> upstream/master
'hasTrace', 'hasTrace',
'emptyStateIllustration', 'emptyStateIllustration',
'isScrollingDown', 'isScrollingDown',
......
...@@ -419,7 +419,7 @@ export default class MergeRequestTabs { ...@@ -419,7 +419,7 @@ export default class MergeRequestTabs {
if (this.diffViewType() === 'parallel' || removeLimited) { if (this.diffViewType() === 'parallel' || removeLimited) {
$wrapper.removeClass('container-limited'); $wrapper.removeClass('container-limited');
} else { } else {
$wrapper.addClass('container-limited'); $wrapper.toggleClass('container-limited', this.fixedLayoutPref);
} }
} }
......
...@@ -52,6 +52,7 @@ ...@@ -52,6 +52,7 @@
@import 'framework/blank'; @import 'framework/blank';
@import 'framework/wells'; @import 'framework/wells';
@import 'framework/page_header'; @import 'framework/page_header';
@import 'framework/page_title';
@import 'framework/awards'; @import 'framework/awards';
@import 'framework/images'; @import 'framework/images';
@import 'framework/broadcast_messages'; @import 'framework/broadcast_messages';
......
...@@ -530,7 +530,6 @@ ...@@ -530,7 +530,6 @@
.header-user { .header-user {
&.show .dropdown-menu { &.show .dropdown-menu {
max-height: 323px;
margin-top: 4px; margin-top: 4px;
color: $gl-text-color; color: $gl-text-color;
left: auto; left: auto;
...@@ -542,15 +541,19 @@ ...@@ -542,15 +541,19 @@
display: block; display: block;
} }
.user-status-emoji { .user-status {
margin-right: 0; margin-right: 0;
display: block;
vertical-align: text-top;
max-width: 240px; max-width: 240px;
font-size: 12px; font-size: $gl-font-size-small;
gl-emoji { gl-emoji {
font-size: $gl-font-size; font-size: $gl-font-size-small;
}
.user-status-emoji {
gl-emoji {
font-size: $gl-font-size;
}
} }
} }
} }
......
.page-title-holder {
@extend .d-flex;
@extend .align-items-center;
padding-top: $gl-padding-top;
border-bottom: 1px solid $border-color;
.page-title {
margin: $gl-padding 0;
font-size: 1.75em;
font-weight: $gl-font-weight-bold;
color: $gl-text-color;
}
.page-title-controls {
margin-left: auto;
}
}
...@@ -195,6 +195,7 @@ $well-light-text-color: #5b6169; ...@@ -195,6 +195,7 @@ $well-light-text-color: #5b6169;
* Text * Text
*/ */
$gl-font-size: 14px; $gl-font-size: 14px;
$gl-font-size-small: 12px;
$gl-font-weight-normal: 400; $gl-font-weight-normal: 400;
$gl-font-weight-bold: 600; $gl-font-weight-bold: 600;
$gl-text-color: #2e2e2e; $gl-text-color: #2e2e2e;
......
...@@ -18,10 +18,15 @@ module Boards ...@@ -18,10 +18,15 @@ module Boards
list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params) list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params)
issues = list_service.execute issues = list_service.execute
issues = issues.page(params[:page]).per(params[:per] || 20).without_count issues = issues.page(params[:page]).per(params[:per] || 20).without_count
make_sure_position_is_set(issues) if Gitlab::Database.read_write? Issue.move_to_end(issues) if Gitlab::Database.read_write?
issues = issues.preload(:project, issues = issues.preload(:milestone,
:milestone,
:assignees, :assignees,
project: [
:route,
{
namespace: [:route]
}
],
labels: [:priorities], labels: [:priorities],
notes: [:award_emoji, :author] notes: [:award_emoji, :author]
) )
...@@ -60,12 +65,6 @@ module Boards ...@@ -60,12 +65,6 @@ module Boards
render json: data render json: data
end end
def make_sure_position_is_set(issues)
issues.each do |issue|
issue.move_to_end && issue.save unless issue.relative_position
end
end
def issue def issue
@issue ||= issues_finder.find(params[:id]) @issue ||= issues_finder.find(params[:id])
end end
......
...@@ -42,7 +42,7 @@ module CiStatusHelper ...@@ -42,7 +42,7 @@ module CiStatusHelper
when 'manual' when 'manual'
s_('CiStatusText|blocked') s_('CiStatusText|blocked')
when 'scheduled' when 'scheduled'
s_('CiStatusText|scheduled') s_('CiStatusText|delayed')
else else
# All states are already being translated inside the detailed statuses: # All states are already being translated inside the detailed statuses:
# :running => Gitlab::Ci::Status::Running # :running => Gitlab::Ci::Status::Running
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Clusters module Clusters
module Applications module Applications
class Runner < ActiveRecord::Base class Runner < ActiveRecord::Base
VERSION = '0.1.31'.freeze VERSION = '0.1.34'.freeze
self.table_name = 'clusters_applications_runners' self.table_name = 'clusters_applications_runners'
......
...@@ -12,6 +12,49 @@ module RelativePositioning ...@@ -12,6 +12,49 @@ module RelativePositioning
after_save :save_positionable_neighbours after_save :save_positionable_neighbours
end end
class_methods do
def move_to_end(objects)
parent_ids = objects.map(&:parent_ids).flatten.uniq
max_relative_position = in_parents(parent_ids).maximum(:relative_position) || START_POSITION
objects = objects.reject(&:relative_position)
self.transaction do
objects.each do |object|
relative_position = position_between(max_relative_position, MAX_POSITION)
object.relative_position = relative_position
max_relative_position = relative_position
object.save
end
end
end
# This method takes two integer values (positions) and
# calculates the position between them. The range is huge as
# the maximum integer value is 2147483647. We are incrementing position by IDEAL_DISTANCE * 2 every time
# when we have enough space. If distance is less then IDEAL_DISTANCE we are calculating an average number
def position_between(pos_before, pos_after)
pos_before ||= MIN_POSITION
pos_after ||= MAX_POSITION
pos_before, pos_after = [pos_before, pos_after].sort
halfway = (pos_after + pos_before) / 2
distance_to_halfway = pos_after - halfway
if distance_to_halfway < IDEAL_DISTANCE
halfway
else
if pos_before == MIN_POSITION
pos_after - IDEAL_DISTANCE
elsif pos_after == MAX_POSITION
pos_before + IDEAL_DISTANCE
else
halfway
end
end
end
end
def min_relative_position def min_relative_position
self.class.in_parents(parent_ids).minimum(:relative_position) self.class.in_parents(parent_ids).minimum(:relative_position)
end end
...@@ -57,7 +100,7 @@ module RelativePositioning ...@@ -57,7 +100,7 @@ module RelativePositioning
@positionable_neighbours = [before] # rubocop:disable Gitlab/ModuleWithInstanceVariables @positionable_neighbours = [before] # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
self.relative_position = position_between(before.relative_position, after.relative_position) self.relative_position = self.class.position_between(before.relative_position, after.relative_position)
end end
def move_after(before = self) def move_after(before = self)
...@@ -72,7 +115,7 @@ module RelativePositioning ...@@ -72,7 +115,7 @@ module RelativePositioning
pos_after = issue_to_move.relative_position pos_after = issue_to_move.relative_position
end end
self.relative_position = position_between(pos_before, pos_after) self.relative_position = self.class.position_between(pos_before, pos_after)
end end
def move_before(after = self) def move_before(after = self)
...@@ -87,15 +130,15 @@ module RelativePositioning ...@@ -87,15 +130,15 @@ module RelativePositioning
pos_before = issue_to_move.relative_position pos_before = issue_to_move.relative_position
end end
self.relative_position = position_between(pos_before, pos_after) self.relative_position = self.class.position_between(pos_before, pos_after)
end end
def move_to_end def move_to_end
self.relative_position = position_between(max_relative_position || START_POSITION, MAX_POSITION) self.relative_position = self.class.position_between(max_relative_position || START_POSITION, MAX_POSITION)
end end
def move_to_start def move_to_start
self.relative_position = position_between(min_relative_position || START_POSITION, MIN_POSITION) self.relative_position = self.class.position_between(min_relative_position || START_POSITION, MIN_POSITION)
end end
# Indicates if there is an issue that should be shifted to free the place # Indicates if there is an issue that should be shifted to free the place
...@@ -112,32 +155,6 @@ module RelativePositioning ...@@ -112,32 +155,6 @@ module RelativePositioning
private private
# This method takes two integer values (positions) and
# calculates the position between them. The range is huge as
# the maximum integer value is 2147483647. We are incrementing position by IDEAL_DISTANCE * 2 every time
# when we have enough space. If distance is less then IDEAL_DISTANCE we are calculating an average number
def position_between(pos_before, pos_after)
pos_before ||= MIN_POSITION
pos_after ||= MAX_POSITION
pos_before, pos_after = [pos_before, pos_after].sort
halfway = (pos_after + pos_before) / 2
distance_to_halfway = pos_after - halfway
if distance_to_halfway < IDEAL_DISTANCE
halfway
else
if pos_before == MIN_POSITION
pos_after - IDEAL_DISTANCE
elsif pos_after == MAX_POSITION
pos_before + IDEAL_DISTANCE
else
halfway
end
end
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
def save_positionable_neighbours def save_positionable_neighbours
return unless @positionable_neighbours return unless @positionable_neighbours
......
...@@ -17,6 +17,7 @@ class List < ActiveRecord::Base ...@@ -17,6 +17,7 @@ class List < ActiveRecord::Base
scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) } scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) }
scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) } scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) }
scope :preload_associations, -> { preload(:board, :label) }
class << self class << self
def destroyable_types def destroyable_types
......
...@@ -80,13 +80,18 @@ class BambooService < CiService ...@@ -80,13 +80,18 @@ class BambooService < CiService
private private
def get_build_result_index
# When Bamboo returns multiple results for a given changeset, arbitrarily assume the most relevant result to be the last one.
-1
end
def read_build_page(response) def read_build_page(response)
if response.code != 200 || response['results']['results']['size'] == '0' if response.code != 200 || response.dig('results', 'results', 'size') == '0'
# If actual build link can't be determined, send user to build summary page. # If actual build link can't be determined, send user to build summary page.
URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s
else else
# If actual build link is available, go to build result page. # If actual build link is available, go to build result page.
result_key = response['results']['results']['result']['planResultKey']['key'] result_key = response.dig('results', 'results', 'result', get_build_result_index, 'planResultKey', 'key')
URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s
end end
end end
...@@ -94,10 +99,10 @@ class BambooService < CiService ...@@ -94,10 +99,10 @@ class BambooService < CiService
def read_commit_status(response) def read_commit_status(response)
return :error unless response.code == 200 || response.code == 404 return :error unless response.code == 200 || response.code == 404
status = if response.code == 404 || response['results']['results']['size'] == '0' status = if response.code == 404 || response.dig('results', 'results', 'size') == '0'
'Pending' 'Pending'
else else
response['results']['results']['result']['buildState'] response.dig('results', 'results', 'result', get_build_result_index, 'buildState')
end end
if status.include?('Success') if status.include?('Success')
......
...@@ -8,7 +8,7 @@ module Boards ...@@ -8,7 +8,7 @@ module Boards
def execute(board) def execute(board)
board.lists.create(list_type: :backlog) unless board.lists.backlog.exists? board.lists.create(list_type: :backlog) unless board.lists.backlog.exists?
board.lists board.lists.preload_associations
end end
end end
end end
......
...@@ -644,7 +644,7 @@ module QuickActions ...@@ -644,7 +644,7 @@ module QuickActions
if users.empty? if users.empty?
users = users =
if params == 'me' if params.strip == 'me'
[current_user] [current_user]
else else
User.where(username: params.split(' ').map(&:strip)) User.where(username: params.split(' ').map(&:strip))
......
.page-title-holder
%h1.page-title= _('Activity')
.top-area .top-area
%ul.nav-links.nav.nav-tabs %ul.nav-links.nav.nav-tabs
%li{ class: active_when(params[:filter].nil?) }> %li{ class: active_when(params[:filter].nil?) }>
......
.page-title-holder
%h1.page-title= _('Groups')
- if current_user.can_create_group?
.page-title-controls
= link_to _("New group"), new_group_path, class: "btn btn-success"
.top-area .top-area
%ul.nav-links.mobile-separator.nav.nav-tabs %ul.nav-links.mobile-separator.nav.nav-tabs
= nav_link(page: dashboard_groups_path) do = nav_link(page: dashboard_groups_path) do
...@@ -9,5 +16,3 @@ ...@@ -9,5 +16,3 @@
.nav-controls .nav-controls
= render 'shared/groups/search_form' = render 'shared/groups/search_form'
= render 'shared/groups/dropdown' = render 'shared/groups/dropdown'
- if current_user.can_create_group?
= link_to _("New group"), new_group_path, class: "btn btn-success"
= content_for :flash_message do = content_for :flash_message do
= render 'shared/project_limit' = render 'shared/project_limit'
.page-title-holder
%h1.page-title= _('Projects')
- if current_user.can_create_project?
.page-title-controls
= link_to "New project", new_project_path, class: "btn btn-success"
.top-area.scrolling-tabs-container.inner-page-scroll-tabs .top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left') .fade-left= icon('angle-left')
.fade-right= icon('angle-right') .fade-right= icon('angle-right')
...@@ -18,5 +25,3 @@ ...@@ -18,5 +25,3 @@
.nav-controls .nav-controls
= render 'shared/projects/search_form' = render 'shared/projects/search_form'
= render 'shared/projects/dropdown' = render 'shared/projects/dropdown'
- if current_user.can_create_project?
= link_to "New project", new_project_path, class: "btn btn-success"
.page-title-holder
%h1.page-title= _('Snippets')
- if current_user
.page-title-controls
= link_to "New snippet", new_snippet_path, class: "btn btn-success", title: "New snippet"
.top-area .top-area
%ul.nav-links.nav.nav-tabs %ul.nav-links.nav.nav-tabs
= nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do
...@@ -6,7 +13,3 @@ ...@@ -6,7 +13,3 @@
= nav_link(page: explore_snippets_path) do = nav_link(page: explore_snippets_path) do
= link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
Explore snippets Explore snippets
- if current_user
.nav-controls.d-none.d-sm-block
= link_to "New snippet", new_snippet_path, class: "btn btn-success", title: "New snippet"
...@@ -4,11 +4,17 @@ ...@@ -4,11 +4,17 @@
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues") = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
.page-title-holder
%h1.page-title= _('Issues')
- if current_user
.page-title-controls
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
.top-area .top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set = render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls .nav-controls
= render 'shared/issuable/feed_buttons' = render 'shared/issuable/feed_buttons'
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
......
...@@ -2,10 +2,15 @@ ...@@ -2,10 +2,15 @@
- page_title _("Merge Requests") - page_title _("Merge Requests")
- @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id) - @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id)
.page-title-holder
%h1.page-title= _('Merge Requests')
- if current_user
.page-title-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set = render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set
.nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
......
...@@ -2,12 +2,18 @@ ...@@ -2,12 +2,18 @@
- page_title 'Milestones' - page_title 'Milestones'
- header_title 'Milestones', dashboard_milestones_path - header_title 'Milestones', dashboard_milestones_path
.page-title-holder
%h1.page-title= _('Milestones')
- if current_user
.page-title-controls
= render 'shared/new_project_item_select',
path: 'milestones/new', label: 'New milestone',
include_groups: true, type: :milestones
.top-area .top-area
= render 'shared/milestones_filter', counts: @milestone_states = render 'shared/milestones_filter', counts: @milestone_states
.nav-controls
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
.milestones .milestones
%ul.content-list %ul.content-list
- if @milestones.blank? - if @milestones.blank?
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
- page_title "Todos" - page_title "Todos"
- header_title "Todos", dashboard_todos_path - header_title "Todos", dashboard_todos_path
.page-title-holder
%h1.page-title= _('Todos')
- if current_user.todos.any? - if current_user.todos.any?
.top-area .top-area
%ul.nav-links.mobile-separator.nav.nav-tabs %ul.nav-links.mobile-separator.nav.nav-tabs
......
- @breadcrumb_link = dashboard_groups_path - @hide_breadcrumbs = true
- breadcrumb_title "Groups"
- @hide_top_links = true - @hide_top_links = true
- page_title 'New Group' - page_title 'New Group'
- header_title "Groups", dashboard_groups_path - header_title "Groups", dashboard_groups_path
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
- unless @hide_breadcrumbs - unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs" = render "layouts/nav/breadcrumbs"
= render "layouts/flash" = render "layouts/flash"
.d-flex
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" } %div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" } .content{ id: "content-body" }
= yield = yield
- page_title _("Dashboard") - page_title _("Dashboard")
- header_title _("Dashboard"), root_path unless header_title - header_title _("Dashboard"), root_path unless header_title
- sidebar "dashboard" - sidebar "dashboard"
- @hide_breadcrumbs = true
= render template: "layouts/application" = render template: "layouts/application"
- page_title _("Explore") - page_title _("Explore")
- @hide_breadcrumbs = true
- unless current_user - unless current_user
- header_title _("Explore GitLab"), explore_root_path - header_title _("Explore GitLab"), explore_root_path
......
...@@ -6,9 +6,11 @@ ...@@ -6,9 +6,11 @@
= current_user.name = current_user.name
= current_user.to_reference = current_user.to_reference
- if current_user.status - if current_user.status
.user-status-emoji.str-truncated.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } } .user-status.d-flex.align-items-center.prepend-top-2.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } }
= emoji_icon current_user.status.emoji %span.user-status-emoji.d-flex.align-items-center
= current_user.status.message_html.html_safe = emoji_icon current_user.status.emoji
%span.user-status-message.str-truncated
= current_user.status.message_html.html_safe
%li.divider %li.divider
- if can?(current_user, :update_user_status, current_user) - if can?(current_user, :update_user_status, current_user)
%li %li
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
- if job.try(:allow_failure) - if job.try(:allow_failure)
%span.badge.badge-danger allowed to fail %span.badge.badge-danger allowed to fail
- if job.schedulable? - if job.schedulable?
%span.badge.badge-info= s_('DelayedJobs|scheduled') %span.badge.badge-info= s_('DelayedJobs|delayed')
- elsif job.action? - elsif job.action?
%span.badge.badge-info manual %span.badge.badge-info manual
......
- @breadcrumb_link = dashboard_projects_path - @hide_breadcrumbs = true
- breadcrumb_title "Projects"
- @hide_top_links = true - @hide_top_links = true
- page_title 'New Project' - page_title 'New Project'
- header_title "Projects", dashboard_projects_path - header_title "Projects", dashboard_projects_path
......
- @hide_top_links = true - @hide_top_links = true
- add_to_breadcrumbs "Snippets", dashboard_snippets_path - @hide_breadcrumbs = true
- breadcrumb_title "New"
- page_title "New Snippet" - page_title "New Snippet"
%h3.page-title
New Snippet .page-title-holder
%hr %h1.page-title= _('New Snippet')
= render "shared/snippets/form", url: snippets_path(@snippet)
.prepend-top-default
= render "shared/snippets/form", url: snippets_path(@snippet)
---
title: "Correctly process Bamboo API result array"
merge_request: 21970
author: Alex Lossent
type: fixed
\ No newline at end of file
---
title: Change single-item breadcrumbs to page titles
merge_request: 22155
author:
type: changed
---
title: Fix size of emojis of user status in user menu
merge_request: 22194
author:
type: fixed
---
title: Resolve assign-me quick action doesn't work if there is extra white space
merge_request: 22402
author:
type: fixed
---
title: Add preload for routes and namespaces for issues controller.
merge_request: 21651
author:
type: performance
---
title: Fix transient spec error in the bar_chart component
merge_request: 22495
author:
type: fixed
---
title: Fixed merge request fill tree toggling not respecting fluid width preference
merge_request:
author:
type: fixed
---
title: Rename "scheduled" label/badge of delayed jobs to "delayed"
merge_request: 22245
author:
type: changed
---
title: Update used version of Runner Helm Chart to 0.1.34
merge_request: 22274
author:
type: other
...@@ -8,7 +8,7 @@ const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); ...@@ -8,7 +8,7 @@ const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const ROOT_PATH = path.resolve(__dirname, '..'); const ROOT_PATH = path.resolve(__dirname, '..');
const CACHE_PATH = path.join(ROOT_PATH, 'tmp/cache'); const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache');
const IS_PRODUCTION = process.env.NODE_ENV === 'production'; const IS_PRODUCTION = process.env.NODE_ENV === 'production';
const IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1; const IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1;
const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost'; const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
......
...@@ -452,7 +452,10 @@ PUT /groups/:id ...@@ -452,7 +452,10 @@ PUT /groups/:id
| `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group | | `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group |
| `request_access_enabled` | boolean | no | Allow users to request member access. | | `request_access_enabled` | boolean | no | Allow users to request member access. |
| `file_template_project_id` | integer | no | **(Premium)** The ID of a project to load custom file templates from | | `file_template_project_id` | integer | no | **(Premium)** The ID of a project to load custom file templates from |
<<<<<<< HEAD
| `shared_runners_minutes_limit` | integer | no | (admin-only) Pipeline minutes quota for this group | | `shared_runners_minutes_limit` | integer | no | (admin-only) Pipeline minutes quota for this group |
=======
>>>>>>> upstream/master
```bash ```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/5?name=Experimental" curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/5?name=Experimental"
......
...@@ -340,8 +340,7 @@ Example response: ...@@ -340,8 +340,7 @@ Example response:
## List project's runners ## List project's runners
List all runners (specific and shared) available in the project. Shared runners List all runners (specific and shared) available in the project. Shared runners
are listed if at least one shared runner is defined **and** shared runners are listed if at least one shared runner is defined.
usage is enabled in the project's settings.
``` ```
GET /projects/:id/runners GET /projects/:id/runners
......
...@@ -39,7 +39,7 @@ few important details: ...@@ -39,7 +39,7 @@ few important details:
In the following example, kaniko is used to build a Docker image and then push In the following example, kaniko is used to build a Docker image and then push
it to [GitLab Container Registry](../../user/project/container_registry.md). it to [GitLab Container Registry](../../user/project/container_registry.md).
The job will run only when a tag is pushed. A `config.json` file is created under The job will run only when a tag is pushed. A `config.json` file is created under
`/root/.docker` with the needed GitLab Container Registry credentials taken from the `/kaniko/.docker` with the needed GitLab Container Registry credentials taken from the
[environment variables](../variables/README.md#predefined-variables-environment-variables) [environment variables](../variables/README.md#predefined-variables-environment-variables)
GitLab CI/CD provides. In the last step, kaniko uses the `Dockerfile` under the GitLab CI/CD provides. In the last step, kaniko uses the `Dockerfile` under the
root directory of the project, builds the Docker image and pushes it to the root directory of the project, builds the Docker image and pushes it to the
...@@ -52,8 +52,7 @@ build: ...@@ -52,8 +52,7 @@ build:
name: gcr.io/kaniko-project/executor:debug name: gcr.io/kaniko-project/executor:debug
entrypoint: [""] entrypoint: [""]
script: script:
- mkdir -p /root/.docker - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /root/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
only: only:
- tags - tags
......
...@@ -209,6 +209,130 @@ it 'is overdue' do ...@@ -209,6 +209,130 @@ it 'is overdue' do
end end
``` ```
### Pristine test environments
The code exercised by a single GitLab test may access and modify many items of
data. Without careful preparation before a test runs, and cleanup afterward,
data can be changed by a test in such a way that it affects the behaviour of
following tests. This should be avoided at all costs! Fortunately, the existing
test framework handles most cases already.
When the test environment does get polluted, a common outcome is
[flaky tests](flaky_tests.md). Pollution will often manifest as an order
dependency: running spec A followed by spec B will reliably fail, but running
spec B followed by spec A will reliably succeed. In these cases, you can use
`rspec --bisect` (or a manual pairwise bisect of spec files) to determine which
spec is at fault. Fixing the problem requires some understanding of how the test
suite ensures the environment is pristine. Read on to discover more about each
data store!
#### SQL database
This is managed for us by the `database_cleaner` gem. Each spec is surrounded in
a transaction, which is rolled back once the test completes. Certain specs will
instead issue `DELETE FROM` queries against every table after completion; this
allows the created rows to be viewed from multiple database connections, which
is important for specs that run in a browser, or migration specs, among others.
One consequence of using these strategies, instead of the well-known
`TRUNCATE TABLES` approach, is that primary keys and other sequences are **not**
reset across specs. So if you create a project in spec A, then create a project
in spec B, the first will have `id=1`, while the second will have `id=2`.
This means that specs should **never** rely on the value of an ID, or any other
sequence-generated column. To avoid accidental conflicts, specs should also
avoid manually specifying any values in these kinds of columns. Instead, leave
them unspecified, and look up the value after the row is created.
#### Redis
GitLab stores two main categories of data in Redis: cached items, and sidekiq
jobs.
In most specs, the Rails cache is actually an in-memory store. This is replaced
between specs, so calls to `Rails.cache.read` and `Rails.cache.write` are safe.
However, if a spec makes direct Redis calls, it should mark itself with the
`:clean_gitlab_redis_cache`, `:clean_gitlab_redis_shared_state` or
`:clean_gitlab_redis_queues` traits as appropriate.
Sidekiq jobs are typically not run in specs, but this behaviour can be altered
in each spec through the use of `Sidekiq::Testing.inline!` blocks. Any spec that
causes Sidekiq jobs to be pushed to Redis should use the `:sidekiq` trait, to
ensure that they are removed once the spec completes.
#### Filesystem
Filesystem data can be roughly split into "repositories", and "everything else".
Repositories are stored in `tmp/tests/repositories`. This directory is emptied
before a test run starts, and after the test run ends. It is not emptied between
specs, so created repositories accumulate within this directory over the
lifetime of the process. Deleting them is expensive, but this could lead to
pollution unless carefully managed.
To avoid this, [hashed storage](../../administration/repository_storage_types.md)
is enabled in the test suite. This means that repositories are given a unique
path that depends on their project's ID. Since the project IDs are not reset
between specs, this guarantees that each spec gets its own repository on disk,
and prevents changes from being visible between specs.
If a spec manually specifies a project ID, or inspects the state of the
`tmp/tests/repositories/` directory directly, then it should clean up the
directory both before and after it runs. In general, these patterns should be
completely avoided.
Other classes of file linked to database objects, such as uploads, are generally
managed in the same way. With hashed storage enabled in the specs, they are
written to disk in locations determined by ID, so conflicts should not occur.
Some specs disable hashed storage by passing the `:legacy_storage` trait to the
`projects` factory. Specs that do this must **never** override the `path` of the
project, or any of its groups. The default path includes the project ID, so will
not conflict; but if two specs create a `:legacy_storage` project with the same
path, they will use the same repository on disk and lead to test environment
pollution.
Other files must be managed manually by the spec. If you run code that creates a
`tmp/test-file.csv` file, for instance, the spec must ensure that the file is
removed as part of cleanup.
#### Persistent in-memory application state
All the specs in a given `rspec` run share the same Ruby process, which means
they can affect each other by modifying Ruby objects that are accessible between
specs. In practice, this means global variables, and constants (which includes
Ruby classes, modules, etc).
Global variables should generally not be modified. If absolutely necessary, a
block like this can be used to ensure the change is rolled back afterwards:
```ruby
around(:each) do |example|
old_value = $0
begin
$0 = "new-value"
example.run
ensure
$0 = old_value
end
end
```
If a spec needs to modify a constant, it should use the `stub_const` helper to
ensure the change is rolled back.
If you need to modify the contents of the `ENV` constant, you can use the
`stub_env` helper method instead.
While most Ruby **instances** are not shared between specs, **classes**
and **modules** generally are. Class and module instance variables, accessors,
class variables, and other stateful idioms, should be treated in the same way as
global variables - don't modify them unless you have to! In particular, prefer
using expectations, or dependency injection along with stubs, to avoid the need
for modifications. If you have no other choice, an `around` block similar to the
example for global variables, above, can be used, but this should be avoided if
at all possible.
### Table-based / Parameterized tests ### Table-based / Parameterized tests
This style of testing is used to exercise one piece of code with a comprehensive This style of testing is used to exercise one piece of code with a comprehensive
......
...@@ -281,6 +281,7 @@ Member Lock lets a group owner to lock down any new project membership to all th ...@@ -281,6 +281,7 @@ Member Lock lets a group owner to lock down any new project membership to all th
projects within the group, allowing tighter control over project membership. projects within the group, allowing tighter control over project membership.
Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#member-lock). Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#member-lock).
<<<<<<< HEAD
For instance, if you want to lock the group for an [Audit Event](../../administration/audit_events.md), For instance, if you want to lock the group for an [Audit Event](../../administration/audit_events.md),
you enable Member Lock to guarantee that any membership is added or changed you enable Member Lock to guarantee that any membership is added or changed
during the audition. during the audition.
...@@ -317,6 +318,14 @@ To enable this feature, navigate to the group settings page, expand the ...@@ -317,6 +318,14 @@ To enable this feature, navigate to the group settings page, expand the
**Save group**. **Save group**.
![Group-level file template settings](img/group_file_template_settings.png) ![Group-level file template settings](img/group_file_template_settings.png)
=======
#### Group-level file templates **[PREMIUM]**
Group-level file templates allow you to share a set of templates for common file
types with every project in a group.
Learn more about [Group-level file templates](https://docs.gitlab.com/ee/user/group/index.html#group-level-file-templates-premium).
>>>>>>> upstream/master
### Advanced settings ### Advanced settings
......
...@@ -24,6 +24,7 @@ discussions, and descriptions: ...@@ -24,6 +24,7 @@ discussions, and descriptions:
| `/reopen` | Reopen | ✓ | ✓ | | `/reopen` | Reopen | ✓ | ✓ |
| `/title <New title>` | Change title | ✓ | ✓ | | `/title <New title>` | Change title | ✓ | ✓ |
| `/award :emoji:` | Toggle emoji award | ✓ | ✓ | | `/award :emoji:` | Toggle emoji award | ✓ | ✓ |
| `/assign me` | Assign yourself | ✓ | ✓ |
| `/assign @user` | Assign one user | ✓ | ✓ | | `/assign @user` | Assign one user | ✓ | ✓ |
| `/assign @user1 @user2` | Assign multiple users **[STARTER]** | ✓ | | | `/assign @user1 @user2` | Assign multiple users **[STARTER]** | ✓ | |
| `/unassign` | Remove assignee(s) | ✓ | ✓ | | `/unassign` | Remove assignee(s) | ✓ | ✓ |
......
...@@ -71,12 +71,6 @@ describe('Bar chart component', () => { ...@@ -71,12 +71,6 @@ describe('Bar chart component', () => {
expect(barChart.xAxisLocation).toEqual('translate(100, 250)'); expect(barChart.xAxisLocation).toEqual('translate(100, 250)');
}); });
it('Contains a total of 4 ticks across the y axis', () => {
const ticks = barChart.$el.querySelector('.y-axis').querySelectorAll('.tick').length;
expect(ticks).toEqual(4);
});
it('rotates the x axis labels a total of 90 degress (CCW)', () => { it('rotates the x axis labels a total of 90 degress (CCW)', () => {
const xAxisLabel = barChart.$el.querySelector('.x-axis').querySelectorAll('text')[0]; const xAxisLabel = barChart.$el.querySelector('.x-axis').querySelectorAll('text')[0];
......
...@@ -151,7 +151,7 @@ module API ...@@ -151,7 +151,7 @@ module API
present build, with: Entities::Job present build, with: Entities::Job
end end
desc 'Trigger a actionable job (manual, scheduled, etc)' do desc 'Trigger a actionable job (manual, delayed, etc)' do
success Entities::Job success Entities::Job
detail 'This feature was added in GitLab 8.11' detail 'This feature was added in GitLab 8.11'
end end
......
...@@ -7,7 +7,7 @@ module Gitlab ...@@ -7,7 +7,7 @@ module Gitlab
{ {
image: 'illustrations/illustrations_scheduled-job_countdown.svg', image: 'illustrations/illustrations_scheduled-job_countdown.svg',
size: 'svg-394', size: 'svg-394',
title: _("This is a scheduled to run in ") + " #{execute_in}", title: _("This is a delayed to run in ") + " #{execute_in}",
content: _("This job will automatically run after it's timer finishes. " \ content: _("This job will automatically run after it's timer finishes. " \
"Often they are used for incremental roll-out deploys " \ "Often they are used for incremental roll-out deploys " \
"to production environments. When unscheduled it converts " \ "to production environments. When unscheduled it converts " \
...@@ -16,7 +16,7 @@ module Gitlab ...@@ -16,7 +16,7 @@ module Gitlab
end end
def status_tooltip def status_tooltip
"scheduled manual action (#{execute_in})" "delayed manual action (#{execute_in})"
end end
def self.matches?(build, user) def self.matches?(build, user)
......
...@@ -2,9 +2,9 @@ module Gitlab ...@@ -2,9 +2,9 @@ module Gitlab
module Ci module Ci
module Status module Status
module Pipeline module Pipeline
class Scheduled < Status::Extended class Delayed < Status::Extended
def text def text
s_('CiStatusText|scheduled') s_('CiStatusText|delayed')
end end
def label def label
......
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
class Factory < Status::Factory class Factory < Status::Factory
def self.extended_statuses def self.extended_statuses
[[Status::SuccessWarning, [[Status::SuccessWarning,
Status::Pipeline::Scheduled, Status::Pipeline::Delayed,
Status::Pipeline::Blocked]] Status::Pipeline::Blocked]]
end end
......
...@@ -3,11 +3,11 @@ module Gitlab ...@@ -3,11 +3,11 @@ module Gitlab
module Status module Status
class Scheduled < Status::Core class Scheduled < Status::Core
def text def text
s_('CiStatusText|scheduled') s_('CiStatusText|delayed')
end end
def label def label
s_('CiStatusLabel|scheduled') s_('CiStatusLabel|delayed')
end end
def icon def icon
......
...@@ -1476,6 +1476,9 @@ msgstr "" ...@@ -1476,6 +1476,9 @@ msgstr ""
msgid "CiStatusLabel|created" msgid "CiStatusLabel|created"
msgstr "" msgstr ""
msgid "CiStatusLabel|delayed"
msgstr ""
msgid "CiStatusLabel|failed" msgid "CiStatusLabel|failed"
msgstr "" msgstr ""
...@@ -1491,9 +1494,6 @@ msgstr "" ...@@ -1491,9 +1494,6 @@ msgstr ""
msgid "CiStatusLabel|pending" msgid "CiStatusLabel|pending"
msgstr "" msgstr ""
msgid "CiStatusLabel|scheduled"
msgstr ""
msgid "CiStatusLabel|skipped" msgid "CiStatusLabel|skipped"
msgstr "" msgstr ""
...@@ -1512,6 +1512,9 @@ msgstr "" ...@@ -1512,6 +1512,9 @@ msgstr ""
msgid "CiStatusText|created" msgid "CiStatusText|created"
msgstr "" msgstr ""
msgid "CiStatusText|delayed"
msgstr ""
msgid "CiStatusText|failed" msgid "CiStatusText|failed"
msgstr "" msgstr ""
...@@ -1524,9 +1527,6 @@ msgstr "" ...@@ -1524,9 +1527,6 @@ msgstr ""
msgid "CiStatusText|pending" msgid "CiStatusText|pending"
msgstr "" msgstr ""
msgid "CiStatusText|scheduled"
msgstr ""
msgid "CiStatusText|skipped" msgid "CiStatusText|skipped"
msgstr "" msgstr ""
...@@ -2548,7 +2548,7 @@ msgstr "" ...@@ -2548,7 +2548,7 @@ msgstr ""
msgid "DelayedJobs|Unschedule" msgid "DelayedJobs|Unschedule"
msgstr "" msgstr ""
msgid "DelayedJobs|scheduled" msgid "DelayedJobs|delayed"
msgstr "" msgstr ""
msgid "Delete" msgid "Delete"
...@@ -7835,7 +7835,7 @@ msgstr "" ...@@ -7835,7 +7835,7 @@ msgstr ""
msgid "This is a confidential issue." msgid "This is a confidential issue."
msgstr "" msgstr ""
msgid "This is a scheduled to run in " msgid "This is a delayed to run in "
msgstr "" msgstr ""
msgid "This is the author's first Merge Request to this project." msgid "This is the author's first Merge Request to this project."
......
...@@ -30,6 +30,15 @@ describe Boards::IssuesController do ...@@ -30,6 +30,15 @@ describe Boards::IssuesController do
context 'when list id is present' do context 'when list id is present' do
context 'with valid list id' do context 'with valid list id' do
let(:group) { create(:group, :private, projects: [project]) }
let(:group_board) { create(:board, group: group) }
let!(:list3) { create(:list, board: group_board, label: development, position: 2) }
let(:sub_group_1) { create(:group, :private, parent: group) }
before do
group.add_maintainer(user)
end
it 'returns issues that have the list label applied' do it 'returns issues that have the list label applied' do
issue = create(:labeled_issue, project: project, labels: [planning]) issue = create(:labeled_issue, project: project, labels: [planning])
create(:labeled_issue, project: project, labels: [planning]) create(:labeled_issue, project: project, labels: [planning])
...@@ -56,6 +65,39 @@ describe Boards::IssuesController do ...@@ -56,6 +65,39 @@ describe Boards::IssuesController do
expect { list_issues(user: user, board: board, list: list2) }.not_to exceed_query_limit(control_count) expect { list_issues(user: user, board: board, list: list2) }.not_to exceed_query_limit(control_count)
end end
it 'avoids N+1 database queries when adding a project', :request_store do
create(:labeled_issue, project: project, labels: [development])
control_count = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: group_board, list: list3) }.count
2.times do
p = create(:project, group: group)
create(:labeled_issue, project: p, labels: [development])
end
project_2 = create(:project, group: group)
create(:labeled_issue, project: project_2, labels: [development], assignees: [johndoe])
# because each issue without relative_position must be updated with
# a different value, we have 8 extra queries per issue
expect { list_issues(user: user, board: group_board, list: list3) }.not_to exceed_query_limit(control_count + (2 * 8 - 1))
end
it 'avoids N+1 database queries when adding a subgroup, project, and issue', :nested_groups do
create(:project, group: sub_group_1)
create(:labeled_issue, project: project, labels: [development])
control_count = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: group_board, list: list3) }.count
project_2 = create(:project, group: group)
2.times do
p = create(:project, group: sub_group_1)
create(:labeled_issue, project: p, labels: [development])
end
create(:labeled_issue, project: project_2, labels: [development], assignees: [johndoe])
expect { list_issues(user: user, board: group_board, list: list3) }.not_to exceed_query_limit(control_count + (2 * 8 - 1))
end
end end
context 'with invalid list id' do context 'with invalid list id' do
...@@ -102,12 +144,15 @@ describe Boards::IssuesController do ...@@ -102,12 +144,15 @@ describe Boards::IssuesController do
sign_in(user) sign_in(user)
params = { params = {
namespace_id: project.namespace.to_param,
project_id: project,
board_id: board.to_param, board_id: board.to_param,
list_id: list.try(:to_param) list_id: list.try(:to_param)
} }
unless board.try(:parent)&.is_a?(Group)
params[:namespace_id] = project.namespace.to_param
params[:project_id] = project
end
get :index, params.compact get :index, params.compact
end end
end end
......
...@@ -2,8 +2,12 @@ require 'spec_helper' ...@@ -2,8 +2,12 @@ require 'spec_helper'
describe 'Dashboard shortcuts', :js do describe 'Dashboard shortcuts', :js do
context 'logged in' do context 'logged in' do
let(:user) { create(:user) }
let(:project) { create(:project) }
before do before do
sign_in(create(:user)) project.add_developer(user)
sign_in(user)
visit root_dashboard_path visit root_dashboard_path
end end
...@@ -50,6 +54,6 @@ describe 'Dashboard shortcuts', :js do ...@@ -50,6 +54,6 @@ describe 'Dashboard shortcuts', :js do
end end
def check_page_title(title) def check_page_title(title)
expect(find('.breadcrumbs-sub-title')).to have_content(title) expect(find('.page-title')).to have_content(title)
end end
end end
...@@ -594,7 +594,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do ...@@ -594,7 +594,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end end
it 'shows delayed job', :js do it 'shows delayed job', :js do
expect(page).to have_content('This is a scheduled to run in') expect(page).to have_content('This is a delayed to run in')
expect(page).to have_content("This job will automatically run after it's timer finishes.") expect(page).to have_content("This job will automatically run after it's timer finishes.")
expect(page).to have_link('Unschedule job') expect(page).to have_link('Unschedule job')
end end
......
...@@ -224,6 +224,14 @@ describe('MergeRequestTabs', function() { ...@@ -224,6 +224,14 @@ describe('MergeRequestTabs', function() {
expect($('.content-wrapper')).not.toContainElement('.container-limited'); expect($('.content-wrapper')).not.toContainElement('.container-limited');
}); });
it('does not add container-limited when fluid layout is prefered', function() {
$('.content-wrapper .container-fluid').removeClass('container-limited');
this.class.expandViewContainer(false);
expect($('.content-wrapper')).not.toContainElement('.container-limited');
});
it('does remove container-limited from breadcrumbs', function() { it('does remove container-limited from breadcrumbs', function() {
$('.container-limited').addClass('breadcrumbs'); $('.container-limited').addClass('breadcrumbs');
this.class.expandViewContainer(); this.class.expandViewContainer();
......
...@@ -339,7 +339,7 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -339,7 +339,7 @@ describe Gitlab::Ci::Status::Build::Factory do
end end
it 'fabricates status with correct details' do it 'fabricates status with correct details' do
expect(status.text).to eq 'scheduled' expect(status.text).to eq 'delayed'
expect(status.group).to eq 'scheduled' expect(status.group).to eq 'scheduled'
expect(status.icon).to eq 'status_scheduled' expect(status.icon).to eq 'status_scheduled'
expect(status.favicon).to eq 'favicon_status_scheduled' expect(status.favicon).to eq 'favicon_status_scheduled'
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Scheduled do describe Gitlab::Ci::Status::Pipeline::Delayed do
let(:pipeline) { double('pipeline') } let(:pipeline) { double('pipeline') }
subject do subject do
...@@ -9,7 +9,7 @@ describe Gitlab::Ci::Status::Pipeline::Scheduled do ...@@ -9,7 +9,7 @@ describe Gitlab::Ci::Status::Pipeline::Scheduled do
describe '#text' do describe '#text' do
it 'overrides status text' do it 'overrides status text' do
expect(subject.text).to eq 'scheduled' expect(subject.text).to eq 'delayed'
end end
end end
......
...@@ -71,7 +71,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do ...@@ -71,7 +71,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
it 'matches a correct extended statuses' do it 'matches a correct extended statuses' do
expect(factory.extended_statuses) expect(factory.extended_statuses)
.to eq [Gitlab::Ci::Status::Pipeline::Scheduled] .to eq [Gitlab::Ci::Status::Pipeline::Delayed]
end end
it 'extends core status with common pipeline methods' do it 'extends core status with common pipeline methods' do
......
...@@ -6,11 +6,11 @@ describe Gitlab::Ci::Status::Scheduled do ...@@ -6,11 +6,11 @@ describe Gitlab::Ci::Status::Scheduled do
end end
describe '#text' do describe '#text' do
it { expect(subject.text).to eq 'scheduled' } it { expect(subject.text).to eq 'delayed' }
end end
describe '#label' do describe '#label' do
it { expect(subject.label).to eq 'scheduled' } it { expect(subject.label).to eq 'delayed' }
end end
describe '#icon' do describe '#icon' do
......
...@@ -17,7 +17,7 @@ describe Clusters::Applications::Runner do ...@@ -17,7 +17,7 @@ describe Clusters::Applications::Runner do
let(:application) { create(:clusters_applications_runner, :scheduled, version: '0.1.30') } let(:application) { create(:clusters_applications_runner, :scheduled, version: '0.1.30') }
it 'updates the application version' do it 'updates the application version' do
expect(application.reload.version).to eq('0.1.31') expect(application.reload.version).to eq('0.1.34')
end end
end end
end end
...@@ -45,7 +45,7 @@ describe Clusters::Applications::Runner do ...@@ -45,7 +45,7 @@ describe Clusters::Applications::Runner do
it 'should be initialized with 4 arguments' do it 'should be initialized with 4 arguments' do
expect(subject.name).to eq('runner') expect(subject.name).to eq('runner')
expect(subject.chart).to eq('runner/gitlab-runner') expect(subject.chart).to eq('runner/gitlab-runner')
expect(subject.version).to eq('0.1.31') expect(subject.version).to eq('0.1.34')
expect(subject).not_to be_rbac expect(subject).not_to be_rbac
expect(subject.repository).to eq('https://charts.gitlab.io') expect(subject.repository).to eq('https://charts.gitlab.io')
expect(subject.files).to eq(gitlab_runner.files) expect(subject.files).to eq(gitlab_runner.files)
...@@ -63,7 +63,7 @@ describe Clusters::Applications::Runner do ...@@ -63,7 +63,7 @@ describe Clusters::Applications::Runner do
let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') } let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') }
it 'should be initialized with the locked version' do it 'should be initialized with the locked version' do
expect(subject.version).to eq('0.1.31') expect(subject.version).to eq('0.1.34')
end end
end end
end end
......
...@@ -6,9 +6,13 @@ describe RelativePositioning do ...@@ -6,9 +6,13 @@ describe RelativePositioning do
let(:issue1) { create(:issue, project: project) } let(:issue1) { create(:issue, project: project) }
let(:new_issue) { create(:issue, project: project) } let(:new_issue) { create(:issue, project: project) }
before do describe '.move_to_end' do
[issue, issue1].each do |issue| it 'moves the object to the end' do
issue.move_to_end && issue.save Issue.move_to_end([issue, issue1])
expect(issue1.prev_relative_position).to eq issue.relative_position
expect(issue.prev_relative_position).to eq nil
expect(issue1.next_relative_position).to eq nil
end end
end end
...@@ -59,6 +63,12 @@ describe RelativePositioning do ...@@ -59,6 +63,12 @@ describe RelativePositioning do
end end
describe '#move_to_end' do describe '#move_to_end' do
before do
[issue, issue1].each do |issue|
issue.move_to_end && issue.save
end
end
it 'moves issue to the end' do it 'moves issue to the end' do
new_issue.move_to_end new_issue.move_to_end
...@@ -67,6 +77,12 @@ describe RelativePositioning do ...@@ -67,6 +77,12 @@ describe RelativePositioning do
end end
describe '#shift_after?' do describe '#shift_after?' do
before do
[issue, issue1].each do |issue|
issue.move_to_end && issue.save
end
end
it 'returns true' do it 'returns true' do
issue.update(relative_position: issue1.relative_position - 1) issue.update(relative_position: issue1.relative_position - 1)
...@@ -81,6 +97,12 @@ describe RelativePositioning do ...@@ -81,6 +97,12 @@ describe RelativePositioning do
end end
describe '#shift_before?' do describe '#shift_before?' do
before do
[issue, issue1].each do |issue|
issue.move_to_end && issue.save
end
end
it 'returns true' do it 'returns true' do
issue.update(relative_position: issue1.relative_position + 1) issue.update(relative_position: issue1.relative_position + 1)
...@@ -95,6 +117,12 @@ describe RelativePositioning do ...@@ -95,6 +117,12 @@ describe RelativePositioning do
end end
describe '#move_between' do describe '#move_between' do
before do
[issue, issue1].each do |issue|
issue.move_to_end && issue.save
end
end
it 'positions issue between two other' do it 'positions issue between two other' do
new_issue.move_between(issue, issue1) new_issue.move_between(issue, issue1)
......
...@@ -245,6 +245,7 @@ describe BambooService, :use_clean_rails_memory_store_caching do ...@@ -245,6 +245,7 @@ describe BambooService, :use_clean_rails_memory_store_caching do
end end
def bamboo_response(result_key: 42, build_state: 'success', size: 1) def bamboo_response(result_key: 42, build_state: 'success', size: 1)
%Q({"results":{"results":{"size":"#{size}","result":{"buildState":"#{build_state}","planResultKey":{"key":"#{result_key}"}}}}}) # reference: https://docs.atlassian.com/atlassian-bamboo/REST/6.2.5/#d2e786
%Q({"results":{"results":{"size":"#{size}","result":[{"buildState":"#{build_state}","planResultKey":{"key":"#{result_key}"}}]}}})
end end
end end
...@@ -77,7 +77,10 @@ describe Project do ...@@ -77,7 +77,10 @@ describe Project do
it { is_expected.to have_many(:lfs_objects_projects) } it { is_expected.to have_many(:lfs_objects_projects) }
it { is_expected.to have_many(:project_group_links) } it { is_expected.to have_many(:project_group_links) }
it { is_expected.to have_many(:notification_settings).dependent(:delete_all) } it { is_expected.to have_many(:notification_settings).dependent(:delete_all) }
<<<<<<< HEAD
it { is_expected.to have_many(:approver_groups).dependent(:destroy) } it { is_expected.to have_many(:approver_groups).dependent(:destroy) }
=======
>>>>>>> upstream/master
it { is_expected.to have_many(:forked_to_members).class_name('ForkNetworkMember') } it { is_expected.to have_many(:forked_to_members).class_name('ForkNetworkMember') }
it { is_expected.to have_many(:forks).through(:forked_to_members) } it { is_expected.to have_many(:forks).through(:forked_to_members) }
it { is_expected.to have_many(:uploads) } it { is_expected.to have_many(:uploads) }
......
...@@ -399,6 +399,14 @@ describe QuickActions::InterpretService do ...@@ -399,6 +399,14 @@ describe QuickActions::InterpretService do
end end
end end
shared_examples 'assign command' do
it 'assigns to a single user' do
_, updates = service.execute(content, issuable)
expect(updates).to eq(assignee_ids: [developer.id])
end
end
it_behaves_like 'reopen command' do it_behaves_like 'reopen command' do
let(:content) { '/reopen' } let(:content) { '/reopen' }
let(:issuable) { issue } let(:issuable) { issue }
...@@ -516,67 +524,56 @@ describe QuickActions::InterpretService do ...@@ -516,67 +524,56 @@ describe QuickActions::InterpretService do
let(:issuable) { issue } let(:issuable) { issue }
end end
context 'assign command' do context 'assign command with one user' do
let(:content) { "/assign @#{developer.username}" } it_behaves_like 'assign command' do
let(:content) { "/assign @#{developer.username}" }
context 'Issue' do let(:issuable) { issue }
it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, issue)
expect(updates[:assignee_ids]).to match_array([developer.id])
end
end end
context 'Merge Request' do it_behaves_like 'assign command' do
it 'fetches assignee and populates assignee_ids if content contains /assign' do let(:content) { "/assign @#{developer.username}" }
_, updates = service.execute(content, merge_request) let(:issuable) { merge_request }
expect(updates).to eq(assignee_ids: [developer.id])
end
end end
end end
# CE does not have multiple assignees
context 'assign command with multiple assignees' do context 'assign command with multiple assignees' do
let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
before do before do
project.add_developer(developer2) project.add_developer(developer2)
end end
context 'Issue' do it_behaves_like 'assign command' do
it 'fetches assignee and populates assignee_ids if content contains /assign' do let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
_, updates = service.execute(content, issue) let(:issuable) { issue }
expect(updates[:assignee_ids]).to match_array([developer.id])
end
end end
context 'Merge Request' do it_behaves_like 'assign command' do
it 'fetches assignee and populates assignee_ids if content contains /assign' do let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
_, updates = service.execute(content, merge_request) let(:issuable) { merge_request }
expect(updates).to eq(assignee_ids: [developer.id])
end
end end
end end
context 'assign command with me alias' do context 'assign command with me alias' do
let(:content) { "/assign me" } it_behaves_like 'assign command' do
let(:content) { '/assign me' }
context 'Issue' do let(:issuable) { issue }
it 'fetches assignee and populates assignee_ids if content contains /assign' do end
_, updates = service.execute(content, issue)
expect(updates).to eq(assignee_ids: [developer.id]) it_behaves_like 'assign command' do
end let(:content) { '/assign me' }
let(:issuable) { merge_request }
end end
end
context 'Merge Request' do context 'assign command with me alias and whitespace' do
it 'fetches assignee and populates assignee_ids if content contains /assign' do it_behaves_like 'assign command' do
_, updates = service.execute(content, merge_request) let(:content) { '/assign me ' }
let(:issuable) { issue }
end
expect(updates).to eq(assignee_ids: [developer.id]) it_behaves_like 'assign command' do
end let(:content) { '/assign me ' }
let(:issuable) { merge_request }
end end
end end
......
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