Commit 88ea34ed authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-to-ee-2018-10-20' into 'master'

CE upstream - 2018-10-20 00:21 UTC

Closes gitlab-ce#52916, gitlab-ce#52918, gitlab-runner#3642 et #8062

See merge request gitlab-org/gitlab-ee!8022
parents 6f9fa714 bc98dc9b
11.4.0-pre
11.5.0-pre
......@@ -419,7 +419,7 @@ export default class MergeRequestTabs {
if (this.diffViewType() === 'parallel' || removeLimited) {
$wrapper.removeClass('container-limited');
} else {
$wrapper.addClass('container-limited');
$wrapper.toggleClass('container-limited', this.fixedLayoutPref);
}
}
......
......@@ -52,6 +52,7 @@
@import 'framework/blank';
@import 'framework/wells';
@import 'framework/page_header';
@import 'framework/page_title';
@import 'framework/awards';
@import 'framework/images';
@import 'framework/broadcast_messages';
......
......@@ -530,7 +530,6 @@
.header-user {
&.show .dropdown-menu {
max-height: 323px;
margin-top: 4px;
color: $gl-text-color;
left: auto;
......@@ -542,15 +541,19 @@
display: block;
}
.user-status-emoji {
.user-status {
margin-right: 0;
display: block;
vertical-align: text-top;
max-width: 240px;
font-size: 12px;
font-size: $gl-font-size-small;
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;
* Text
*/
$gl-font-size: 14px;
$gl-font-size-small: 12px;
$gl-font-weight-normal: 400;
$gl-font-weight-bold: 600;
$gl-text-color: #2e2e2e;
......
......@@ -18,10 +18,15 @@ module Boards
list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params)
issues = list_service.execute
issues = issues.page(params[:page]).per(params[:per] || 20).without_count
make_sure_position_is_set(issues) if Gitlab::Database.read_write?
issues = issues.preload(:project,
:milestone,
Issue.move_to_end(issues) if Gitlab::Database.read_write?
issues = issues.preload(:milestone,
:assignees,
project: [
:route,
{
namespace: [:route]
}
],
labels: [:priorities],
notes: [:award_emoji, :author]
)
......@@ -60,12 +65,6 @@ module Boards
render json: data
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
@issue ||= issues_finder.find(params[:id])
end
......
......@@ -42,7 +42,7 @@ module CiStatusHelper
when 'manual'
s_('CiStatusText|blocked')
when 'scheduled'
s_('CiStatusText|scheduled')
s_('CiStatusText|delayed')
else
# All states are already being translated inside the detailed statuses:
# :running => Gitlab::Ci::Status::Running
......
......@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ActiveRecord::Base
VERSION = '0.1.31'.freeze
VERSION = '0.1.34'.freeze
self.table_name = 'clusters_applications_runners'
......
......@@ -12,6 +12,49 @@ module RelativePositioning
after_save :save_positionable_neighbours
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
self.class.in_parents(parent_ids).minimum(:relative_position)
end
......@@ -57,7 +100,7 @@ module RelativePositioning
@positionable_neighbours = [before] # rubocop:disable Gitlab/ModuleWithInstanceVariables
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
def move_after(before = self)
......@@ -72,7 +115,7 @@ module RelativePositioning
pos_after = issue_to_move.relative_position
end
self.relative_position = position_between(pos_before, pos_after)
self.relative_position = self.class.position_between(pos_before, pos_after)
end
def move_before(after = self)
......@@ -87,15 +130,15 @@ module RelativePositioning
pos_before = issue_to_move.relative_position
end
self.relative_position = position_between(pos_before, pos_after)
self.relative_position = self.class.position_between(pos_before, pos_after)
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
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
# Indicates if there is an issue that should be shifted to free the place
......@@ -112,32 +155,6 @@ module RelativePositioning
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
def save_positionable_neighbours
return unless @positionable_neighbours
......
......@@ -17,6 +17,7 @@ class List < ActiveRecord::Base
scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) }
scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) }
scope :preload_associations, -> { preload(:board, :label) }
class << self
def destroyable_types
......
......@@ -80,13 +80,18 @@ class BambooService < CiService
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)
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.
URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s
else
# 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
end
end
......@@ -94,10 +99,10 @@ class BambooService < CiService
def read_commit_status(response)
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'
else
response['results']['results']['result']['buildState']
response.dig('results', 'results', 'result', get_build_result_index, 'buildState')
end
if status.include?('Success')
......
......@@ -8,7 +8,7 @@ module Boards
def execute(board)
board.lists.create(list_type: :backlog) unless board.lists.backlog.exists?
board.lists
board.lists.preload_associations
end
end
end
......
......@@ -644,7 +644,7 @@ module QuickActions
if users.empty?
users =
if params == 'me'
if params.strip == 'me'
[current_user]
else
User.where(username: params.split(' ').map(&:strip))
......
.page-title-holder
%h1.page-title= _('Activity')
.top-area
%ul.nav-links.nav.nav-tabs
%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
%ul.nav-links.mobile-separator.nav.nav-tabs
= nav_link(page: dashboard_groups_path) do
......@@ -9,5 +16,3 @@
.nav-controls
= render 'shared/groups/search_form'
= 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
= 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
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
......@@ -18,5 +25,3 @@
.nav-controls
= render 'shared/projects/search_form'
= 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
%ul.nav-links.nav.nav-tabs
= nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do
......@@ -6,7 +13,3 @@
= nav_link(page: explore_snippets_path) do
= link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
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 @@
= content_for :meta_tags do
= 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
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls
= 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
......
......@@ -2,10 +2,15 @@
- page_title _("Merge Requests")
- @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
= 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
......
......@@ -2,12 +2,18 @@
- page_title 'Milestones'
- 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
= 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
%ul.content-list
- if @milestones.blank?
......
......@@ -2,6 +2,9 @@
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
.page-title-holder
%h1.page-title= _('Todos')
- if current_user.todos.any?
.top-area
%ul.nav-links.mobile-separator.nav.nav-tabs
......
- @breadcrumb_link = dashboard_groups_path
- breadcrumb_title "Groups"
- @hide_breadcrumbs = true
- @hide_top_links = true
- page_title 'New Group'
- header_title "Groups", dashboard_groups_path
......
......@@ -14,6 +14,7 @@
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
= render "layouts/flash"
.d-flex
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" }
= yield
- page_title _("Dashboard")
- header_title _("Dashboard"), root_path unless header_title
- sidebar "dashboard"
- @hide_breadcrumbs = true
= render template: "layouts/application"
- page_title _("Explore")
- @hide_breadcrumbs = true
- unless current_user
- header_title _("Explore GitLab"), explore_root_path
......
......@@ -6,9 +6,11 @@
= current_user.name
= current_user.to_reference
- if current_user.status
.user-status-emoji.str-truncated.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } }
= emoji_icon current_user.status.emoji
= current_user.status.message_html.html_safe
.user-status.d-flex.align-items-center.prepend-top-2.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } }
%span.user-status-emoji.d-flex.align-items-center
= emoji_icon current_user.status.emoji
%span.user-status-message.str-truncated
= current_user.status.message_html.html_safe
%li.divider
- if can?(current_user, :update_user_status, current_user)
%li
......
......@@ -48,7 +48,7 @@
- if job.try(:allow_failure)
%span.badge.badge-danger allowed to fail
- if job.schedulable?
%span.badge.badge-info= s_('DelayedJobs|scheduled')
%span.badge.badge-info= s_('DelayedJobs|delayed')
- elsif job.action?
%span.badge.badge-info manual
......
- @breadcrumb_link = dashboard_projects_path
- breadcrumb_title "Projects"
- @hide_breadcrumbs = true
- @hide_top_links = true
- page_title 'New Project'
- header_title "Projects", dashboard_projects_path
......
- @hide_top_links = true
- add_to_breadcrumbs "Snippets", dashboard_snippets_path
- breadcrumb_title "New"
- @hide_breadcrumbs = true
- page_title "New Snippet"
%h3.page-title
New Snippet
%hr
= render "shared/snippets/form", url: snippets_path(@snippet)
.page-title-holder
%h1.page-title= _('New 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');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
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_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1;
const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
......
......@@ -340,8 +340,7 @@ Example response:
## List project's 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
usage is enabled in the project's settings.
are listed if at least one shared runner is defined.
```
GET /projects/:id/runners
......
......@@ -39,7 +39,7 @@ few important details:
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).
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)
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
......@@ -52,8 +52,7 @@ build:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- mkdir -p /root/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /root/.docker/config.json
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
only:
- tags
......
......@@ -209,6 +209,130 @@ it 'is overdue' do
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
This style of testing is used to exercise one piece of code with a comprehensive
......
......@@ -24,6 +24,7 @@ discussions, and descriptions:
| `/reopen` | Reopen | ✓ | ✓ |
| `/title <New title>` | Change title | ✓ | ✓ |
| `/award :emoji:` | Toggle emoji award | ✓ | ✓ |
| `/assign me` | Assign yourself | ✓ | ✓ |
| `/assign @user` | Assign one user | ✓ | ✓ |
| `/assign @user1 @user2` | Assign multiple users **[STARTER]** | ✓ | |
| `/unassign` | Remove assignee(s) | ✓ | ✓ |
......
......@@ -71,12 +71,6 @@ describe('Bar chart component', () => {
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)', () => {
const xAxisLabel = barChart.$el.querySelector('.x-axis').querySelectorAll('text')[0];
......
......@@ -22,6 +22,7 @@ describe Project do
it { is_expected.to have_many(:source_pipelines) }
it { is_expected.to have_many(:audit_events).dependent(false) }
it { is_expected.to have_many(:protected_environments) }
it { is_expected.to have_many(:approver_groups).dependent(:destroy) }
end
describe 'validations' do
......
......@@ -151,7 +151,7 @@ module API
present build, with: Entities::Job
end
desc 'Trigger a actionable job (manual, scheduled, etc)' do
desc 'Trigger a actionable job (manual, delayed, etc)' do
success Entities::Job
detail 'This feature was added in GitLab 8.11'
end
......
......@@ -7,7 +7,7 @@ module Gitlab
{
image: 'illustrations/illustrations_scheduled-job_countdown.svg',
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. " \
"Often they are used for incremental roll-out deploys " \
"to production environments. When unscheduled it converts " \
......@@ -16,7 +16,7 @@ module Gitlab
end
def status_tooltip
"scheduled manual action (#{execute_in})"
"delayed manual action (#{execute_in})"
end
def self.matches?(build, user)
......
......@@ -2,9 +2,9 @@ module Gitlab
module Ci
module Status
module Pipeline
class Scheduled < Status::Extended
class Delayed < Status::Extended
def text
s_('CiStatusText|scheduled')
s_('CiStatusText|delayed')
end
def label
......
......@@ -5,7 +5,7 @@ module Gitlab
class Factory < Status::Factory
def self.extended_statuses
[[Status::SuccessWarning,
Status::Pipeline::Scheduled,
Status::Pipeline::Delayed,
Status::Pipeline::Blocked]]
end
......
......@@ -3,11 +3,11 @@ module Gitlab
module Status
class Scheduled < Status::Core
def text
s_('CiStatusText|scheduled')
s_('CiStatusText|delayed')
end
def label
s_('CiStatusLabel|scheduled')
s_('CiStatusLabel|delayed')
end
def icon
......
......@@ -1476,6 +1476,9 @@ msgstr ""
msgid "CiStatusLabel|created"
msgstr ""
msgid "CiStatusLabel|delayed"
msgstr ""
msgid "CiStatusLabel|failed"
msgstr ""
......@@ -1491,9 +1494,6 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
msgid "CiStatusLabel|scheduled"
msgstr ""
msgid "CiStatusLabel|skipped"
msgstr ""
......@@ -1512,6 +1512,9 @@ msgstr ""
msgid "CiStatusText|created"
msgstr ""
msgid "CiStatusText|delayed"
msgstr ""
msgid "CiStatusText|failed"
msgstr ""
......@@ -1524,9 +1527,6 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
msgid "CiStatusText|scheduled"
msgstr ""
msgid "CiStatusText|skipped"
msgstr ""
......@@ -2548,7 +2548,7 @@ msgstr ""
msgid "DelayedJobs|Unschedule"
msgstr ""
msgid "DelayedJobs|scheduled"
msgid "DelayedJobs|delayed"
msgstr ""
msgid "Delete"
......@@ -7835,7 +7835,7 @@ msgstr ""
msgid "This is a confidential issue."
msgstr ""
msgid "This is a scheduled to run in "
msgid "This is a delayed to run in "
msgstr ""
msgid "This is the author's first Merge Request to this project."
......
......@@ -30,6 +30,15 @@ describe Boards::IssuesController do
context 'when list id is present' 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
issue = create(:labeled_issue, project: project, labels: [planning])
create(:labeled_issue, project: project, labels: [planning])
......@@ -56,6 +65,39 @@ describe Boards::IssuesController do
expect { list_issues(user: user, board: board, list: list2) }.not_to exceed_query_limit(control_count)
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
context 'with invalid list id' do
......@@ -102,12 +144,15 @@ describe Boards::IssuesController do
sign_in(user)
params = {
namespace_id: project.namespace.to_param,
project_id: project,
board_id: board.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
end
end
......
......@@ -2,8 +2,12 @@ require 'spec_helper'
describe 'Dashboard shortcuts', :js do
context 'logged in' do
let(:user) { create(:user) }
let(:project) { create(:project) }
before do
sign_in(create(:user))
project.add_developer(user)
sign_in(user)
visit root_dashboard_path
end
......@@ -50,6 +54,6 @@ describe 'Dashboard shortcuts', :js do
end
def check_page_title(title)
expect(find('.breadcrumbs-sub-title')).to have_content(title)
expect(find('.page-title')).to have_content(title)
end
end
......@@ -594,7 +594,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
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_link('Unschedule job')
end
......
......@@ -224,6 +224,14 @@ describe('MergeRequestTabs', function() {
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() {
$('.container-limited').addClass('breadcrumbs');
this.class.expandViewContainer();
......
......@@ -339,7 +339,7 @@ describe Gitlab::Ci::Status::Build::Factory do
end
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.icon).to eq 'status_scheduled'
expect(status.favicon).to eq 'favicon_status_scheduled'
......
require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Scheduled do
describe Gitlab::Ci::Status::Pipeline::Delayed do
let(:pipeline) { double('pipeline') }
subject do
......@@ -9,7 +9,7 @@ describe Gitlab::Ci::Status::Pipeline::Scheduled do
describe '#text' do
it 'overrides status text' do
expect(subject.text).to eq 'scheduled'
expect(subject.text).to eq 'delayed'
end
end
......
......@@ -71,7 +71,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
it 'matches a correct extended statuses' do
expect(factory.extended_statuses)
.to eq [Gitlab::Ci::Status::Pipeline::Scheduled]
.to eq [Gitlab::Ci::Status::Pipeline::Delayed]
end
it 'extends core status with common pipeline methods' do
......
......@@ -6,11 +6,11 @@ describe Gitlab::Ci::Status::Scheduled do
end
describe '#text' do
it { expect(subject.text).to eq 'scheduled' }
it { expect(subject.text).to eq 'delayed' }
end
describe '#label' do
it { expect(subject.label).to eq 'scheduled' }
it { expect(subject.label).to eq 'delayed' }
end
describe '#icon' do
......
......@@ -17,7 +17,7 @@ describe Clusters::Applications::Runner do
let(:application) { create(:clusters_applications_runner, :scheduled, version: '0.1.30') }
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
......@@ -45,7 +45,7 @@ describe Clusters::Applications::Runner do
it 'should be initialized with 4 arguments' do
expect(subject.name).to eq('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.repository).to eq('https://charts.gitlab.io')
expect(subject.files).to eq(gitlab_runner.files)
......@@ -63,7 +63,7 @@ describe Clusters::Applications::Runner do
let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') }
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
......
......@@ -6,9 +6,13 @@ describe RelativePositioning do
let(:issue1) { create(:issue, project: project) }
let(:new_issue) { create(:issue, project: project) }
before do
[issue, issue1].each do |issue|
issue.move_to_end && issue.save
describe '.move_to_end' do
it 'moves the object to the end' do
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
......@@ -59,6 +63,12 @@ describe RelativePositioning do
end
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
new_issue.move_to_end
......@@ -67,6 +77,12 @@ describe RelativePositioning do
end
describe '#shift_after?' do
before do
[issue, issue1].each do |issue|
issue.move_to_end && issue.save
end
end
it 'returns true' do
issue.update(relative_position: issue1.relative_position - 1)
......@@ -81,6 +97,12 @@ describe RelativePositioning do
end
describe '#shift_before?' do
before do
[issue, issue1].each do |issue|
issue.move_to_end && issue.save
end
end
it 'returns true' do
issue.update(relative_position: issue1.relative_position + 1)
......@@ -95,6 +117,12 @@ describe RelativePositioning do
end
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
new_issue.move_between(issue, issue1)
......
......@@ -245,6 +245,7 @@ describe BambooService, :use_clean_rails_memory_store_caching do
end
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
......@@ -77,7 +77,6 @@ describe Project do
it { is_expected.to have_many(:lfs_objects_projects) }
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(:approver_groups).dependent(:destroy) }
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(:uploads) }
......
......@@ -399,6 +399,14 @@ describe QuickActions::InterpretService do
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
let(:content) { '/reopen' }
let(:issuable) { issue }
......@@ -516,67 +524,56 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
context 'assign command' do
let(:content) { "/assign @#{developer.username}" }
context 'Issue' do
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
context 'assign command with one user' do
it_behaves_like 'assign command' do
let(:content) { "/assign @#{developer.username}" }
let(:issuable) { issue }
end
context 'Merge Request' do
it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, merge_request)
expect(updates).to eq(assignee_ids: [developer.id])
end
it_behaves_like 'assign command' do
let(:content) { "/assign @#{developer.username}" }
let(:issuable) { merge_request }
end
end
# CE does not have multiple assignees
context 'assign command with multiple assignees' do
let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
before do
project.add_developer(developer2)
end
context 'Issue' do
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
it_behaves_like 'assign command' do
let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
let(:issuable) { issue }
end
context 'Merge Request' do
it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, merge_request)
expect(updates).to eq(assignee_ids: [developer.id])
end
it_behaves_like 'assign command' do
let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
let(:issuable) { merge_request }
end
end
context 'assign command with me alias' do
let(:content) { "/assign me" }
context 'Issue' do
it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, issue)
it_behaves_like 'assign command' do
let(:content) { '/assign me' }
let(:issuable) { issue }
end
expect(updates).to eq(assignee_ids: [developer.id])
end
it_behaves_like 'assign command' do
let(:content) { '/assign me' }
let(:issuable) { merge_request }
end
end
context 'Merge Request' do
it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, merge_request)
context 'assign command with me alias and whitespace' do
it_behaves_like 'assign command' do
let(:content) { '/assign me ' }
let(:issuable) { issue }
end
expect(updates).to eq(assignee_ids: [developer.id])
end
it_behaves_like 'assign command' do
let(:content) { '/assign me ' }
let(:issuable) { merge_request }
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