From b14dcfd1d617d9cf9524b3bd1694739cd8a28c61 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 18 Apr 2018 18:48:19 +0100 Subject: Use cards for label list --- app/assets/stylesheets/pages/labels.scss | 14 ++++++++++++++ app/views/projects/labels/index.html.haml | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index b0852adb459..bd9b3169d2c 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -143,6 +143,10 @@ cursor: move; cursor: -webkit-grab; cursor: -moz-grab; + margin-bottom: 5px; + + padding: 11px 10px 11px $gl-padding; + border-radius: $border-radius-default; &:active { cursor: -webkit-grabbing; @@ -152,6 +156,10 @@ &.sortable-ghost { opacity: 0.3; } + + .prioritized-labels & { + box-shadow: 0 1px 2px $issue-boards-card-shadow; + } } .btn-action { @@ -319,3 +327,9 @@ font-size: $label-font-size; } } + +.labels-container { + background-color: $gray-light; + border-radius: $border-radius-default; + padding: $gl-padding $gl-padding-8; +} \ No newline at end of file diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 9c78bade254..5456ddd9a8f 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -17,12 +17,12 @@ = link_to new_project_label_path(@project), class: "btn btn-new" do New label - .labels + .labels-container.prepend-top-5 - if can_admin_label -# Only show it in the first page - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') .prioritized-labels{ class: ('hide' if hide) } - %h5 Prioritized Labels + %h5.prepend-top-0 Prioritized Labels %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) } #js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } = render 'shared/empty_states/priority_labels' -- cgit v1.2.1 From 8e342f1c7f7623304419d1dfeca68bf1b106eb89 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 19 Apr 2018 00:09:44 +0100 Subject: Start restyle label list items --- app/assets/stylesheets/pages/labels.scss | 31 +++++++- app/views/shared/_label.html.haml | 120 +++++++++++-------------------- app/views/shared/_label_row.html.haml | 30 +++----- 3 files changed, 82 insertions(+), 99 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index bd9b3169d2c..3449c90e3ad 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -63,8 +63,7 @@ margin-bottom: 10px; @media (min-width: $screen-sm-min) { - width: 200px; - margin-left: $gl-padding * 2; + width: 100px; margin-bottom: 0; } @@ -332,4 +331,32 @@ background-color: $gray-light; border-radius: $border-radius-default; padding: $gl-padding $gl-padding-8; +} + +.label-actions-list { + list-style: none; +} + +.label-badge { + color: $theme-gray-900; + font-weight: $gl-font-weight-bold; + padding: $gl-padding-4; + border-radius: $border-radius-default; +} + +.label-badge-blue { + background-color: $theme-blue-100; +} + +.label-badge-gray { + background-color: $theme-gray-100; +} + +.label-links { + list-style: none; + padding: 0; +} + +.label-link-item { + padding: 0; } \ No newline at end of file diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 836df57a3a2..578c6c73fe2 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -7,87 +7,51 @@ %li.label-list-item{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label - - .visible-xs.visible-sm-inline-block.dropdown - %button.btn.btn-default.label-options-toggle{ type: 'button', data: { toggle: "dropdown" } } - Options - = icon('caret-down') - .dropdown-menu.dropdown-menu-align-right - %ul - - if show_label_merge_requests_link - %li - = link_to_label(label, subject: subject, type: :merge_request) do - View merge requests - - if show_label_issues_link - %li - = link_to_label(label, subject: subject) do - View open issues - - if current_user - %li.label-subscription - - if can_subscribe_to_label_in_different_levels?(label) - %a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } } - %span Unsubscribe - %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } } - %span Subscribe at project level - %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } } - %span Subscribe at group level - - else - %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', data: { status: status, url: toggle_subscription_path } } - %span= label_subscription_toggle_button_text(label, @project) - - - if can?(current_user, :admin_label, label) - %li - = link_to 'Edit', edit_label_path(label) - %li - = link_to 'Delete', - destroy_label_path(label), - title: 'Delete', - method: :delete, - data: {confirm: 'Remove this label? Are you sure?'}, - class: 'text-danger' - - .pull-right.hidden-xs.hidden-sm - - if can?(current_user, :admin_label, label) - - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) - %button.js-promote-project-label-button.btn.btn-transparent.btn-action.has-tooltip{ title: _('Promote to Group Label'), - disabled: true, - type: 'button', - data: { url: promote_project_label_path(label.project, label), - label_title: label.title, - label_color: label.color, - label_text_color: label.text_color, - group_name: label.project.group.name, - target: '#promote-label-modal', - container: 'body', - toggle: 'modal' } } - = sprite_icon('level-up') - = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do - %span.sr-only Edit - = sprite_icon('pencil') - %span{ data: { toggle: 'modal', target: "#modal-delete-label-#{label.id}" } } - = link_to "#", title: "Delete", class: 'btn btn-transparent btn-action remove-row', data: { toggle: "tooltip" } do - %span.sr-only Delete - = sprite_icon('remove') + %ul.label-actions-list.inline + %li.inline + .label-badge.label-badge-gray= label.model_name.human.titleize + - if can?(current_user, :admin_label, @project) + %li.inline.js-toggle-priority.toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), + dom_id: dom_id(label), type: label.type } } + %button.add-priority.btn.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' } + = icon('star-o') + %button.remove-priority.btn.has-tooltip{ title: 'Remove priority', type: 'button', :'data-placement' => 'top' } + = icon('star') + %li.inline + = link_to edit_label_path(label) do + = icon('pencil') + %li.inline + .dropdown + %button{ type: 'button', class: 'btn btn-transparent js-label-options-dropdown', data: { toggle: "dropdown" } } + = custom_icon('ellipsis_v') + .dropdown-menu.dropdown-menu-align-right + %ul + - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) + %li + %button.js-promote-project-label-button.btn.btn-transparent.btn-action.has-tooltip{ title: _('Promote to Group Label'), + disabled: true, + type: 'button', + data: { url: promote_project_label_path(label.project, label), + label_title: label.title, + label_color: label.color, + label_text_color: label.text_color, + group_name: label.project.group.name, + target: '#promote-label-modal', + container: 'body', + toggle: 'modal' } } + = sprite_icon('level-up') + %li + = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: { confirm: 'Remove this label? Are you sure?' }, class: 'text-danger' - if current_user - .label-subscription.inline + %li.inline.label-subscription - if can_subscribe_to_label_in_different_levels?(label) - %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } } + %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } } %span Unsubscribe - = icon('spinner spin', class: 'label-subscribe-button-loading') - - .dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) } - %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } - %span Subscribe - = icon('chevron-down') - %ul.dropdown-menu - %li - %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } } - Project level - %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } } - Group level + %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } } + %span Subscribe at project level + %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } } + %span Subscribe at group level - else - %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', data: { status: status, url: toggle_subscription_path } } - %span= label_subscription_toggle_button_text(label, @project) - = icon('spinner spin', class: 'label-subscribe-button-loading') + %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ data: { status: status, url: toggle_subscription_path } } = render 'shared/delete_label_modal', label: label diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index bd4f191502e..fa72086e048 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -2,29 +2,21 @@ - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) -%span.label-row - - if can?(current_user, :admin_label, @project) - .draggable-handler - = icon('bars') - .js-toggle-priority.toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), - dom_id: dom_id(label), type: label.type } } - %button.add-priority.btn.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' } - = icon('star-o') - %button.remove-priority.btn.has-tooltip{ title: 'Remove priority', type: 'button', :'data-placement' => 'top' } - = icon('star') - %span.label-name +.label-row.inline + .label-name = link_to_label(label, subject: @project, tooltip: false) - - if defined?(@project) && @project.group.present? - %span.label-type - = label.model_name.human.titleize - - %span.label-description + .label-description - if label.description.present? .description-text = markdown_field(label, :description) - .hidden-xs.hidden-sm + %ul.label-links - if show_label_issues_link - = link_to_label(label, subject: subject) { 'Issues' } + %li.label-link-item.inline + = link_to_label(label, subject: subject) { 'Issues' } - if show_label_merge_requests_link · - = link_to_label(label, subject: subject, type: :merge_request) { 'Merge requests' } + %li.label-link-item.inline + = link_to_label(label, subject: subject, type: :merge_request) { 'Merge requests' } + - if label.priorities.present? + %li.label-link-item.inline + .label-badge.label-badge-blue Prioritized label \ No newline at end of file -- cgit v1.2.1 From ce0677e2b7d37aa3dff1e96b06145d1a7c164629 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 19 Apr 2018 00:11:15 +0100 Subject: add changelog --- .../39549-label-list-page-redesign-with-draggable-labels.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/39549-label-list-page-redesign-with-draggable-labels.yml diff --git a/changelogs/unreleased/39549-label-list-page-redesign-with-draggable-labels.yml b/changelogs/unreleased/39549-label-list-page-redesign-with-draggable-labels.yml new file mode 100644 index 00000000000..fb4fbf80575 --- /dev/null +++ b/changelogs/unreleased/39549-label-list-page-redesign-with-draggable-labels.yml @@ -0,0 +1,5 @@ +--- +title: Label list page redesign +merge_request: 18466 +author: +type: changed -- cgit v1.2.1 From 3466bf1cc183641077013d553bd59dcc76074dc3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 19 Apr 2018 00:34:03 +0100 Subject: flex it --- app/assets/stylesheets/pages/labels.scss | 72 +++++++------------------------- 1 file changed, 16 insertions(+), 56 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 3449c90e3ad..5d15d03a7bd 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -58,14 +58,11 @@ } .label-row { - .label-name { - display: inline-block; - margin-bottom: 10px; + display: flex; - @media (min-width: $screen-sm-min) { - width: 100px; - margin-bottom: 0; - } + .label-name { + width: 200px; + flex-shrink: 0; .label { overflow: hidden; @@ -78,14 +75,6 @@ display: block; margin-bottom: 10px; margin-left: 50px; - - @media (min-width: $screen-sm-min) { - display: inline-block; - width: 100px; - margin-left: 10px; - margin-bottom: 0; - vertical-align: top; - } } .label-description { @@ -99,14 +88,6 @@ a { color: $blue-600; } - - @media (min-width: $screen-sm-min) { - display: inline-block; - max-width: 50%; - margin-left: 10px; - margin-bottom: 0; - vertical-align: top; - } } .label { @@ -131,19 +112,14 @@ } .manage-labels-list { - @media(min-width: $screen-md-min) { - &.content-list li { - padding: $gl-padding 0; - } - } - > li:not(.empty-message):not(.is-not-draggable) { background-color: $white-light; cursor: move; cursor: -webkit-grab; cursor: -moz-grab; margin-bottom: 5px; - + display: flex; + justify-content: space-between; padding: 11px 10px 11px $gl-padding; border-radius: $border-radius-default; @@ -176,27 +152,6 @@ } } } - - .dropdown { - @media (min-width: $screen-sm-min) { - float: right; - } - } - - @media (max-width: $screen-xs-max) { - .dropdown-menu { - min-width: 100%; - } - } -} - -.draggable-handler { - display: inline-block; - vertical-align: top; - margin: 5px 0; - opacity: 0; - transition: opacity .3s; - color: $gray-darkest; } .prioritized-labels { @@ -290,11 +245,6 @@ } .label-subscribe-button { - @media(min-width: $screen-md-min) { - min-width: 105px; - margin-left: $gl-padding; - } - .label-subscribe-button-icon { &[disabled] { opacity: 0.5; @@ -335,6 +285,7 @@ .label-actions-list { list-style: none; + flex-shrink: 0; } .label-badge { @@ -355,8 +306,17 @@ .label-links { list-style: none; padding: 0; + white-space: nowrap; } .label-link-item { padding: 0; +} + +.label-list-item { + .content-list &:before, + .content-list &:after { + content: none; + display: block; + } } \ No newline at end of file -- cgit v1.2.1 From f416c8a128e846d3c8d0a0050626600f665866a1 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 19 Apr 2018 00:36:09 +0100 Subject: Margin left for priority label --- app/views/shared/_label_row.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index fa72086e048..2d7fdfae46d 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -18,5 +18,5 @@ %li.label-link-item.inline = link_to_label(label, subject: subject, type: :merge_request) { 'Merge requests' } - if label.priorities.present? - %li.label-link-item.inline + %li.label-link-item.inline.prepend-left-10 .label-badge.label-badge-blue Prioritized label \ No newline at end of file -- cgit v1.2.1 From d0d521f362b5f8ba67e1a8b259db48d4b2a9b541 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 19 Apr 2018 01:17:14 +0100 Subject: Dont hide sub button text when toggling --- app/assets/javascripts/project_label_subscription.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/javascripts/project_label_subscription.js b/app/assets/javascripts/project_label_subscription.js index f31beb4dc78..e3824d83728 100644 --- a/app/assets/javascripts/project_label_subscription.js +++ b/app/assets/javascripts/project_label_subscription.js @@ -20,7 +20,6 @@ export default class ProjectLabelSubscription { const oldStatus = $btn.attr('data-status'); $btn.addClass('disabled'); - $span.toggleClass('hidden'); axios.post(url).then(() => { let newStatus; @@ -32,7 +31,6 @@ export default class ProjectLabelSubscription { [newStatus, newAction] = ['unsubscribed', 'Subscribe']; } - $span.toggleClass('hidden'); $btn.removeClass('disabled'); this.$buttons.attr('data-status', newStatus); -- cgit v1.2.1 From c9f23ffe5bb4cc5bf28e28e7736d867ec0de5443 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 19 Apr 2018 01:17:29 +0100 Subject: Remove unneeded inlines and remove hand where needed --- app/assets/stylesheets/pages/labels.scss | 10 +++++++--- app/views/shared/_label.html.haml | 5 +++-- app/views/shared/_label_row.html.haml | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 5d15d03a7bd..c84c1594986 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -79,10 +79,9 @@ .label-description { display: block; - margin-bottom: 10px; .description-text { - margin-bottom: $gl-padding; + margin: 0 $gl-padding 10px 0; } a { @@ -135,6 +134,10 @@ .prioritized-labels & { box-shadow: 0 1px 2px $issue-boards-card-shadow; } + + &.no-hand { + cursor: auto; + } } .btn-action { @@ -286,6 +289,7 @@ .label-actions-list { list-style: none; flex-shrink: 0; + padding: 0; } .label-badge { @@ -305,7 +309,7 @@ .label-links { list-style: none; - padding: 0; + padding: 0 $gl-padding 0 0; white-space: nowrap; } diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 578c6c73fe2..8302918ebc9 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -5,9 +5,9 @@ - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) -%li.label-list-item{ id: label_css_id, data: { id: label.id } } +%li.label-list-item{ class: label.priorities.present? ? '' : 'no-hand', id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label - %ul.label-actions-list.inline + %ul.label-actions-list %li.inline .label-badge.label-badge-gray= label.model_name.human.titleize - if can?(current_user, :admin_label, @project) @@ -53,5 +53,6 @@ %span Subscribe at group level - else %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ data: { status: status, url: toggle_subscription_path } } + %span= label_subscription_toggle_button_text(label, @project) = render 'shared/delete_label_modal', label: label diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 2d7fdfae46d..a2997bb9146 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -2,7 +2,7 @@ - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) -.label-row.inline +.label-row .label-name = link_to_label(label, subject: @project, tooltip: false) .label-description -- cgit v1.2.1 From 8b4e1d0ccd6c2624ac230597c4e63c8b51bc76ba Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 19 Apr 2018 01:20:38 +0100 Subject: Add/use Label#priority? and remove weird padding --- app/assets/stylesheets/pages/labels.scss | 4 ++-- app/models/label.rb | 4 ++++ app/views/shared/_label.html.haml | 2 +- app/views/shared/_label_row.html.haml | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index c84c1594986..85740b91ba6 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -81,7 +81,7 @@ display: block; .description-text { - margin: 0 $gl-padding 10px 0; + margin: 0 0 10px 0; } a { @@ -309,7 +309,7 @@ .label-links { list-style: none; - padding: 0 $gl-padding 0 0; + padding: 0; white-space: nowrap; } diff --git a/app/models/label.rb b/app/models/label.rb index f3496884cff..12e8c5695d4 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -137,6 +137,10 @@ class Label < ActiveRecord::Base priority.try(:priority) end + def priority? + priorities.present? + end + def template? template end diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 8302918ebc9..3d7cad8ecf8 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -5,7 +5,7 @@ - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) -%li.label-list-item{ class: label.priorities.present? ? '' : 'no-hand', id: label_css_id, data: { id: label.id } } +%li.label-list-item{ class: label.priority? ? '' : 'no-hand', id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label %ul.label-actions-list %li.inline diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index a2997bb9146..c098c916915 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -17,6 +17,6 @@ · %li.label-link-item.inline = link_to_label(label, subject: subject, type: :merge_request) { 'Merge requests' } - - if label.priorities.present? + - if label.priority? %li.label-link-item.inline.prepend-left-10 .label-badge.label-badge-blue Prioritized label \ No newline at end of file -- cgit v1.2.1 From 8ce9a09da0d186ad9ee5eadb87ad84f95d388bb6 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 19 Apr 2018 03:02:38 +0100 Subject: More stylezz --- app/assets/stylesheets/framework/common.scss | 2 + app/assets/stylesheets/pages/labels.scss | 72 +++++++++++++--------------- app/views/shared/_label_row.html.haml | 10 ++-- 3 files changed, 39 insertions(+), 45 deletions(-) diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index d0dda50a835..0db1b78ac9f 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -463,11 +463,13 @@ img.emoji { .prepend-left-5 { margin-left: 5px; } .prepend-left-8 { margin-left: 8px; } .prepend-left-10 { margin-left: 10px; } +.prepend-left-15 { margin-left: 15px; } .prepend-left-default { margin-left: $gl-padding; } .prepend-left-20 { margin-left: 20px; } .append-right-5 { margin-right: 5px; } .append-right-8 { margin-right: 8px; } .append-right-10 { margin-right: 10px; } +.append-right-15 { margin-right: 15px; } .append-right-default { margin-right: $gl-padding; } .append-right-20 { margin-right: 20px; } .append-bottom-0 { margin-bottom: 0; } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 85740b91ba6..c4fb0c2c8e9 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -57,46 +57,6 @@ border-bottom-left-radius: $border-radius-base; } -.label-row { - display: flex; - - .label-name { - width: 200px; - flex-shrink: 0; - - .label { - overflow: hidden; - text-overflow: ellipsis; - max-width: 100%; - } - } - - .label-type { - display: block; - margin-bottom: 10px; - margin-left: 50px; - } - - .label-description { - display: block; - - .description-text { - margin: 0 0 10px 0; - } - - a { - color: $blue-600; - } - } - - .label { - padding: 4px $grid-size; - font-size: $label-font-size; - position: relative; - top: ($grid-size / 2); - } -} - .color-label { padding: 0 $grid-size; line-height: 16px; @@ -323,4 +283,36 @@ content: none; display: block; } + + .label-name { + width: 150px; + flex-shrink: 0; + + .label { + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + } + } + + .label-type { + display: block; + margin-bottom: 10px; + margin-left: 50px; + } + + .label-description { + flex-grow: 1; + + a { + color: $blue-600; + } + } + + .label { + padding: 4px $grid-size; + font-size: $label-font-size; + position: relative; + top: ($grid-size / 2); + } } \ No newline at end of file diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index c098c916915..89dea6c6f10 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -2,12 +2,12 @@ - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) -.label-row - .label-name - = link_to_label(label, subject: @project, tooltip: false) - .label-description +.label-name + = link_to_label(label, subject: @project, tooltip: false) +.label-description + .append-right-15.prepend-left-15 - if label.description.present? - .description-text + .description-text.append-bottom-10 = markdown_field(label, :description) %ul.label-links - if show_label_issues_link -- cgit v1.2.1 From 5099e73f9fcf6a835db8f8cff5380a4a3db1c736 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 19 Apr 2018 03:09:20 +0100 Subject: Move new label to header_content --- app/views/projects/labels/index.html.haml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 5456ddd9a8f..a5f7ce8e2ad 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -1,7 +1,14 @@ - @no_container = true - page_title "Labels" -- hide_class = '' - can_admin_label = can?(current_user, :admin_label, @project) +- hide_class = '' + +- if can_admin_label + - content_for(:header_content) do + .nav-controls + = link_to new_project_label_path(@project), class: "btn btn-new" do + New label + - if @labels.exists? || @prioritized_labels.exists? #promote-label-modal @@ -12,11 +19,6 @@ - if can_admin_label Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. - - if can_admin_label - .nav-controls - = link_to new_project_label_path(@project), class: "btn btn-new" do - New label - .labels-container.prepend-top-5 - if can_admin_label -# Only show it in the first page -- cgit v1.2.1 From 53a4437a6450a35942f91a92d0fcb296cd6dc989 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 19 Apr 2018 03:19:55 +0100 Subject: Add and remove priority label --- app/assets/javascripts/label_manager.js | 15 ++++++++++++++- app/views/projects/labels/index.html.haml | 5 ++++- app/views/shared/_label_row.html.haml | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index e230dbbd4ac..628e084a5e0 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -13,6 +13,7 @@ export default class LabelManager { this.otherLabels = otherLabels || $('.js-other-labels'); this.errorMessage = 'Unable to update label prioritization at this time'; this.emptyState = document.querySelector('#js-priority-labels-empty-state'); + this.$badgeItemTemplate = $(document.getElementById('js-badge-item-template').innerHTML); this.sortable = Sortable.create(this.prioritizedLabels.get(0), { filter: '.empty-message', forceFallback: true, @@ -63,7 +64,11 @@ export default class LabelManager { $target = this.otherLabels; $from = this.prioritizedLabels; } - $label.detach().appendTo($target); + + const $detachedLabel = $label.detach(); + this.toggleLabelPriorityBadge($detachedLabel, action); + $detachedLabel.appendTo($target); + if ($from.find('li').length) { $from.find('.empty-message').removeClass('hidden'); } @@ -88,6 +93,14 @@ export default class LabelManager { } } + toggleLabelPriorityBadge($label, action) { + if (action === 'remove') { + $('.js-priority-badge', $label).remove(); + } else { + $('.label-links', $label).append(this.$badgeItemTemplate); + } + } + onPrioritySortUpdate() { this.savePrioritySort() .catch(() => flash(this.errorMessage)); diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index a5f7ce8e2ad..632355fa338 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -9,7 +9,6 @@ = link_to new_project_label_path(@project), class: "btn btn-new" do New label - - if @labels.exists? || @prioritized_labels.exists? #promote-label-modal %div{ class: container_class } @@ -40,3 +39,7 @@ = paginate @labels, theme: 'gitlab' - else = render 'shared/empty_states/labels' + +%template#js-badge-item-template + %li.label-link-item.js-priority-badge.inline.prepend-left-10 + .label-badge.label-badge-blue Prioritized label \ No newline at end of file diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 89dea6c6f10..51ff88b9981 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -18,5 +18,5 @@ %li.label-link-item.inline = link_to_label(label, subject: subject, type: :merge_request) { 'Merge requests' } - if label.priority? - %li.label-link-item.inline.prepend-left-10 + %li.label-link-item.js-priority-badge.inline.prepend-left-10 .label-badge.label-badge-blue Prioritized label \ No newline at end of file -- cgit v1.2.1 From bc3fd40480d3cafc97e0aea1a1a047cce53ab3f5 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 19 Apr 2018 03:44:53 +0100 Subject: Subscription options dropdown --- app/views/shared/_label.html.haml | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 3d7cad8ecf8..cf42c0d1bd4 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -5,11 +5,11 @@ - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) -%li.label-list-item{ class: label.priority? ? '' : 'no-hand', id: label_css_id, data: { id: label.id } } +%li.label-list-item{ class: ('no-hand' unless label.priority?), id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label %ul.label-actions-list %li.inline - .label-badge.label-badge-gray= label.model_name.human.titleize + .label-badge.label-badge-gray= label.model_name.human.capitalize - if can?(current_user, :admin_label, @project) %li.inline.js-toggle-priority.toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), dom_id: dom_id(label), type: label.type } } @@ -22,15 +22,13 @@ = icon('pencil') %li.inline .dropdown - %button{ type: 'button', class: 'btn btn-transparent js-label-options-dropdown', data: { toggle: "dropdown" } } + %button{ type: 'button', class: 'btn btn-transparent js-label-options-dropdown', data: { toggle: 'dropdown' } } = custom_icon('ellipsis_v') .dropdown-menu.dropdown-menu-align-right %ul - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) %li - %button.js-promote-project-label-button.btn.btn-transparent.btn-action.has-tooltip{ title: _('Promote to Group Label'), - disabled: true, - type: 'button', + %button.js-promote-project-label-button.btn.btn-transparent.btn-action{ disabled: true, type: 'button', data: { url: promote_project_label_path(label.project, label), label_title: label.title, label_color: label.color, @@ -39,18 +37,27 @@ target: '#promote-label-modal', container: 'body', toggle: 'modal' } } - = sprite_icon('level-up') - %li - = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: { confirm: 'Remove this label? Are you sure?' }, class: 'text-danger' + Promote to group label + %li + = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: { confirm: 'Remove this label? Are you sure?' }, class: 'text-danger' - if current_user %li.inline.label-subscription - if can_subscribe_to_label_in_different_levels?(label) %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } } %span Unsubscribe - %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } } - %span Subscribe at project level - %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } } - %span Subscribe at group level + .dropdown{ class: ('hidden' unless status.unsubscribed?) } + %button.label-subscribe-button.btn.btn-default{ data: { toggle: 'dropdown' } } + %span + Subscribe + = icon('chevron-down') + .dropdown-menu.dropdown-menu-align-right + %ul + %li + %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } } + %span Subscribe at project level + %li + %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } } + %span Subscribe at group level - else %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ data: { status: status, url: toggle_subscription_path } } %span= label_subscription_toggle_button_text(label, @project) -- cgit v1.2.1 From 523daec15074d406dac9bd2d59e9838ac1dcc200 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 24 Apr 2018 13:19:26 +0100 Subject: Fix group dropdown --- app/views/shared/_label.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index cf42c0d1bd4..c2357a97975 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -45,7 +45,7 @@ - if can_subscribe_to_label_in_different_levels?(label) %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } } %span Unsubscribe - .dropdown{ class: ('hidden' unless status.unsubscribed?) } + .dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) } %button.label-subscribe-button.btn.btn-default{ data: { toggle: 'dropdown' } } %span Subscribe @@ -53,10 +53,10 @@ .dropdown-menu.dropdown-menu-align-right %ul %li - %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } } + %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_project_label_path(@project, label) } } %span Subscribe at project level %li - %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } } + %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } } %span Subscribe at group level - else %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ data: { status: status, url: toggle_subscription_path } } -- cgit v1.2.1 From 752e4e0042db64ae67cc0c1a2971a91920a1cecd Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 24 Apr 2018 13:19:47 +0100 Subject: Remove span --- app/assets/javascripts/project_label_subscription.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/project_label_subscription.js b/app/assets/javascripts/project_label_subscription.js index e3824d83728..c5607a3cada 100644 --- a/app/assets/javascripts/project_label_subscription.js +++ b/app/assets/javascripts/project_label_subscription.js @@ -15,7 +15,6 @@ export default class ProjectLabelSubscription { event.preventDefault(); const $btn = $(event.currentTarget); - const $span = $btn.find('span'); const url = $btn.attr('data-url'); const oldStatus = $btn.attr('data-status'); -- cgit v1.2.1 From dc67ab29784b0895a76d3de705826a8749e8bf9b Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 24 Apr 2018 13:26:39 +0100 Subject: Clone pri label --- app/assets/javascripts/label_manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index 628e084a5e0..8013df53f25 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -97,7 +97,7 @@ export default class LabelManager { if (action === 'remove') { $('.js-priority-badge', $label).remove(); } else { - $('.label-links', $label).append(this.$badgeItemTemplate); + $('.label-links', $label).append(this.$badgeItemTemplate.clone()); } } -- cgit v1.2.1 From 6b07a352a9ad23416f962846fd77bcc52ae06186 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 24 Apr 2018 13:26:50 +0100 Subject: Fix cursor for pri/other --- app/assets/stylesheets/pages/labels.scss | 18 +++++++----------- app/views/shared/_label.html.haml | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index c4fb0c2c8e9..3ea6be9f638 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -73,30 +73,26 @@ .manage-labels-list { > li:not(.empty-message):not(.is-not-draggable) { background-color: $white-light; - cursor: move; - cursor: -webkit-grab; - cursor: -moz-grab; margin-bottom: 5px; display: flex; justify-content: space-between; padding: 11px 10px 11px $gl-padding; border-radius: $border-radius-default; - &:active { - cursor: -webkit-grabbing; - cursor: -moz-grabbing; - } - &.sortable-ghost { opacity: 0.3; } .prioritized-labels & { box-shadow: 0 1px 2px $issue-boards-card-shadow; - } + cursor: move; + cursor: -webkit-grab; + cursor: -moz-grab; - &.no-hand { - cursor: auto; + &:active { + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + } } } diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index c2357a97975..d1d5d707410 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -5,7 +5,7 @@ - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) -%li.label-list-item{ class: ('no-hand' unless label.priority?), id: label_css_id, data: { id: label.id } } +%li.label-list-item{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label %ul.label-actions-list %li.inline -- cgit v1.2.1 From a72fc8e650128d56ec9af1f3ea5960c0f4321db0 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 24 Apr 2018 14:32:51 +0100 Subject: add label actions styling --- app/assets/stylesheets/pages/labels.scss | 33 ++++++++++++++++---------------- app/views/shared/_label.html.haml | 10 +++++----- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 3ea6be9f638..b63b552415d 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -135,22 +135,6 @@ } } -.toggle-priority { - display: inline-block; - vertical-align: top; - - button { - border-color: transparent; - padding: 5px 8px; - vertical-align: top; - font-size: 14px; - - &:hover { - border-color: transparent; - } - } -} - .filtered-labels { font-size: 0; padding: 12px 16px; @@ -311,4 +295,21 @@ position: relative; top: ($grid-size / 2); } + + .label-action { + color: $theme-gray-800; + cursor: pointer; + + svg { + fill: $theme-gray-800; + } + + &:hover { + color: $blue-600; + + svg { + fill: $blue-600; + } + } + } } \ No newline at end of file diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index d1d5d707410..bccca0bb7d6 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -11,18 +11,18 @@ %li.inline .label-badge.label-badge-gray= label.model_name.human.capitalize - if can?(current_user, :admin_label, @project) - %li.inline.js-toggle-priority.toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), + %li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), dom_id: dom_id(label), type: label.type } } - %button.add-priority.btn.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' } + %button.label-action.add-priority.btn.btn-transparent.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' } = icon('star-o') - %button.remove-priority.btn.has-tooltip{ title: 'Remove priority', type: 'button', :'data-placement' => 'top' } + %button.label-action.remove-priority.btn.btn-transparent.has-tooltip{ title: 'Remove priority', type: 'button', :'data-placement' => 'top' } = icon('star') %li.inline - = link_to edit_label_path(label) do + = link_to edit_label_path(label), class: 'btn btn-transparent label-action' do = icon('pencil') %li.inline .dropdown - %button{ type: 'button', class: 'btn btn-transparent js-label-options-dropdown', data: { toggle: 'dropdown' } } + %button{ type: 'button', class: 'btn btn-transparent js-label-options-dropdown label-action', data: { toggle: 'dropdown' } } = custom_icon('ellipsis_v') .dropdown-menu.dropdown-menu-align-right %ul -- cgit v1.2.1 From e5ae43e23ffd8d75865a3572439afe40a0dd9444 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 25 Apr 2018 11:56:54 +0100 Subject: Start changing group label page --- app/controllers/groups/labels_controller.rb | 30 ++++++++------- app/views/groups/labels/index.html.haml | 58 ++++++++++++++++++++--------- app/views/shared/_label.html.haml | 17 +++++---- 3 files changed, 67 insertions(+), 38 deletions(-) diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 58be330f466..596dd5b2d69 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -2,27 +2,20 @@ class Groups::LabelsController < Groups::ApplicationController include ToggleSubscriptionAction before_action :label, only: [:edit, :update, :destroy] + before_action :find_labels, only: [:index] before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy] before_action :save_previous_label_path, only: [:edit] respond_to :html def index - respond_to do |format| - format.html do - @labels = @group.labels.page(params[:page]) - end + @prioritized_labels = @available_labels.prioritized(@group) + @labels = @available_labels.unprioritized(@group).page(params[:page]) + respond_to do |format| + format.html format.json do - available_labels = LabelsFinder.new( - current_user, - group_id: @group.id, - only_group_labels: params[:only_group_labels], - include_ancestor_groups: params[:include_ancestor_groups], - include_descendant_groups: params[:include_descendant_groups] - ).execute - - render json: LabelSerializer.new.represent_appearance(available_labels) + render json: LabelSerializer.new.represent_appearance(@available_labels) end end end @@ -113,4 +106,15 @@ class Groups::LabelsController < Groups::ApplicationController def save_previous_label_path session[:previous_labels_path] = URI(request.referer || '').path end + + def find_labels + @available_labels ||= + LabelsFinder.new( + current_user, + group_id: @group.id, + only_group_labels: params[:only_group_labels], + include_ancestor_groups: params[:include_ancestor_groups], + include_descendant_groups: params[:include_descendant_groups] + ).execute + end end diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index ac7e12fcd0b..1173499f921 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -1,21 +1,45 @@ -- page_title 'Labels' +- @no_container = true +- page_title "Labels" +- can_admin_label = can?(current_user, :admin_label, @group) +- hide_class = '' -- issuables = ['issues', 'merge requests'] +- if can_admin_label + - content_for(:header_content) do + .nav-controls + = link_to new_group_label_path(@group), class: "btn btn-new" do + New label -.top-area.adjust - .nav-text - = _("Labels can be applied to %{features}. Group labels are available for any project within the group.") % { features: issuables.to_sentence } +- if @labels.exists? || @prioritized_labels.exists? + #promote-label-modal + %div{ class: container_class } + .top-area.adjust + .nav-text + Labels can be applied to issues, merge requests and epics. Group labels are available for any project within the group. + - if can_admin_label + Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. - .nav-controls - - if can?(current_user, :admin_label, @group) - = link_to "New label", new_group_label_path(@group), class: "btn btn-new" + .labels-container.prepend-top-5 + - if can_admin_label + -# Only show it in the first page + - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') + .prioritized-labels{ class: ('hide' if hide) } + %h5.prepend-top-0 Prioritized Labels + %ul.content-list.manage-labels-list.js-prioritized-labels + #js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } + = render 'shared/empty_states/priority_labels' + - if @prioritized_labels.present? + = render partial: 'shared/label', subject: @group, collection: @prioritized_labels, as: :label -.labels - .other-labels - - if @labels.present? - %ul.content-list.manage-labels-list.js-other-labels - = render partial: 'shared/label', subject: @group, collection: @labels, as: :label - = paginate @labels, theme: 'gitlab' - - else - .nothing-here-block - = _("No labels created yet.") + - if @labels.present? + .other-labels + - if can_admin_label + %h5{ class: ('hide' if hide) } Other Labels + %ul.content-list.manage-labels-list.js-other-labels + = render partial: 'shared/label', subject: @group, collection: @labels, as: :label + = paginate @labels, theme: 'gitlab' +- else + = render 'shared/empty_states/labels' + +%template#js-badge-item-template + %li.label-link-item.js-priority-badge.inline.prepend-left-10 + .label-badge.label-badge-blue Prioritized label diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index bccca0bb7d6..ec84c565aae 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -6,17 +6,18 @@ - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) %li.label-list-item{ id: label_css_id, data: { id: label.id } } - = render "shared/label_row", label: label + = render "shared/label_row", label: label, subject: subject %ul.label-actions-list %li.inline .label-badge.label-badge-gray= label.model_name.human.capitalize - - if can?(current_user, :admin_label, @project) - %li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), - dom_id: dom_id(label), type: label.type } } - %button.label-action.add-priority.btn.btn-transparent.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' } - = icon('star-o') - %button.label-action.remove-priority.btn.btn-transparent.has-tooltip{ title: 'Remove priority', type: 'button', :'data-placement' => 'top' } - = icon('star') + - if can?(current_user, :admin_label, label) + - if subject.is_a?(Project) + %li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), + dom_id: dom_id(label), type: label.type } } + %button.label-action.add-priority.btn.btn-transparent.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' } + = icon('star-o') + %button.label-action.remove-priority.btn.btn-transparent.has-tooltip{ title: 'Remove priority', type: 'button', :'data-placement' => 'top' } + = icon('star') %li.inline = link_to edit_label_path(label), class: 'btn btn-transparent label-action' do = icon('pencil') -- cgit v1.2.1 From c792501440c7ecc2123d654747364447f74b7491 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 25 Apr 2018 12:49:01 +0100 Subject: Fix pri toggle conditional --- app/views/shared/_label.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index ec84c565aae..f325c45a53f 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -11,7 +11,7 @@ %li.inline .label-badge.label-badge-gray= label.model_name.human.capitalize - if can?(current_user, :admin_label, label) - - if subject.is_a?(Project) + - if @project.present? %li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), dom_id: dom_id(label), type: label.type } } %button.label-action.add-priority.btn.btn-transparent.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' } -- cgit v1.2.1 From 57129140ba1160fd3d61588708d98602c421ee97 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Thu, 26 Apr 2018 01:18:22 +0200 Subject: backport changes from gitlab-org/gitlab-ee!5461 --- .../services/projects/housekeeping_service_spec.rb | 72 ++++++++++++---------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index b7b5de07380..1cf373d1d72 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::HousekeepingService do subject { described_class.new(project) } - let(:project) { create(:project, :repository) } + set(:project) { create(:project, :repository) } before do project.reset_pushes_since_gc @@ -16,12 +16,12 @@ describe Projects::HousekeepingService do it 'enqueues a sidekiq job' do expect(subject).to receive(:try_obtain_lease).and_return(:the_uuid) expect(subject).to receive(:lease_key).and_return(:the_lease_key) - expect(subject).to receive(:task).and_return(:the_task) - expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :the_task, :the_lease_key, :the_uuid) + expect(subject).to receive(:task).and_return(:incremental_repack) + expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid).and_call_original - subject.execute - - expect(project.reload.pushes_since_gc).to eq(0) + Sidekiq::Testing.fake! do + expect { subject.execute }.to change(GitGarbageCollectWorker.jobs, :size).by(1) + end end it 'yields the block if given' do @@ -30,6 +30,16 @@ describe Projects::HousekeepingService do end.to yield_with_no_args end + it 'resets counter after execution' do + expect(subject).to receive(:try_obtain_lease).and_return(:the_uuid) + allow(subject).to receive(:gc_period).and_return(1) + project.increment_pushes_since_gc + + Sidekiq::Testing.inline! do + expect { subject.execute }.to change { project.pushes_since_gc }.to(0) + end + end + context 'when no lease can be obtained' do before do expect(subject).to receive(:try_obtain_lease).and_return(false) @@ -54,6 +64,30 @@ describe Projects::HousekeepingService do end.not_to yield_with_no_args end end + + context 'task type' do + it 'goes through all three housekeeping tasks, executing only the highest task when there is overlap' do + allow(subject).to receive(:try_obtain_lease).and_return(:the_uuid) + allow(subject).to receive(:lease_key).and_return(:the_lease_key) + + # At push 200 + expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :the_lease_key, :the_uuid) + .exactly(1).times + # At push 50, 100, 150 + expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :full_repack, :the_lease_key, :the_uuid) + .exactly(3).times + # At push 10, 20, ... (except those above) + expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid) + .exactly(16).times + + 201.times do + subject.increment! + subject.execute if subject.needed? + end + + expect(project.pushes_since_gc).to eq(1) + end + end end describe '#needed?' do @@ -69,31 +103,7 @@ describe Projects::HousekeepingService do describe '#increment!' do it 'increments the pushes_since_gc counter' do - expect do - subject.increment! - end.to change { project.pushes_since_gc }.from(0).to(1) + expect { subject.increment! }.to change { project.pushes_since_gc }.by(1) end end - - it 'goes through all three housekeeping tasks, executing only the highest task when there is overlap' do - allow(subject).to receive(:try_obtain_lease).and_return(:the_uuid) - allow(subject).to receive(:lease_key).and_return(:the_lease_key) - - # At push 200 - expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :the_lease_key, :the_uuid) - .exactly(1).times - # At push 50, 100, 150 - expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :full_repack, :the_lease_key, :the_uuid) - .exactly(3).times - # At push 10, 20, ... (except those above) - expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid) - .exactly(16).times - - 201.times do - subject.increment! - subject.execute if subject.needed? - end - - expect(project.pushes_since_gc).to eq(1) - end end -- cgit v1.2.1 From c3836b9e45f8ad8609e80ff09f96f03d2634b507 Mon Sep 17 00:00:00 2001 From: Tom Downes Date: Fri, 27 Apr 2018 03:12:47 +0000 Subject: Update google.md to reflect need for Google+ API for oauth integration; clarify need for Kubernetes Engine API is only necessary for CI/CD integration with GKE --- doc/integration/google.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/integration/google.md b/doc/integration/google.md index ae1d848f439..8906f91b6b4 100644 --- a/doc/integration/google.md +++ b/doc/integration/google.md @@ -35,7 +35,12 @@ In Google's side: 1. You should now be able to see a Client ID and Client secret. Note them down or keep this page open as you will need them later. -1. From the **Dashboard** select **ENABLE APIS AND SERVICES > Compute > Google Kubernetes Engine API > Enable** +1. From the **Dashboard** select **ENABLE APIS AND SERVICES > Compute > Google+ API > Enable** +1. To enable projects to access [Google Kubernetes Engine](../user/project/clusters/index.md), you must also + enable these APIs: + - Google Kubernetes Engine API + - Cloud Resource Manager API + - Cloud Billing API On your GitLab server: -- cgit v1.2.1 From 3248ca0467a4bd816f281db1b30ca83df2865edd Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 24 Apr 2018 17:08:43 +0900 Subject: Add per-project pipeline id --- app/models/ci/pipeline.rb | 3 +++ app/models/internal_id.rb | 2 +- .../20180424160449_add_pipeline_iid_to_ci_pipelines.rb | 15 +++++++++++++++ db/schema.rb | 1 + 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index e1b9bc76475..65b282ecec4 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -7,12 +7,15 @@ module Ci include Presentable include Gitlab::OptimisticLocking include Gitlab::Utils::StrongMemoize + include AtomicInternalId belongs_to :project, inverse_of: :pipelines belongs_to :user belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' + has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.pipelines.maximum(:iid) } + has_many :stages has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb index 189942c5ad8..dbd82dda06e 100644 --- a/app/models/internal_id.rb +++ b/app/models/internal_id.rb @@ -14,7 +14,7 @@ class InternalId < ActiveRecord::Base belongs_to :project belongs_to :namespace - enum usage: { issues: 0, merge_requests: 1, deployments: 2, milestones: 3, epics: 4 } + enum usage: { issues: 0, merge_requests: 1, deployments: 2, milestones: 3, epics: 4, ci_pipelines: 5 } validates :usage, presence: true diff --git a/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb b/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb new file mode 100644 index 00000000000..d732116e9ce --- /dev/null +++ b/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb @@ -0,0 +1,15 @@ +class AddPipelineIidToCiPipelines < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column :ci_pipelines, :iid, :integer + end + + def down + remove_column :ci_pipelines, :iid, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 10cd1bff125..d4bc075eb2e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -434,6 +434,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do t.integer "config_source" t.boolean "protected" t.integer "failure_reason" + t.integer "iid" end add_index "ci_pipelines", ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree -- cgit v1.2.1 From 332d3d0ef5215c2a363c99c90fb7d31af90a0a5a Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 25 Apr 2018 21:03:35 +0900 Subject: Change column name to iid_per_project. Add index to project_id and iid --- .../20180424160449_add_pipeline_iid_to_ci_pipelines.rb | 4 ++-- ...0180425205249_add_index_constraints_to_pipeline_iid.rb | 15 +++++++++++++++ db/schema.rb | 5 +++-- 3 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20180425205249_add_index_constraints_to_pipeline_iid.rb diff --git a/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb b/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb index d732116e9ce..128377e1c9d 100644 --- a/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb +++ b/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb @@ -6,10 +6,10 @@ class AddPipelineIidToCiPipelines < ActiveRecord::Migration disable_ddl_transaction! def up - add_column :ci_pipelines, :iid, :integer + add_column :ci_pipelines, :iid_per_project, :integer end def down - remove_column :ci_pipelines, :iid, :integer + remove_column :ci_pipelines, :iid_per_project, :integer end end diff --git a/db/migrate/20180425205249_add_index_constraints_to_pipeline_iid.rb b/db/migrate/20180425205249_add_index_constraints_to_pipeline_iid.rb new file mode 100644 index 00000000000..7daa7197b7c --- /dev/null +++ b/db/migrate/20180425205249_add_index_constraints_to_pipeline_iid.rb @@ -0,0 +1,15 @@ +class AddIndexConstraintsToPipelineIid < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :ci_pipelines, [:project_id, :iid_per_project], unique: true, where: 'iid_per_project IS NOT NULL' + end + + def down + remove_concurrent_index :ci_pipelines, [:project_id, :iid_per_project] + end +end diff --git a/db/schema.rb b/db/schema.rb index d4bc075eb2e..af838e109b2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180425131009) do +ActiveRecord::Schema.define(version: 20180425205249) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -434,11 +434,12 @@ ActiveRecord::Schema.define(version: 20180425131009) do t.integer "config_source" t.boolean "protected" t.integer "failure_reason" - t.integer "iid" + t.integer "iid_per_project" end add_index "ci_pipelines", ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree add_index "ci_pipelines", ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id", using: :btree + add_index "ci_pipelines", ["project_id", "iid_per_project"], name: "index_ci_pipelines_on_project_id_and_iid_per_project", unique: true, where: "(iid_per_project IS NOT NULL)", using: :btree add_index "ci_pipelines", ["project_id", "ref", "status", "id"], name: "index_ci_pipelines_on_project_id_and_ref_and_status_and_id", using: :btree add_index "ci_pipelines", ["project_id", "sha"], name: "index_ci_pipelines_on_project_id_and_sha", using: :btree add_index "ci_pipelines", ["project_id"], name: "index_ci_pipelines_on_project_id", using: :btree -- cgit v1.2.1 From 8192cd70ed109e9c0b7f4670234af11f206694d4 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 25 Apr 2018 21:42:46 +0900 Subject: Expose CI_PIPELINE_IID_PER_PROJECT variable --- app/models/ci/pipeline.rb | 3 ++- doc/ci/variables/README.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 65b282ecec4..3cdb86e7b55 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -14,7 +14,7 @@ module Ci belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' - has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.pipelines.maximum(:iid) } + has_internal_id :iid_per_project, scope: :project, init: ->(s) { s&.project&.pipelines.count } has_many :stages has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline @@ -492,6 +492,7 @@ module Ci def predefined_variables Gitlab::Ci::Variables::Collection.new .append(key: 'CI_PIPELINE_ID', value: id.to_s) + .append(key: 'CI_PIPELINE_IID_PER_PROJECT', value: iid_per_project.to_s) .append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path) .append(key: 'CI_PIPELINE_SOURCE', value: source.to_s) end diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 38a988f4507..8eb0fc5ea49 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -60,6 +60,7 @@ future GitLab releases.** | **CI_RUNNER_REVISION** | all | 10.6 | GitLab Runner revision that is executing the current job | | **CI_RUNNER_EXECUTABLE_ARCH** | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) | | **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally | +| **CI_PIPELINE_IID_PER_PROJECT** | 10.8 | all | The unique id of the current pipeline that GitLab CI uses internally (Incremented per project) | | **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] | | **CI_PIPELINE_SOURCE** | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` | | **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run | -- cgit v1.2.1 From 8327ad8d8edffe17870571aff1dd68a6c0bce9e7 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 25 Apr 2018 21:44:05 +0900 Subject: Add changelog --- changelogs/unreleased/per-project-pipeline-iid.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/per-project-pipeline-iid.yml diff --git a/changelogs/unreleased/per-project-pipeline-iid.yml b/changelogs/unreleased/per-project-pipeline-iid.yml new file mode 100644 index 00000000000..78a513a9986 --- /dev/null +++ b/changelogs/unreleased/per-project-pipeline-iid.yml @@ -0,0 +1,5 @@ +--- +title: Add per-project pipeline id +merge_request: 18558 +author: +type: added -- cgit v1.2.1 From 787eddd55d3699c73a69c99eb66e6a4b3b63b330 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 25 Apr 2018 22:00:11 +0900 Subject: Revert column name change --- app/models/ci/pipeline.rb | 4 ++-- db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb | 4 ++-- db/migrate/20180425205249_add_index_constraints_to_pipeline_iid.rb | 4 ++-- db/schema.rb | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 3cdb86e7b55..efab6f4283a 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -14,7 +14,7 @@ module Ci belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' - has_internal_id :iid_per_project, scope: :project, init: ->(s) { s&.project&.pipelines.count } + has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.pipelines.count } has_many :stages has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline @@ -492,7 +492,7 @@ module Ci def predefined_variables Gitlab::Ci::Variables::Collection.new .append(key: 'CI_PIPELINE_ID', value: id.to_s) - .append(key: 'CI_PIPELINE_IID_PER_PROJECT', value: iid_per_project.to_s) + .append(key: 'CI_PIPELINE_IID_PER_PROJECT', value: iid.to_s) .append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path) .append(key: 'CI_PIPELINE_SOURCE', value: source.to_s) end diff --git a/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb b/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb index 128377e1c9d..d732116e9ce 100644 --- a/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb +++ b/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb @@ -6,10 +6,10 @@ class AddPipelineIidToCiPipelines < ActiveRecord::Migration disable_ddl_transaction! def up - add_column :ci_pipelines, :iid_per_project, :integer + add_column :ci_pipelines, :iid, :integer end def down - remove_column :ci_pipelines, :iid_per_project, :integer + remove_column :ci_pipelines, :iid, :integer end end diff --git a/db/migrate/20180425205249_add_index_constraints_to_pipeline_iid.rb b/db/migrate/20180425205249_add_index_constraints_to_pipeline_iid.rb index 7daa7197b7c..3fa59b44d5d 100644 --- a/db/migrate/20180425205249_add_index_constraints_to_pipeline_iid.rb +++ b/db/migrate/20180425205249_add_index_constraints_to_pipeline_iid.rb @@ -6,10 +6,10 @@ class AddIndexConstraintsToPipelineIid < ActiveRecord::Migration disable_ddl_transaction! def up - add_concurrent_index :ci_pipelines, [:project_id, :iid_per_project], unique: true, where: 'iid_per_project IS NOT NULL' + add_concurrent_index :ci_pipelines, [:project_id, :iid], unique: true, where: 'iid IS NOT NULL' end def down - remove_concurrent_index :ci_pipelines, [:project_id, :iid_per_project] + remove_concurrent_index :ci_pipelines, [:project_id, :iid] end end diff --git a/db/schema.rb b/db/schema.rb index af838e109b2..50abe1a8838 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -434,12 +434,12 @@ ActiveRecord::Schema.define(version: 20180425205249) do t.integer "config_source" t.boolean "protected" t.integer "failure_reason" - t.integer "iid_per_project" + t.integer "iid" end add_index "ci_pipelines", ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree add_index "ci_pipelines", ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id", using: :btree - add_index "ci_pipelines", ["project_id", "iid_per_project"], name: "index_ci_pipelines_on_project_id_and_iid_per_project", unique: true, where: "(iid_per_project IS NOT NULL)", using: :btree + add_index "ci_pipelines", ["project_id", "iid"], name: "index_ci_pipelines_on_project_id_and_iid", unique: true, where: "(iid IS NOT NULL)", using: :btree add_index "ci_pipelines", ["project_id", "ref", "status", "id"], name: "index_ci_pipelines_on_project_id_and_ref_and_status_and_id", using: :btree add_index "ci_pipelines", ["project_id", "sha"], name: "index_ci_pipelines_on_project_id_and_sha", using: :btree add_index "ci_pipelines", ["project_id"], name: "index_ci_pipelines_on_project_id", using: :btree -- cgit v1.2.1 From 35cf604b72ec6cabffc46cc7c2da76fb303525cb Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 30 Apr 2018 22:25:56 +0900 Subject: Add to_param override to lookup :id path --- spec/models/ci/pipeline_spec.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index dd94515b0a4..51cdf185c9f 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -35,6 +35,15 @@ describe Ci::Pipeline, :mailer do end end + describe 'modules' do + it_behaves_like 'AtomicInternalId' do + let(:internal_id_attribute) { :iid } + let(:instance) { build(:ci_pipeline) } + let(:scope_attrs) { { project: instance.project } } + let(:usage) { :ci_pipelines } + end + end + describe '#source' do context 'when creating new pipeline' do let(:pipeline) do @@ -173,7 +182,7 @@ describe Ci::Pipeline, :mailer do it 'includes all predefined variables in a valid order' do keys = subject.map { |variable| variable[:key] } - expect(keys).to eq %w[CI_PIPELINE_ID CI_CONFIG_PATH CI_PIPELINE_SOURCE] + expect(keys).to eq %w[CI_PIPELINE_ID CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE] end end -- cgit v1.2.1 From 58f229af992a9481c9eee3165dc5d14eb1dc7d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Thu, 3 May 2018 10:48:23 +0200 Subject: Make Atomic Internal ID work for pipelines --- app/models/ci/pipeline.rb | 4 +++- app/models/concerns/atomic_internal_id.rb | 18 +++++++++++------- lib/gitlab/ci/pipeline/chain/create.rb | 3 +++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index efab6f4283a..0622b9c6918 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -14,7 +14,9 @@ module Ci belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' - has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.pipelines.count } + has_internal_id :iid, scope: :project, presence: false, to_param: false, init: -> do |s| + s&.project&.pipelines&.maximum(:iid) || s&.project&.pipelines.count + end has_many :stages has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb index 22f516a172f..709589a9128 100644 --- a/app/models/concerns/atomic_internal_id.rb +++ b/app/models/concerns/atomic_internal_id.rb @@ -25,9 +25,13 @@ module AtomicInternalId extend ActiveSupport::Concern module ClassMethods - def has_internal_id(column, scope:, init:) # rubocop:disable Naming/PredicateName - before_validation(on: :create) do + def has_internal_id(column, scope:, init:, presence: true, to_param: true) # rubocop:disable Naming/PredicateName + before_validation :"ensure_#{column}!", on: :create + validates column, presence: presence, numericality: true + + define_method("ensure_#{column}!") do scope_value = association(scope).reader + if read_attribute(column).blank? && scope_value scope_attrs = { scope_value.class.table_name.singularize.to_sym => scope_value } usage = self.class.table_name.to_sym @@ -35,13 +39,13 @@ module AtomicInternalId new_iid = InternalId.generate_next(self, scope_attrs, usage, init) write_attribute(column, new_iid) end + + read_attribute(column) end - validates column, presence: true, numericality: true + define_method("to_param") do + read_attribute(column) + end if to_param end end - - def to_param - iid.to_s - end end diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb index f4c8d5342c1..5967a7a6a58 100644 --- a/lib/gitlab/ci/pipeline/chain/create.rb +++ b/lib/gitlab/ci/pipeline/chain/create.rb @@ -6,6 +6,9 @@ module Gitlab include Chain::Helpers def perform! + # TODO: allocate next IID outside of transaction + pipeline.ensure_iid! + ::Ci::Pipeline.transaction do pipeline.save! -- cgit v1.2.1 From 07d1d8bd6730015e65bd5123f305bf35b4839237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Thu, 3 May 2018 10:50:57 +0200 Subject: Use **CI_PIPELINE_IID** --- app/models/ci/pipeline.rb | 2 +- doc/ci/variables/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 0622b9c6918..84c5dae24bb 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -494,7 +494,7 @@ module Ci def predefined_variables Gitlab::Ci::Variables::Collection.new .append(key: 'CI_PIPELINE_ID', value: id.to_s) - .append(key: 'CI_PIPELINE_IID_PER_PROJECT', value: iid.to_s) + .append(key: 'CI_PIPELINE_IID', value: iid.to_s) .append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path) .append(key: 'CI_PIPELINE_SOURCE', value: source.to_s) end diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 8eb0fc5ea49..2282cc24beb 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -60,7 +60,7 @@ future GitLab releases.** | **CI_RUNNER_REVISION** | all | 10.6 | GitLab Runner revision that is executing the current job | | **CI_RUNNER_EXECUTABLE_ARCH** | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) | | **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally | -| **CI_PIPELINE_IID_PER_PROJECT** | 10.8 | all | The unique id of the current pipeline that GitLab CI uses internally (Incremented per project) | +| **CI_PIPELINE_IID** | 10.8 | all | The unique id of the current pipeline scoped to project | | **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] | | **CI_PIPELINE_SOURCE** | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` | | **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run | -- cgit v1.2.1 From f6f60443f58466895b46add7a26dbbc5e9bd44c9 Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Mon, 7 May 2018 14:15:06 +0300 Subject: Change label link vertical alignment property --- app/assets/stylesheets/pages/labels.scss | 2 +- .../unreleased/remove-link-label-vertical-alignment-property.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/remove-link-label-vertical-alignment-property.yml diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index d81236c5883..96ea72c69fe 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -312,7 +312,7 @@ .label-link { display: inline-flex; - vertical-align: top; + vertical-align: text-bottom; &:hover .color-label { text-decoration: underline; diff --git a/changelogs/unreleased/remove-link-label-vertical-alignment-property.yml b/changelogs/unreleased/remove-link-label-vertical-alignment-property.yml new file mode 100644 index 00000000000..40ec3998b05 --- /dev/null +++ b/changelogs/unreleased/remove-link-label-vertical-alignment-property.yml @@ -0,0 +1,5 @@ +--- +title: Change label link vertical alignment property +merge_request: 18777 +author: George Tsiolis +type: changed -- cgit v1.2.1 From 0af2ab18fa7914380150c0403289a9d99ea45ded Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 8 May 2018 14:57:41 +0900 Subject: Decouple to_params from AtomicInternalId concern --- app/models/ci/pipeline.rb | 2 +- app/models/concerns/atomic_internal_id.rb | 6 +----- app/models/concerns/iid_routes.rb | 9 +++++++++ app/models/deployment.rb | 1 + app/models/issue.rb | 1 + app/models/merge_request.rb | 1 + app/models/milestone.rb | 1 + 7 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 app/models/concerns/iid_routes.rb diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 6bd2c42bbd3..d542868f01f 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -14,7 +14,7 @@ module Ci belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' - has_internal_id :iid, scope: :project, presence: false, to_param: false, init: -> do |s| + has_internal_id :iid, scope: :project, presence: false, init: -> do |s| s&.project&.pipelines&.maximum(:iid) || s&.project&.pipelines.count end diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb index 709589a9128..3d867df544f 100644 --- a/app/models/concerns/atomic_internal_id.rb +++ b/app/models/concerns/atomic_internal_id.rb @@ -25,7 +25,7 @@ module AtomicInternalId extend ActiveSupport::Concern module ClassMethods - def has_internal_id(column, scope:, init:, presence: true, to_param: true) # rubocop:disable Naming/PredicateName + def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName before_validation :"ensure_#{column}!", on: :create validates column, presence: presence, numericality: true @@ -42,10 +42,6 @@ module AtomicInternalId read_attribute(column) end - - define_method("to_param") do - read_attribute(column) - end if to_param end end end diff --git a/app/models/concerns/iid_routes.rb b/app/models/concerns/iid_routes.rb new file mode 100644 index 00000000000..50957e0230d --- /dev/null +++ b/app/models/concerns/iid_routes.rb @@ -0,0 +1,9 @@ +module IIDRoutes + ## + # This automagically enforces all related routes to use `iid` instead of `id` + # If you want to use `iid` for some routes and `id` for other routes, this module should not to be included, + # instead you should define `iid` or `id` explictly at each route generators. e.g. pipeline_path(project.id, pipeline.iid) + def to_param + iid.to_s + end +end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 254764eefde..dac97676348 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -1,5 +1,6 @@ class Deployment < ActiveRecord::Base include AtomicInternalId + include IIDRoutes belongs_to :project, required: true belongs_to :environment, required: true diff --git a/app/models/issue.rb b/app/models/issue.rb index 0332bfa9371..6d33a67845f 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -2,6 +2,7 @@ require 'carrierwave/orm/activerecord' class Issue < ActiveRecord::Base include AtomicInternalId + include IIDRoutes include Issuable include Noteable include Referable diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 628c61d5d69..a14681897fd 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1,5 +1,6 @@ class MergeRequest < ActiveRecord::Base include AtomicInternalId + include IIDRoutes include Issuable include Noteable include Referable diff --git a/app/models/milestone.rb b/app/models/milestone.rb index d14e3a4ded5..f143b8452a2 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -9,6 +9,7 @@ class Milestone < ActiveRecord::Base include CacheMarkdownField include AtomicInternalId + include IIDRoutes include Sortable include Referable include StripAttribute -- cgit v1.2.1 From 04dc80dbb5cb991172ebeb69b9a20c7b6eef4dbf Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 8 May 2018 16:01:18 +0900 Subject: Fix spec --- app/models/ci/pipeline.rb | 2 +- doc/ci/variables/README.md | 2 +- spec/models/ci/pipeline_spec.rb | 1 + .../shared_examples/models/atomic_internal_id_spec.rb | 13 +++++++++++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d542868f01f..e9a56525fde 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -14,7 +14,7 @@ module Ci belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' - has_internal_id :iid, scope: :project, presence: false, init: -> do |s| + has_internal_id :iid, scope: :project, presence: false, init: ->(s) do s&.project&.pipelines&.maximum(:iid) || s&.project&.pipelines.count end diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 7fa293665e0..4a83d4fbe33 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -63,7 +63,7 @@ future GitLab releases.** | **CI_RUNNER_REVISION** | all | 10.6 | GitLab Runner revision that is executing the current job | | **CI_RUNNER_EXECUTABLE_ARCH** | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) | | **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally | -| **CI_PIPELINE_IID** | 10.8 | all | The unique id of the current pipeline scoped to project | +| **CI_PIPELINE_IID** | 11.0 | all | The unique id of the current pipeline scoped to project | | **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] | | **CI_PIPELINE_SOURCE** | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` | | **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run | diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 60ba9e62be7..d3e0389cc72 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -41,6 +41,7 @@ describe Ci::Pipeline, :mailer do let(:instance) { build(:ci_pipeline) } let(:scope_attrs) { { project: instance.project } } let(:usage) { :ci_pipelines } + let(:validate_presence) { false } end end diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb index 6a6e13418a9..1bccb578d79 100644 --- a/spec/support/shared_examples/models/atomic_internal_id_spec.rb +++ b/spec/support/shared_examples/models/atomic_internal_id_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' -shared_examples_for 'AtomicInternalId' do +shared_examples_for 'AtomicInternalId' do + let(:validate_presence) { true } + describe '.has_internal_id' do describe 'Module inclusion' do subject { described_class } @@ -15,7 +17,14 @@ shared_examples_for 'AtomicInternalId' do allow(InternalId).to receive(:generate_next).and_return(nil) end - it { is_expected.to validate_presence_of(internal_id_attribute) } + it 'checks presence' do + if validate_presence + is_expected.to validate_presence_of(internal_id_attribute) + else + is_expected.not_to validate_presence_of(internal_id_attribute) + end + end + it { is_expected.to validate_numericality_of(internal_id_attribute) } end -- cgit v1.2.1 From 892717452c1fef5e31c1372f1e96d6312a2d3328 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 8 May 2018 20:53:40 +0100 Subject: UX review changes --- app/assets/stylesheets/pages/labels.scss | 9 +++++++-- app/views/groups/labels/index.html.haml | 2 +- app/views/projects/labels/index.html.haml | 2 +- app/views/shared/_label.html.haml | 10 +++++----- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index b63b552415d..591a3414355 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -234,9 +234,10 @@ .label-badge { color: $theme-gray-900; - font-weight: $gl-font-weight-bold; - padding: $gl-padding-4; + font-weight: $gl-font-weight-normal; + padding: $gl-padding-4 $gl-padding-8; border-radius: $border-radius-default; + font-size: $label-font-size; } .label-badge-blue { @@ -312,4 +313,8 @@ } } } +} + +.priority-labels-empty-state .svg-content img { + max-width: 114px; } \ No newline at end of file diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 1173499f921..1fa6389de0f 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -25,7 +25,7 @@ .prioritized-labels{ class: ('hide' if hide) } %h5.prepend-top-0 Prioritized Labels %ul.content-list.manage-labels-list.js-prioritized-labels - #js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } + #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } = render 'shared/empty_states/priority_labels' - if @prioritized_labels.present? = render partial: 'shared/label', subject: @group, collection: @prioritized_labels, as: :label diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 632355fa338..1bb90281093 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -25,7 +25,7 @@ .prioritized-labels{ class: ('hide' if hide) } %h5.prepend-top-0 Prioritized Labels %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) } - #js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } + #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } = render 'shared/empty_states/priority_labels' - if @prioritized_labels.present? = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index f325c45a53f..4d6a822ca14 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -15,16 +15,16 @@ %li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), dom_id: dom_id(label), type: label.type } } %button.label-action.add-priority.btn.btn-transparent.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' } - = icon('star-o') + = sprite_icon('star-o') %button.label-action.remove-priority.btn.btn-transparent.has-tooltip{ title: 'Remove priority', type: 'button', :'data-placement' => 'top' } - = icon('star') + = sprite_icon('star') %li.inline = link_to edit_label_path(label), class: 'btn btn-transparent label-action' do - = icon('pencil') + = sprite_icon('pencil') %li.inline .dropdown %button{ type: 'button', class: 'btn btn-transparent js-label-options-dropdown label-action', data: { toggle: 'dropdown' } } - = custom_icon('ellipsis_v') + = sprite_icon('ellipsis_v') .dropdown-menu.dropdown-menu-align-right %ul - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) @@ -50,7 +50,7 @@ %button.label-subscribe-button.btn.btn-default{ data: { toggle: 'dropdown' } } %span Subscribe - = icon('chevron-down') + = sprite_icon('chevron-down') .dropdown-menu.dropdown-menu-align-right %ul %li -- cgit v1.2.1 From 30a6fb64dbce39cc0298e805935bb242c2d7b715 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 10 May 2018 15:56:47 +0900 Subject: Fix atomic internal id spec to allow generate pipeline --- .../shared_examples/models/atomic_internal_id_spec.rb | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb index 1bccb578d79..cd6465675b5 100644 --- a/spec/support/shared_examples/models/atomic_internal_id_spec.rb +++ b/spec/support/shared_examples/models/atomic_internal_id_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -shared_examples_for 'AtomicInternalId' do +shared_examples_for 'AtomicInternalId' do let(:validate_presence) { true } describe '.has_internal_id' do @@ -11,21 +11,16 @@ shared_examples_for 'AtomicInternalId' do end describe 'Validation' do - subject { instance } - before do - allow(InternalId).to receive(:generate_next).and_return(nil) + allow_any_instance_of(described_class).to receive(:ensure_iid!) {} end - it 'checks presence' do - if validate_presence - is_expected.to validate_presence_of(internal_id_attribute) - else - is_expected.not_to validate_presence_of(internal_id_attribute) - end - end + it 'validates presence' do + instance.valid? - it { is_expected.to validate_numericality_of(internal_id_attribute) } + expect(instance.errors[:iid]).to include("can't be blank") if validate_presence + expect(instance.errors[:iid]).to include("is not a number") # numericality + end end describe 'Creating an instance' do -- cgit v1.2.1 From 6a108b8fbd5f1b5c2d8e6b51bb34cda06fe0e5ee Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 10 May 2018 16:22:43 +0900 Subject: Fix ensure_iid! method override problem --- app/models/concerns/atomic_internal_id.rb | 4 ++-- lib/gitlab/ci/pipeline/chain/create.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb index 3d867df544f..876dd0ee1f2 100644 --- a/app/models/concerns/atomic_internal_id.rb +++ b/app/models/concerns/atomic_internal_id.rb @@ -26,10 +26,10 @@ module AtomicInternalId module ClassMethods def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName - before_validation :"ensure_#{column}!", on: :create + before_validation :"ensure_#{scope}_#{column}!", on: :create validates column, presence: presence, numericality: true - define_method("ensure_#{column}!") do + define_method("ensure_#{scope}_#{column}!") do scope_value = association(scope).reader if read_attribute(column).blank? && scope_value diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb index 5967a7a6a58..918a0d151fc 100644 --- a/lib/gitlab/ci/pipeline/chain/create.rb +++ b/lib/gitlab/ci/pipeline/chain/create.rb @@ -6,8 +6,8 @@ module Gitlab include Chain::Helpers def perform! - # TODO: allocate next IID outside of transaction - pipeline.ensure_iid! + # Allocate next IID outside of transaction + pipeline.ensure_project_iid! ::Ci::Pipeline.transaction do pipeline.save! -- cgit v1.2.1 From 910a7d02a812b1203e320d843a77cad2c7069b63 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 11 May 2018 15:34:36 +0900 Subject: Remove numericality as it's redandant with integer column and validates nil IID --- app/models/concerns/atomic_internal_id.rb | 2 +- spec/lib/gitlab/import_export/safe_model_attributes.yml | 1 + spec/models/ci/build_spec.rb | 1 + spec/support/shared_examples/models/atomic_internal_id_spec.rb | 3 +-- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb index 876dd0ee1f2..164c704260e 100644 --- a/app/models/concerns/atomic_internal_id.rb +++ b/app/models/concerns/atomic_internal_id.rb @@ -27,7 +27,7 @@ module AtomicInternalId module ClassMethods def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName before_validation :"ensure_#{scope}_#{column}!", on: :create - validates column, presence: presence, numericality: true + validates column, presence: presence define_method("ensure_#{scope}_#{column}!") do scope_value = association(scope).reader diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 62da967cf96..2619c6a10d8 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -228,6 +228,7 @@ Ci::Pipeline: - config_source - failure_reason - protected +- iid Ci::Stage: - id - name diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index dc810489011..490b1f0b54e 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1517,6 +1517,7 @@ describe Ci::Build do { key: 'CI_PROJECT_URL', value: project.web_url, public: true }, { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true }, { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, + { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true }, { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true }, { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true }, { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true }, diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb index cd6465675b5..ac1cfa47def 100644 --- a/spec/support/shared_examples/models/atomic_internal_id_spec.rb +++ b/spec/support/shared_examples/models/atomic_internal_id_spec.rb @@ -12,14 +12,13 @@ shared_examples_for 'AtomicInternalId' do describe 'Validation' do before do - allow_any_instance_of(described_class).to receive(:ensure_iid!) {} + allow_any_instance_of(described_class).to receive(:"ensure_#{scope_attrs.keys.first}_#{internal_id_attribute}!") {} end it 'validates presence' do instance.valid? expect(instance.errors[:iid]).to include("can't be blank") if validate_presence - expect(instance.errors[:iid]).to include("is not a number") # numericality end end -- cgit v1.2.1 From 46fa3089c84642170d86799c4f60fe87507e575d Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 11 May 2018 16:49:18 +0900 Subject: Rescue RecordNotUnique when pipeline is created with non-unique iid --- lib/gitlab/ci/pipeline/chain/create.rb | 2 +- spec/lib/gitlab/ci/pipeline/chain/create_spec.rb | 37 ++++++++++++++++------ spec/models/ci/pipeline_spec.rb | 2 +- .../models/atomic_internal_id_spec.rb | 8 +++-- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb index 918a0d151fc..e62056766bd 100644 --- a/lib/gitlab/ci/pipeline/chain/create.rb +++ b/lib/gitlab/ci/pipeline/chain/create.rb @@ -23,7 +23,7 @@ module Gitlab end end end - rescue ActiveRecord::RecordInvalid => e + rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique => e error("Failed to persist the pipeline: #{e}") end diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb index 0edc3f315bb..b8ab0135092 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb @@ -37,21 +37,38 @@ describe Gitlab::Ci::Pipeline::Chain::Create do end context 'when pipeline has validation errors' do - let(:pipeline) do - build(:ci_pipeline, project: project, ref: nil) + shared_examples_for 'expectations' do + it 'breaks the chain' do + expect(step.break?).to be true + end + + it 'appends validation error' do + expect(pipeline.errors.to_a) + .to include /Failed to persist the pipeline/ + end end + + context 'when ref is nil' do + let(:pipeline) do + build(:ci_pipeline, project: project, ref: nil) + end - before do - step.perform! - end + before do + step.perform! + end - it 'breaks the chain' do - expect(step.break?).to be true + it_behaves_like 'expectations' end - it 'appends validation error' do - expect(pipeline.errors.to_a) - .to include /Failed to persist the pipeline/ + context 'when pipeline has a duplicate iid' do + before do + allow_any_instance_of(Ci::Pipeline).to receive(:ensure_project_iid!) { |p| p.send(:write_attribute, :iid, 1) } + create(:ci_pipeline, project: project) + + step.perform! + end + + it_behaves_like 'expectations' end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index d3e0389cc72..c10d0eb55da 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -41,7 +41,7 @@ describe Ci::Pipeline, :mailer do let(:instance) { build(:ci_pipeline) } let(:scope_attrs) { { project: instance.project } } let(:usage) { :ci_pipelines } - let(:validate_presence) { false } + let(:allow_nil) { true } end end diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb index ac1cfa47def..dce2622172b 100644 --- a/spec/support/shared_examples/models/atomic_internal_id_spec.rb +++ b/spec/support/shared_examples/models/atomic_internal_id_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' shared_examples_for 'AtomicInternalId' do - let(:validate_presence) { true } + let(:allow_nil) { false } describe '.has_internal_id' do describe 'Module inclusion' do @@ -18,7 +18,11 @@ shared_examples_for 'AtomicInternalId' do it 'validates presence' do instance.valid? - expect(instance.errors[:iid]).to include("can't be blank") if validate_presence + if allow_nil + expect(instance.errors[:iid]).to be_empty + else + expect(instance.errors[:iid]).to include("can't be blank") + end end end -- cgit v1.2.1 From a74184eb5e692ef77fe3be28b1f4a40549c8fcff Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 11 May 2018 16:52:48 +0900 Subject: Fix static analysys --- app/models/ci/pipeline.rb | 2 +- app/models/concerns/iid_routes.rb | 2 +- app/models/deployment.rb | 2 +- app/models/issue.rb | 2 +- app/models/merge_request.rb | 2 +- app/models/milestone.rb | 2 +- spec/lib/gitlab/ci/pipeline/chain/create_spec.rb | 6 +++--- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index e9a56525fde..accd0f11a9b 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -15,7 +15,7 @@ module Ci belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' has_internal_id :iid, scope: :project, presence: false, init: ->(s) do - s&.project&.pipelines&.maximum(:iid) || s&.project&.pipelines.count + s&.project&.pipelines&.maximum(:iid) || s&.project&.pipelines&.count end has_many :stages diff --git a/app/models/concerns/iid_routes.rb b/app/models/concerns/iid_routes.rb index 50957e0230d..246748cf52c 100644 --- a/app/models/concerns/iid_routes.rb +++ b/app/models/concerns/iid_routes.rb @@ -1,4 +1,4 @@ -module IIDRoutes +module IidRoutes ## # This automagically enforces all related routes to use `iid` instead of `id` # If you want to use `iid` for some routes and `id` for other routes, this module should not to be included, diff --git a/app/models/deployment.rb b/app/models/deployment.rb index dac97676348..ac86e9e8de0 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -1,6 +1,6 @@ class Deployment < ActiveRecord::Base include AtomicInternalId - include IIDRoutes + include IidRoutes belongs_to :project, required: true belongs_to :environment, required: true diff --git a/app/models/issue.rb b/app/models/issue.rb index 6d33a67845f..d0e8fe908e4 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -2,7 +2,7 @@ require 'carrierwave/orm/activerecord' class Issue < ActiveRecord::Base include AtomicInternalId - include IIDRoutes + include IidRoutes include Issuable include Noteable include Referable diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a14681897fd..f42d432cc66 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1,6 +1,6 @@ class MergeRequest < ActiveRecord::Base include AtomicInternalId - include IIDRoutes + include IidRoutes include Issuable include Noteable include Referable diff --git a/app/models/milestone.rb b/app/models/milestone.rb index f143b8452a2..d05dcfd083a 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -9,7 +9,7 @@ class Milestone < ActiveRecord::Base include CacheMarkdownField include AtomicInternalId - include IIDRoutes + include IidRoutes include Sortable include Referable include StripAttribute diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb index b8ab0135092..9c0bedfb6b7 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb @@ -41,13 +41,13 @@ describe Gitlab::Ci::Pipeline::Chain::Create do it 'breaks the chain' do expect(step.break?).to be true end - + it 'appends validation error' do expect(pipeline.errors.to_a) .to include /Failed to persist the pipeline/ end end - + context 'when ref is nil' do let(:pipeline) do build(:ci_pipeline, project: project, ref: nil) @@ -64,7 +64,7 @@ describe Gitlab::Ci::Pipeline::Chain::Create do before do allow_any_instance_of(Ci::Pipeline).to receive(:ensure_project_iid!) { |p| p.send(:write_attribute, :iid, 1) } create(:ci_pipeline, project: project) - + step.perform! end -- cgit v1.2.1 From 82a49d0fea1ace87b5619fbc4ed728f43fdef7bd Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 14 May 2018 17:41:56 +0900 Subject: Clarify scope for AtomicInternalId shared spec --- spec/models/ci/pipeline_spec.rb | 1 + spec/models/deployment_spec.rb | 1 + spec/models/issue_spec.rb | 1 + spec/models/merge_request_spec.rb | 1 + spec/models/milestone_spec.rb | 2 ++ spec/support/shared_examples/models/atomic_internal_id_spec.rb | 6 +++--- 6 files changed, 9 insertions(+), 3 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index c10d0eb55da..e0fe62b39e6 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -39,6 +39,7 @@ describe Ci::Pipeline, :mailer do it_behaves_like 'AtomicInternalId' do let(:internal_id_attribute) { :iid } let(:instance) { build(:ci_pipeline) } + let(:scope) { :project } let(:scope_attrs) { { project: instance.project } } let(:usage) { :ci_pipelines } let(:allow_nil) { true } diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index aee70bcfb29..e01906f4b6c 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -20,6 +20,7 @@ describe Deployment do it_behaves_like 'AtomicInternalId' do let(:internal_id_attribute) { :iid } let(:instance) { build(:deployment) } + let(:scope) { :project } let(:scope_attrs) { { project: instance.project } } let(:usage) { :deployments } end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 128acf83686..e818fbeb9cf 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -17,6 +17,7 @@ describe Issue do it_behaves_like 'AtomicInternalId' do let(:internal_id_attribute) { :iid } let(:instance) { build(:issue) } + let(:scope) { :project } let(:scope_attrs) { { project: instance.project } } let(:usage) { :issues } end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 04379e7d2c3..c2ab1259ebf 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -25,6 +25,7 @@ describe MergeRequest do it_behaves_like 'AtomicInternalId' do let(:internal_id_attribute) { :iid } let(:instance) { build(:merge_request) } + let(:scope) { :target_project } let(:scope_attrs) { { project: instance.target_project } } let(:usage) { :merge_requests } end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 4bb9717d33e..204d6b47832 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -6,6 +6,7 @@ describe Milestone do it_behaves_like 'AtomicInternalId' do let(:internal_id_attribute) { :iid } let(:instance) { build(:milestone, project: build(:project), group: nil) } + let(:scope) { :project } let(:scope_attrs) { { project: instance.project } } let(:usage) { :milestones } end @@ -15,6 +16,7 @@ describe Milestone do it_behaves_like 'AtomicInternalId' do let(:internal_id_attribute) { :iid } let(:instance) { build(:milestone, project: nil, group: build(:group)) } + let(:scope) { :group } let(:scope_attrs) { { namespace: instance.group } } let(:usage) { :milestones } end diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb index dce2622172b..a05279364f2 100644 --- a/spec/support/shared_examples/models/atomic_internal_id_spec.rb +++ b/spec/support/shared_examples/models/atomic_internal_id_spec.rb @@ -12,16 +12,16 @@ shared_examples_for 'AtomicInternalId' do describe 'Validation' do before do - allow_any_instance_of(described_class).to receive(:"ensure_#{scope_attrs.keys.first}_#{internal_id_attribute}!") {} + allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!") {} end it 'validates presence' do instance.valid? if allow_nil - expect(instance.errors[:iid]).to be_empty + expect(instance.errors[internal_id_attribute]).to be_empty else - expect(instance.errors[:iid]).to include("can't be blank") + expect(instance.errors[internal_id_attribute]).to include("can't be blank") end end end -- cgit v1.2.1 From 736292c9502cc57242b4411430f9d9f7faebda9a Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 14 May 2018 12:30:28 +0100 Subject: Add unsub tooltip (broken) --- app/views/shared/_label.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 4d6a822ca14..78daac26b26 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -4,6 +4,7 @@ - toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) +- unsubscribe_tooltip_title = status.subscribed?(current_user, label.group) ? 'Unsubscribe at group level' : status.subscribed?(current_user, @project) ? 'Unsubscribe at project level' : 'Unsubscribe' %li.label-list-item{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label, subject: subject @@ -44,7 +45,7 @@ - if current_user %li.inline.label-subscription - if can_subscribe_to_label_in_different_levels?(label) - %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } } + %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: unsubscribe_tooltip_title, test: status.subscribed?(current_user, label.group), test_two: status.subscribed? } %span Unsubscribe .dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) } %button.label-subscribe-button.btn.btn-default{ data: { toggle: 'dropdown' } } -- cgit v1.2.1 From 6d3bc3970dbced33aac6a91b9ddeba777a6acad6 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 17 May 2018 03:00:35 +0100 Subject: UX review changes --- app/assets/javascripts/label_manager.js | 3 ++- app/assets/stylesheets/pages/labels.scss | 2 ++ app/views/projects/labels/index.html.haml | 16 +++++++++++++--- app/views/shared/_label.html.haml | 6 +++--- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index 8013df53f25..43621deb4fd 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -9,7 +9,7 @@ import axios from './lib/utils/axios_utils'; export default class LabelManager { constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority'); - this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); + this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels-sortable'); this.otherLabels = otherLabels || $('.js-other-labels'); this.errorMessage = 'Unable to update label prioritization at this time'; this.emptyState = document.querySelector('#js-priority-labels-empty-state'); @@ -102,6 +102,7 @@ export default class LabelManager { } onPrioritySortUpdate() { + debugger; this.savePrioritySort() .catch(() => flash(this.errorMessage)); } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 591a3414355..a04f1bb9131 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -188,6 +188,8 @@ } .label-subscribe-button { + width: 105px; + .label-subscribe-button-icon { &[disabled] { opacity: 0.5; diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 1bb90281093..9d3213b0cda 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -24,17 +24,27 @@ - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') .prioritized-labels{ class: ('hide' if hide) } %h5.prepend-top-0 Prioritized Labels - %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) } + .content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) } #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } = render 'shared/empty_states/priority_labels' - if @prioritized_labels.present? - = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label + %table + %thead + %tr + %th + %th + %th + %th + %th + %th + %th + %tbody.js-prioritized-labels-sortable= render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label - if @labels.present? .other-labels - if can_admin_label %h5{ class: ('hide' if hide) } Other Labels - %ul.content-list.manage-labels-list.js-other-labels + .content-list.manage-labels-list.js-other-labels = render partial: 'shared/label', subject: @project, collection: @labels, as: :label = paginate @labels, theme: 'gitlab' - else diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 78daac26b26..7be4d4cf786 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -4,7 +4,7 @@ - toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) -- unsubscribe_tooltip_title = status.subscribed?(current_user, label.group) ? 'Unsubscribe at group level' : status.subscribed?(current_user, @project) ? 'Unsubscribe at project level' : 'Unsubscribe' +- tooltip_title = "#{status.unsubscribed? ? 'Subscribe' : 'Unsubscribe'} at #{label.is_a?(ProjectLabel) ? 'project' : 'group'} level" %li.label-list-item{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label, subject: subject @@ -45,7 +45,7 @@ - if current_user %li.inline.label-subscription - if can_subscribe_to_label_in_different_levels?(label) - %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: unsubscribe_tooltip_title, test: status.subscribed?(current_user, label.group), test_two: status.subscribed? } + %button.js-unsubscribe-button.test.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } %span Unsubscribe .dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) } %button.label-subscribe-button.btn.btn-default{ data: { toggle: 'dropdown' } } @@ -61,7 +61,7 @@ %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } } %span Subscribe at group level - else - %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ data: { status: status, url: toggle_subscription_path } } + %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } %span= label_subscription_toggle_button_text(label, @project) = render 'shared/delete_label_modal', label: label -- cgit v1.2.1 From 2e1c546b087a47afd6b4077fdbae309eba52a3cf Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 17 May 2018 03:03:09 +0100 Subject: i18n --- app/views/shared/_label.html.haml | 16 ++++++++-------- app/views/shared/_label_row.html.haml | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 7be4d4cf786..1f41cb26588 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -15,9 +15,9 @@ - if @project.present? %li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), dom_id: dom_id(label), type: label.type } } - %button.label-action.add-priority.btn.btn-transparent.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' } + %button.label-action.add-priority.btn.btn-transparent.has-tooltip{ title: _('Prioritize'), type: 'button', :'data-placement' => 'top' } = sprite_icon('star-o') - %button.label-action.remove-priority.btn.btn-transparent.has-tooltip{ title: 'Remove priority', type: 'button', :'data-placement' => 'top' } + %button.label-action.remove-priority.btn.btn-transparent.has-tooltip{ title: _('Remove priority'), type: 'button', :'data-placement' => 'top' } = sprite_icon('star') %li.inline = link_to edit_label_path(label), class: 'btn btn-transparent label-action' do @@ -39,27 +39,27 @@ target: '#promote-label-modal', container: 'body', toggle: 'modal' } } - Promote to group label + = _('Promote to group label') %li - = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: { confirm: 'Remove this label? Are you sure?' }, class: 'text-danger' + = link_to _('Delete'), destroy_label_path(label), title: 'Delete', method: :delete, data: { confirm: _('Remove this label? Are you sure?') }, class: 'text-danger' - if current_user %li.inline.label-subscription - if can_subscribe_to_label_in_different_levels?(label) %button.js-unsubscribe-button.test.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } - %span Unsubscribe + %span= _('Unsubscribe') .dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) } %button.label-subscribe-button.btn.btn-default{ data: { toggle: 'dropdown' } } %span - Subscribe + = _('Subscribe') = sprite_icon('chevron-down') .dropdown-menu.dropdown-menu-align-right %ul %li %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_project_label_path(@project, label) } } - %span Subscribe at project level + %span= _('Subscribe at project level') %li %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } } - %span Subscribe at group level + %span= _('Subscribe at group level') - else %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } %span= label_subscription_toggle_button_text(label, @project) diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 51ff88b9981..578fe6bcd8a 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -16,7 +16,7 @@ - if show_label_merge_requests_link · %li.label-link-item.inline - = link_to_label(label, subject: subject, type: :merge_request) { 'Merge requests' } + = link_to_label(label, subject: subject, type: :merge_request) { _('Merge requests') } - if label.priority? %li.label-link-item.js-priority-badge.inline.prepend-left-10 - .label-badge.label-badge-blue Prioritized label \ No newline at end of file + .label-badge.label-badge-blue= _('Prioritized label') \ No newline at end of file -- cgit v1.2.1 From bc2144d1a74eed8f7f76813d4ad8ca16b8f95922 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 17 May 2018 03:19:31 +0100 Subject: Scroll items on overflow --- app/assets/stylesheets/pages/labels.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index a04f1bb9131..9357621e435 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -261,6 +261,8 @@ } .label-list-item { + overflow-x: scroll; + .content-list &:before, .content-list &:after { content: none; -- cgit v1.2.1 From 097f6f5ef86bb8cb8fbc8459ebce782647c7d8f1 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 17 May 2018 03:20:21 +0100 Subject: revert scrollable --- app/assets/javascripts/label_manager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index 43621deb4fd..8013df53f25 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -9,7 +9,7 @@ import axios from './lib/utils/axios_utils'; export default class LabelManager { constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority'); - this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels-sortable'); + this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); this.otherLabels = otherLabels || $('.js-other-labels'); this.errorMessage = 'Unable to update label prioritization at this time'; this.emptyState = document.querySelector('#js-priority-labels-empty-state'); @@ -102,7 +102,6 @@ export default class LabelManager { } onPrioritySortUpdate() { - debugger; this.savePrioritySort() .catch(() => flash(this.errorMessage)); } -- cgit v1.2.1 From 256467f31080ac317228daba2cd74e810d918485 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 17 May 2018 03:28:48 +0100 Subject: Revert table --- app/views/projects/labels/index.html.haml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 9d3213b0cda..725fd8107e8 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -28,17 +28,7 @@ #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } = render 'shared/empty_states/priority_labels' - if @prioritized_labels.present? - %table - %thead - %tr - %th - %th - %th - %th - %th - %th - %th - %tbody.js-prioritized-labels-sortable= render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label + = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label - if @labels.present? .other-labels -- cgit v1.2.1 From bd47118e1b5a814b7660b8b2d808690ca971871e Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 17 May 2018 15:57:46 +0100 Subject: Make sub button text light --- app/assets/stylesheets/pages/labels.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 9357621e435..8e6b9ca6725 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -189,6 +189,7 @@ .label-subscribe-button { width: 105px; + font-weight: 200; .label-subscribe-button-icon { &[disabled] { -- cgit v1.2.1 From 2fd334b86a33cd96c7708980e1f9cd7f8717877a Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 17 May 2018 16:14:17 +0100 Subject: Fix group labels list --- app/assets/stylesheets/pages/labels.scss | 2 -- app/controllers/groups/labels_controller.rb | 6 +++--- app/views/groups/labels/index.html.haml | 27 ++++++++------------------- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 8e6b9ca6725..6348c5a878d 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -262,8 +262,6 @@ } .label-list-item { - overflow-x: scroll; - .content-list &:before, .content-list &:after { content: none; diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 596dd5b2d69..0570edb51c2 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -9,11 +9,11 @@ class Groups::LabelsController < Groups::ApplicationController respond_to :html def index - @prioritized_labels = @available_labels.prioritized(@group) - @labels = @available_labels.unprioritized(@group).page(params[:page]) + @labels = @group.labels.page(params[:page]) respond_to do |format| - format.html + format.html do + end format.json do render json: LabelSerializer.new.represent_appearance(@available_labels) end diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 1fa6389de0f..44b64a31972 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -2,6 +2,7 @@ - page_title "Labels" - can_admin_label = can?(current_user, :admin_label, @group) - hide_class = '' +- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') - if can_admin_label - content_for(:header_content) do @@ -9,7 +10,7 @@ = link_to new_group_label_path(@group), class: "btn btn-new" do New label -- if @labels.exists? || @prioritized_labels.exists? +- if @labels.exists? #promote-label-modal %div{ class: container_class } .top-area.adjust @@ -19,24 +20,12 @@ Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. .labels-container.prepend-top-5 - - if can_admin_label - -# Only show it in the first page - - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') - .prioritized-labels{ class: ('hide' if hide) } - %h5.prepend-top-0 Prioritized Labels - %ul.content-list.manage-labels-list.js-prioritized-labels - #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } - = render 'shared/empty_states/priority_labels' - - if @prioritized_labels.present? - = render partial: 'shared/label', subject: @group, collection: @prioritized_labels, as: :label - - - if @labels.present? - .other-labels - - if can_admin_label - %h5{ class: ('hide' if hide) } Other Labels - %ul.content-list.manage-labels-list.js-other-labels - = render partial: 'shared/label', subject: @group, collection: @labels, as: :label - = paginate @labels, theme: 'gitlab' + .other-labels + - if can_admin_label + %h5{ class: ('hide' if hide) } Labels + %ul.content-list.manage-labels-list.js-other-labels + = render partial: 'shared/label', subject: @group, collection: @labels, as: :label + = paginate @labels, theme: 'gitlab' - else = render 'shared/empty_states/labels' -- cgit v1.2.1 From e1ef4ac620d691af9f1d74da7e821acaa7577102 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 17 May 2018 16:18:56 +0100 Subject: Remove group label badge on group labels page --- app/views/shared/_label.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 1f41cb26588..6272189e289 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -9,8 +9,9 @@ %li.label-list-item{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label, subject: subject %ul.label-actions-list - %li.inline - .label-badge.label-badge-gray= label.model_name.human.capitalize + - if controller.is_a?(Projects::LabelsController) + %li.inline + .label-badge.label-badge-gray= label.model_name.human.capitalize - if can?(current_user, :admin_label, label) - if @project.present? %li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), -- cgit v1.2.1 From 631bd9bf082c396059867d512fcfbdc80445c65e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 21 May 2018 11:39:46 +0200 Subject: Use persisted stages to load pipelines index table --- app/controllers/projects/pipelines_controller.rb | 5 ++- app/models/ci/pipeline.rb | 2 +- app/models/ci/stage.rb | 18 ++++++++ app/models/project.rb | 1 + app/serializers/pipeline_details_entity.rb | 7 ++- lib/gitlab/ci/pipeline/preloader.rb | 2 +- lib/gitlab/ci/status/stage/common.rb | 4 +- lib/gitlab/query_limiting/transaction.rb | 2 + .../projects/pipelines_controller_spec.rb | 52 +++++++++++++++------- 9 files changed, 72 insertions(+), 21 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 6b40fc2fe68..886431df7f7 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,5 +1,5 @@ class Projects::PipelinesController < Projects::ApplicationController - before_action :whitelist_query_limiting, only: [:create, :retry] + # before_action :whitelist_query_limiting, only: [:create, :retry] before_action :pipeline, except: [:index, :new, :create, :charts] before_action :commit, only: [:show, :builds, :failures] before_action :authorize_read_pipeline! @@ -15,6 +15,7 @@ class Projects::PipelinesController < Projects::ApplicationController @pipelines = PipelinesFinder .new(project, scope: @scope) .execute + .preload(:stages) .page(params[:page]) .per(30) @@ -23,7 +24,7 @@ class Projects::PipelinesController < Projects::ApplicationController @finished_count = limited_pipelines_count(project, 'finished') @pipelines_count = limited_pipelines_count(project) - Gitlab::Ci::Pipeline::Preloader.preload(@pipelines) + Gitlab::Ci::Pipeline::Preloader.preload(@project, @pipelines) respond_to do |format| format.html diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index c26f0b6dcdc..e7569e0d31e 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -13,7 +13,7 @@ module Ci belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' - has_many :stages + has_many :stages, inverse_of: :pipeline # -> { order(position: :asc) }, inverse_of: :pipeline has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 5a1eeb966aa..4fc8a00d9c3 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -79,5 +79,23 @@ module Ci end end end + + def groups + @groups ||= statuses.ordered.latest + .sort_by(&:sortable_name).group_by(&:group_name) + .map do |group_name, grouped_statuses| + Ci::Group.new(self, name: group_name, jobs: grouped_statuses) + end + end + + def has_warnings? + statuses.latest.failed_but_allowed.any? + end + + def detailed_status(current_user) + Gitlab::Ci::Status::Stage::Factory + .new(self, current_user) + .fabricate! + end end end diff --git a/app/models/project.rb b/app/models/project.rb index 35c873830a7..50b8a9a95ea 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -227,6 +227,7 @@ class Project < ActiveRecord::Base has_many :commit_statuses has_many :pipelines, class_name: 'Ci::Pipeline', inverse_of: :project + has_many :stages, class_name: 'Ci::Stage', inverse_of: :project # Ci::Build objects store data on the file system such as artifact files and # build traces. Currently there's no efficient way of removing this data in diff --git a/app/serializers/pipeline_details_entity.rb b/app/serializers/pipeline_details_entity.rb index 130968a44c1..2438a5bbbdf 100644 --- a/app/serializers/pipeline_details_entity.rb +++ b/app/serializers/pipeline_details_entity.rb @@ -1,6 +1,11 @@ class PipelineDetailsEntity < PipelineEntity expose :details do - expose :legacy_stages, as: :stages, using: StageEntity + ## + # TODO consider switching to persisted stages only in pipelines table + # (not necessairly in the show pipeline page because of #23257. + # Hide this behind two feature flags - enabled / disabled and only + # gitlab-ce / everywhere. + expose :stages, as: :stages, using: StageEntity expose :artifacts, using: BuildArtifactEntity expose :manual_actions, using: BuildActionEntity end diff --git a/lib/gitlab/ci/pipeline/preloader.rb b/lib/gitlab/ci/pipeline/preloader.rb index e7a2e5511cf..36a19719802 100644 --- a/lib/gitlab/ci/pipeline/preloader.rb +++ b/lib/gitlab/ci/pipeline/preloader.rb @@ -6,7 +6,7 @@ module Gitlab # Class for preloading data associated with pipelines such as commit # authors. module Preloader - def self.preload(pipelines) + def self.preload(project, pipelines) # This ensures that all the pipeline commits are eager loaded before we # start using them. pipelines.each(&:commit) diff --git a/lib/gitlab/ci/status/stage/common.rb b/lib/gitlab/ci/status/stage/common.rb index bc99d925347..f60a7662075 100644 --- a/lib/gitlab/ci/status/stage/common.rb +++ b/lib/gitlab/ci/status/stage/common.rb @@ -8,7 +8,9 @@ module Gitlab end def details_path - project_pipeline_path(subject.project, subject.pipeline, anchor: subject.name) + project_pipeline_path(subject.pipeline.project, + subject.pipeline, + anchor: subject.name) end def has_action? diff --git a/lib/gitlab/query_limiting/transaction.rb b/lib/gitlab/query_limiting/transaction.rb index 66d7d9275cf..cd712676263 100644 --- a/lib/gitlab/query_limiting/transaction.rb +++ b/lib/gitlab/query_limiting/transaction.rb @@ -47,6 +47,8 @@ module Gitlab # Sends a notification based on the number of executed SQL queries. def act_upon_results + puts "XXXX\n\n\n\n\n #{count} \n\n\nXXXX" + return unless threshold_exceeded? error = ThresholdExceededError.new(error_message) diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 9e7bc20a6d1..d7ff2d6175c 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -19,16 +19,21 @@ describe Projects::PipelinesController do before do %w(pending running created success).each_with_index do |status, index| sha = project.commit("HEAD~#{index}") - create(:ci_empty_pipeline, status: status, project: project, sha: sha) - end - end - subject do - get :index, namespace_id: project.namespace, project_id: project, format: :json + pipeline = create(:ci_empty_pipeline, status: status, + project: project, + sha: sha) + + + create_build(pipeline, 'test', 1, 'unit') + create_build(pipeline, 'test', 1, 'feature') + create_build(pipeline, 'review', 2, 'staging') + create_build(pipeline, 'deploy', 3, 'production') + end end - it 'returns JSON with serialized pipelines' do - subject + it 'returns JSON with serialized pipelines', :request_store do + queries = ActiveRecord::QueryRecorder.new { get_pipelines_index_json } expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('pipeline') @@ -39,22 +44,35 @@ describe Projects::PipelinesController do expect(json_response['count']['running']).to eq '1' expect(json_response['count']['pending']).to eq '1' expect(json_response['count']['finished']).to eq '1' + puts queries.log + expect(queries.count).to be < 25 end it 'does not include coverage data for the pipelines' do - subject + get_pipelines_index_json expect(json_response['pipelines'][0]).not_to include('coverage') end context 'when performing gitaly calls', :request_store do it 'limits the Gitaly requests' do - expect { subject }.to change { Gitlab::GitalyClient.get_request_count }.by(3) + expect { get_pipelines_index_json } + .to change { Gitlab::GitalyClient.get_request_count }.by(2) end end + + def get_pipelines_index_json + get :index, namespace_id: project.namespace, + project_id: project, + format: :json + end + + def create_build(pipeline, stage, stage_idx, name) + create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name) + end end - describe 'GET show JSON' do + describe 'GET show.json' do let(:pipeline) { create(:ci_pipeline_with_one_job, project: project) } it 'returns the pipeline' do @@ -67,6 +85,14 @@ describe Projects::PipelinesController do end context 'when the pipeline has multiple stages and groups', :request_store do + let(:project) { create(:project, :repository) } + + let(:pipeline) do + create(:ci_empty_pipeline, project: project, + user: user, + sha: project.commit.id) + end + before do create_build('build', 0, 'build') create_build('test', 1, 'rspec 0') @@ -74,11 +100,6 @@ describe Projects::PipelinesController do create_build('post deploy', 3, 'pages 0') end - let(:project) { create(:project, :repository) } - let(:pipeline) do - create(:ci_empty_pipeline, project: project, user: user, sha: project.commit.id) - end - it 'does not perform N + 1 queries' do control_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count @@ -90,6 +111,7 @@ describe Projects::PipelinesController do create_build('post deploy', 3, 'pages 2') new_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count + expect(new_count).to be_within(12).of(control_count) end end -- cgit v1.2.1 From 8e92e25b62ca108de775362e6d2981e54535f094 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 22 May 2018 15:21:45 +0900 Subject: Remvoe disable_ddl_transaction! and redandant RecordNotUnique exception rescue --- ...80424160449_add_pipeline_iid_to_ci_pipelines.rb | 2 -- lib/gitlab/ci/pipeline/chain/create.rb | 2 +- spec/lib/gitlab/ci/pipeline/chain/create_spec.rb | 27 +++++----------------- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb b/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb index d732116e9ce..e8f0c91d612 100644 --- a/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb +++ b/db/migrate/20180424160449_add_pipeline_iid_to_ci_pipelines.rb @@ -3,8 +3,6 @@ class AddPipelineIidToCiPipelines < ActiveRecord::Migration DOWNTIME = false - disable_ddl_transaction! - def up add_column :ci_pipelines, :iid, :integer end diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb index e62056766bd..918a0d151fc 100644 --- a/lib/gitlab/ci/pipeline/chain/create.rb +++ b/lib/gitlab/ci/pipeline/chain/create.rb @@ -23,7 +23,7 @@ module Gitlab end end end - rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique => e + rescue ActiveRecord::RecordInvalid => e error("Failed to persist the pipeline: #{e}") end diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb index 9c0bedfb6b7..de69a65aee4 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb @@ -37,17 +37,6 @@ describe Gitlab::Ci::Pipeline::Chain::Create do end context 'when pipeline has validation errors' do - shared_examples_for 'expectations' do - it 'breaks the chain' do - expect(step.break?).to be true - end - - it 'appends validation error' do - expect(pipeline.errors.to_a) - .to include /Failed to persist the pipeline/ - end - end - context 'when ref is nil' do let(:pipeline) do build(:ci_pipeline, project: project, ref: nil) @@ -57,18 +46,14 @@ describe Gitlab::Ci::Pipeline::Chain::Create do step.perform! end - it_behaves_like 'expectations' - end - - context 'when pipeline has a duplicate iid' do - before do - allow_any_instance_of(Ci::Pipeline).to receive(:ensure_project_iid!) { |p| p.send(:write_attribute, :iid, 1) } - create(:ci_pipeline, project: project) - - step.perform! + it 'breaks the chain' do + expect(step.break?).to be true end - it_behaves_like 'expectations' + it 'appends validation error' do + expect(pipeline.errors.to_a) + .to include /Failed to persist the pipeline/ + end end end end -- cgit v1.2.1 From 9520d2ff278f12cf2e01a755b1ea12213fd22edb Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 22 May 2018 12:29:52 +0200 Subject: Memoize project active runners to avoid N+1 queries --- app/models/project.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 50b8a9a95ea..5943a929364 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1418,7 +1418,9 @@ class Project < ActiveRecord::Base end def any_runners?(&block) - all_runners.active.any?(&block) + @active_runners ||= all_runners.active + + @active_runners.any?(&block) end def valid_runners_token?(token) -- cgit v1.2.1 From 6c63f96e0a26fa046fb0cc4664240e37db8b37d8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 22 May 2018 12:30:45 +0200 Subject: Preload number of warnings in every stage in a pipeline This makes it possible to avoid N+1 queries when loading pipelines table. --- app/controllers/projects/pipelines_controller.rb | 2 +- app/models/ci/pipeline.rb | 2 +- app/models/ci/stage.rb | 13 ++++++- lib/gitlab/ci/pipeline/preloader.rb | 7 +++- .../projects/pipelines_controller_spec.rb | 45 ++++++++++++---------- 5 files changed, 44 insertions(+), 25 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 886431df7f7..172642a1225 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -24,7 +24,7 @@ class Projects::PipelinesController < Projects::ApplicationController @finished_count = limited_pipelines_count(project, 'finished') @pipelines_count = limited_pipelines_count(project) - Gitlab::Ci::Pipeline::Preloader.preload(@project, @pipelines) + Gitlab::Ci::Pipeline::Preloader.preload(@pipelines) respond_to do |format| format.html diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index e7569e0d31e..9462afe9e71 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -411,7 +411,7 @@ module Ci def number_of_warnings BatchLoader.for(id).batch(default_value: 0) do |pipeline_ids, loader| - Build.where(commit_id: pipeline_ids) + ::Ci::Build.where(commit_id: pipeline_ids) .latest .failed_but_allowed .group(:commit_id) diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 4fc8a00d9c3..faedb9c29fd 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -89,7 +89,18 @@ module Ci end def has_warnings? - statuses.latest.failed_but_allowed.any? + number_of_warnings.positive? + end + + def number_of_warnings + BatchLoader.for(id).batch(default_value: 0) do |stage_ids, loader| + ::Ci::Build.where(stage_id: stage_ids) + .latest + .failed_but_allowed + .group(:stage_id) + .count + .each { |id, amount| loader.call(id, amount) } + end end def detailed_status(current_user) diff --git a/lib/gitlab/ci/pipeline/preloader.rb b/lib/gitlab/ci/pipeline/preloader.rb index 36a19719802..2f792062124 100644 --- a/lib/gitlab/ci/pipeline/preloader.rb +++ b/lib/gitlab/ci/pipeline/preloader.rb @@ -6,7 +6,7 @@ module Gitlab # Class for preloading data associated with pipelines such as commit # authors. module Preloader - def self.preload(project, pipelines) + def self.preload(pipelines) # This ensures that all the pipeline commits are eager loaded before we # start using them. pipelines.each(&:commit) @@ -20,6 +20,11 @@ module Gitlab # that Ci::Pipeline#has_warnings? doesn't execute any additional # queries. pipeline.number_of_warnings + + # This preloads the number of warnings for every stage, ensuring + # that Ci::Stage#has_warnings? doesn't execute any additional + # queries. + pipeline.stages.each { |stage| stage.number_of_warnings } end end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index d7ff2d6175c..d5a0d32054b 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -17,35 +17,27 @@ describe Projects::PipelinesController do describe 'GET index.json' do before do - %w(pending running created success).each_with_index do |status, index| - sha = project.commit("HEAD~#{index}") - - pipeline = create(:ci_empty_pipeline, status: status, - project: project, - sha: sha) - - - create_build(pipeline, 'test', 1, 'unit') - create_build(pipeline, 'test', 1, 'feature') - create_build(pipeline, 'review', 2, 'staging') - create_build(pipeline, 'deploy', 3, 'production') - end + %w(pending running running success canceled) + .each_with_index do |status, index| + create_pipeline(status, project.commit("HEAD~#{index}")) + end end it 'returns JSON with serialized pipelines', :request_store do - queries = ActiveRecord::QueryRecorder.new { get_pipelines_index_json } + queries = ActiveRecord::QueryRecorder.new do + get_pipelines_index_json + end expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('pipeline') expect(json_response).to include('pipelines') - expect(json_response['pipelines'].count).to eq 4 - expect(json_response['count']['all']).to eq '4' - expect(json_response['count']['running']).to eq '1' + expect(json_response['pipelines'].count).to eq 5 + expect(json_response['count']['all']).to eq '5' + expect(json_response['count']['running']).to eq '2' expect(json_response['count']['pending']).to eq '1' - expect(json_response['count']['finished']).to eq '1' - puts queries.log - expect(queries.count).to be < 25 + expect(json_response['count']['finished']).to eq '2' + expect(queries.count).to be < 32 end it 'does not include coverage data for the pipelines' do @@ -67,8 +59,19 @@ describe Projects::PipelinesController do format: :json end + def create_pipeline(status, sha) + pipeline = create(:ci_empty_pipeline, status: status, + project: project, + sha: sha) + + create_build(pipeline, 'build', 1, 'build') + create_build(pipeline, 'test', 2, 'test') + create_build(pipeline, 'deploy', 3, 'deploy') + end + def create_build(pipeline, stage, stage_idx, name) - create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name) + status = %w[created running pending success failed canceled].sample + create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name, status: status) end end -- cgit v1.2.1 From fb706d69abb2bb8f07f78b2a14206027f062cc30 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 22 May 2018 13:03:42 +0200 Subject: Order pipeline stages by a position in a relation --- app/models/ci/pipeline.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 9462afe9e71..774d5eb8125 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -13,7 +13,7 @@ module Ci belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' - has_many :stages, inverse_of: :pipeline # -> { order(position: :asc) }, inverse_of: :pipeline + has_many :stages, -> { order(position: :asc) }, inverse_of: :pipeline has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent -- cgit v1.2.1 From 76a7157c76c47fd4f835a27eeeda7b42012936be Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 22 May 2018 13:04:07 +0200 Subject: Abstract persisted/legacy stages in pipeline model --- app/controllers/projects/pipelines_controller.rb | 2 +- app/models/ci/pipeline.rb | 10 ++++++++++ app/serializers/pipeline_details_entity.rb | 7 +------ spec/controllers/projects/pipelines_controller_spec.rb | 11 ++++++----- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 172642a1225..7c18c0c4fcb 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,5 +1,5 @@ class Projects::PipelinesController < Projects::ApplicationController - # before_action :whitelist_query_limiting, only: [:create, :retry] + before_action :whitelist_query_limiting, only: [:create, :retry] # TODO? before_action :pipeline, except: [:index, :new, :create, :charts] before_action :commit, only: [:show, :builds, :failures] before_action :authorize_read_pipeline! diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 774d5eb8125..9dac56fcd57 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -249,6 +249,16 @@ module Ci stage unless stage.statuses_count.zero? end + ## + # TODO consider switching to persisted stages only in pipelines table + # (not necessairly in the show pipeline page because of #23257. + # Hide this behind two feature flags - enabled / disabled and only + # gitlab-ce / everywhere. + # + def stages + super + end + def legacy_stages # TODO, this needs refactoring, see gitlab-ce#26481. diff --git a/app/serializers/pipeline_details_entity.rb b/app/serializers/pipeline_details_entity.rb index 2438a5bbbdf..d58572a5f87 100644 --- a/app/serializers/pipeline_details_entity.rb +++ b/app/serializers/pipeline_details_entity.rb @@ -1,11 +1,6 @@ class PipelineDetailsEntity < PipelineEntity expose :details do - ## - # TODO consider switching to persisted stages only in pipelines table - # (not necessairly in the show pipeline page because of #23257. - # Hide this behind two feature flags - enabled / disabled and only - # gitlab-ce / everywhere. - expose :stages, as: :stages, using: StageEntity + expose :stages, using: StageEntity expose :artifacts, using: BuildArtifactEntity expose :manual_actions, using: BuildActionEntity end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index d5a0d32054b..c8080056f69 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -17,8 +17,7 @@ describe Projects::PipelinesController do describe 'GET index.json' do before do - %w(pending running running success canceled) - .each_with_index do |status, index| + %w(pending running success failed).each_with_index do |status, index| create_pipeline(status, project.commit("HEAD~#{index}")) end end @@ -32,11 +31,13 @@ describe Projects::PipelinesController do expect(response).to match_response_schema('pipeline') expect(json_response).to include('pipelines') - expect(json_response['pipelines'].count).to eq 5 - expect(json_response['count']['all']).to eq '5' - expect(json_response['count']['running']).to eq '2' + expect(json_response['pipelines'].count).to eq 4 + expect(json_response['count']['all']).to eq '4' + expect(json_response['count']['running']).to eq '1' expect(json_response['count']['pending']).to eq '1' expect(json_response['count']['finished']).to eq '2' + puts queries.log + puts "Queries count: #{queries.count}" expect(queries.count).to be < 32 end -- cgit v1.2.1 From ea35fd05bb175cf921e94d8b1c5e6bc5f3e217c9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 22 May 2018 13:55:05 +0200 Subject: Refactor pipeline preloader to split reponsibilities better --- app/controllers/projects/pipelines_controller.rb | 2 +- lib/gitlab/ci/pipeline/preloader.rb | 40 ++++++++++++++++------ .../projects/pipelines_controller_spec.rb | 2 -- spec/lib/gitlab/ci/pipeline/preloader_spec.rb | 22 ++++++------ 4 files changed, 43 insertions(+), 23 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 7c18c0c4fcb..c8a4709fe98 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -24,7 +24,7 @@ class Projects::PipelinesController < Projects::ApplicationController @finished_count = limited_pipelines_count(project, 'finished') @pipelines_count = limited_pipelines_count(project) - Gitlab::Ci::Pipeline::Preloader.preload(@pipelines) + Gitlab::Ci::Pipeline::Preloader.new(@pipelines).preload! respond_to do |format| format.html diff --git a/lib/gitlab/ci/pipeline/preloader.rb b/lib/gitlab/ci/pipeline/preloader.rb index 2f792062124..668c1b7189c 100644 --- a/lib/gitlab/ci/pipeline/preloader.rb +++ b/lib/gitlab/ci/pipeline/preloader.rb @@ -5,26 +5,46 @@ module Gitlab module Pipeline # Class for preloading data associated with pipelines such as commit # authors. - module Preloader - def self.preload(pipelines) - # This ensures that all the pipeline commits are eager loaded before we - # start using them. - pipelines.each(&:commit) + class Preloader + def initialize(pipelines) + @pipelines = pipelines + end + + def preload! + @pipelines.each do |pipeline| + Pipeline::Preloader::Instance.new(pipeline) + .preload_commits + .preload_pipeline_warnings + .preload_stages_warnings + end + end - pipelines.each do |pipeline| - # This preloads the author of every commit. We're using "lazy_author" + class Instance + def initialize(pipeline) + @pipeline = pipeline + end + + def preload_commits + # This ensures that all the pipeline commits are eager loaded before we + # start using them. + # + # This also preloads the author of every commit. We're using "lazy_author" # here since "author" immediately loads the data on the first call. - pipeline.commit.try(:lazy_author) + tap { @pipeline.commit.try(:lazy_author) } + end + def preload_pipeline_warnings # This preloads the number of warnings for every pipeline, ensuring # that Ci::Pipeline#has_warnings? doesn't execute any additional # queries. - pipeline.number_of_warnings + tap { @pipeline.number_of_warnings } + end + def preload_stages_warnings # This preloads the number of warnings for every stage, ensuring # that Ci::Stage#has_warnings? doesn't execute any additional # queries. - pipeline.stages.each { |stage| stage.number_of_warnings } + tap { @pipeline.stages.each { |stage| stage.number_of_warnings } } end end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index c8080056f69..536ef9b2d79 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -36,8 +36,6 @@ describe Projects::PipelinesController do expect(json_response['count']['running']).to eq '1' expect(json_response['count']['pending']).to eq '1' expect(json_response['count']['finished']).to eq '2' - puts queries.log - puts "Queries count: #{queries.count}" expect(queries.count).to be < 32 end diff --git a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb index 477c7477df0..c8cfe2c696d 100644 --- a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb @@ -1,20 +1,22 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' describe Gitlab::Ci::Pipeline::Preloader do - describe '.preload' do - it 'preloads the author of every pipeline commit' do - commit = double(:commit) - pipeline = double(:pipeline, commit: commit) + let(:stage) { double(:stage) } + let(:commit) { double(:commit) } - expect(commit) - .to receive(:lazy_author) + let(:pipeline) do + double(:pipeline, commit: commit, stages: [stage]) + end - expect(pipeline) - .to receive(:number_of_warnings) + describe '#preload!' do + it 'preloads commit authors and number of warnings' do + expect(commit).to receive(:lazy_author) + expect(pipeline).to receive(:number_of_warnings) + expect(stage).to receive(:number_of_warnings) - described_class.preload([pipeline]) + described_class.new([pipeline]).preload! end end end -- cgit v1.2.1 From e4eb330af7577940d2440544004d1335fe221839 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 22 May 2018 14:00:42 +0200 Subject: Add pipeline stages factory method import/export support --- spec/lib/gitlab/import_export/all_models.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 8b46b04b8b5..002e5e47208 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -256,6 +256,7 @@ project: - import_data - commit_statuses - pipelines +- stages - builds - runner_projects - runners -- cgit v1.2.1 From be3a0377dad4da54f5dbc51b88e39394c1b920be Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 22 May 2018 14:03:02 +0200 Subject: Fix memoization-related rubocop offense in project --- app/models/project.rb | 10 +++++++--- lib/gitlab/query_limiting/transaction.rb | 2 -- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 5943a929364..e0f6c856f00 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1417,10 +1417,14 @@ class Project < ActiveRecord::Base Ci::Runner.from("(#{union.to_sql}) ci_runners") end - def any_runners?(&block) - @active_runners ||= all_runners.active + def active_runners + strong_memoize(:active_runners) do + all_runners.active + end + end - @active_runners.any?(&block) + def any_runners?(&block) + active_runners.any?(&block) end def valid_runners_token?(token) diff --git a/lib/gitlab/query_limiting/transaction.rb b/lib/gitlab/query_limiting/transaction.rb index cd712676263..66d7d9275cf 100644 --- a/lib/gitlab/query_limiting/transaction.rb +++ b/lib/gitlab/query_limiting/transaction.rb @@ -47,8 +47,6 @@ module Gitlab # Sends a notification based on the number of executed SQL queries. def act_upon_results - puts "XXXX\n\n\n\n\n #{count} \n\n\nXXXX" - return unless threshold_exceeded? error = ThresholdExceededError.new(error_message) -- cgit v1.2.1 From 0b3cca568ddaa43b4531392a8ccc391b1688bfdc Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 22 May 2018 14:38:25 +0200 Subject: Fix rubocop offense in pipeline controller specs :cop: --- app/controllers/projects/pipelines_controller.rb | 2 +- spec/controllers/projects/pipelines_controller_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index c8a4709fe98..b4a8cdadd9b 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,5 +1,5 @@ class Projects::PipelinesController < Projects::ApplicationController - before_action :whitelist_query_limiting, only: [:create, :retry] # TODO? + before_action :whitelist_query_limiting, only: [:create, :retry] before_action :pipeline, except: [:index, :new, :create, :charts] before_action :commit, only: [:show, :builds, :failures] before_action :authorize_read_pipeline! diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 536ef9b2d79..2e0f9b00023 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -18,8 +18,8 @@ describe Projects::PipelinesController do describe 'GET index.json' do before do %w(pending running success failed).each_with_index do |status, index| - create_pipeline(status, project.commit("HEAD~#{index}")) - end + create_pipeline(status, project.commit("HEAD~#{index}")) + end end it 'returns JSON with serialized pipelines', :request_store do -- cgit v1.2.1 From f89f232d19a6c28a3504e964fbfd2b5eb344aa85 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 23 May 2018 10:22:28 +0200 Subject: Simplify pipelines preloader implementation --- app/controllers/projects/pipelines_controller.rb | 2 +- lib/gitlab/ci/pipeline/preloader.rb | 63 +++++++++++------------- spec/lib/gitlab/ci/pipeline/preloader_spec.rb | 4 +- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index b4a8cdadd9b..2b758b960c2 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -24,7 +24,7 @@ class Projects::PipelinesController < Projects::ApplicationController @finished_count = limited_pipelines_count(project, 'finished') @pipelines_count = limited_pipelines_count(project) - Gitlab::Ci::Pipeline::Preloader.new(@pipelines).preload! + Gitlab::Ci::Pipeline::Preloader.preload!(@pipelines) respond_to do |format| format.html diff --git a/lib/gitlab/ci/pipeline/preloader.rb b/lib/gitlab/ci/pipeline/preloader.rb index 668c1b7189c..6db6554a606 100644 --- a/lib/gitlab/ci/pipeline/preloader.rb +++ b/lib/gitlab/ci/pipeline/preloader.rb @@ -6,46 +6,41 @@ module Gitlab # Class for preloading data associated with pipelines such as commit # authors. class Preloader - def initialize(pipelines) - @pipelines = pipelines - end - - def preload! - @pipelines.each do |pipeline| - Pipeline::Preloader::Instance.new(pipeline) - .preload_commits - .preload_pipeline_warnings - .preload_stages_warnings + def self.preload!(pipelines) + pipelines.each do |pipeline| + self.new(pipeline).tap do |preloader| + preloader.preload_commits + preloader.preload_pipeline_warnings + preloader.preload_stages_warnings + end end end - class Instance - def initialize(pipeline) - @pipeline = pipeline - end + def initialize(pipeline) + @pipeline = pipeline + end - def preload_commits - # This ensures that all the pipeline commits are eager loaded before we - # start using them. - # - # This also preloads the author of every commit. We're using "lazy_author" - # here since "author" immediately loads the data on the first call. - tap { @pipeline.commit.try(:lazy_author) } - end + def preload_commits + # This ensures that all the pipeline commits are eager loaded before we + # start using them. + # + # This also preloads the author of every commit. We're using "lazy_author" + # here since "author" immediately loads the data on the first call. + @pipeline.commit.try(:lazy_author) + end - def preload_pipeline_warnings - # This preloads the number of warnings for every pipeline, ensuring - # that Ci::Pipeline#has_warnings? doesn't execute any additional - # queries. - tap { @pipeline.number_of_warnings } - end + def preload_pipeline_warnings + # This preloads the number of warnings for every pipeline, ensuring + # that Ci::Pipeline#has_warnings? doesn't execute any additional + # queries. + @pipeline.number_of_warnings + end - def preload_stages_warnings - # This preloads the number of warnings for every stage, ensuring - # that Ci::Stage#has_warnings? doesn't execute any additional - # queries. - tap { @pipeline.stages.each { |stage| stage.number_of_warnings } } - end + def preload_stages_warnings + # This preloads the number of warnings for every stage, ensuring + # that Ci::Stage#has_warnings? doesn't execute any additional + # queries. + @pipeline.stages.each { |stage| stage.number_of_warnings } end end end diff --git a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb index c8cfe2c696d..16d3631ec7c 100644 --- a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb @@ -10,13 +10,13 @@ describe Gitlab::Ci::Pipeline::Preloader do double(:pipeline, commit: commit, stages: [stage]) end - describe '#preload!' do + describe '.preload!' do it 'preloads commit authors and number of warnings' do expect(commit).to receive(:lazy_author) expect(pipeline).to receive(:number_of_warnings) expect(stage).to receive(:number_of_warnings) - described_class.new([pipeline]).preload! + described_class.preload!([pipeline]) end end end -- cgit v1.2.1 From 8cca6c83a99100fcf3a0a3a56f10eebe3c9b7716 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 23 May 2018 10:37:01 +0200 Subject: DRY creating groups of common builds in a stage --- app/models/ci/group.rb | 8 +++++++ app/models/ci/legacy_stage.rb | 6 +---- app/models/ci/stage.rb | 6 +---- spec/models/ci/group_spec.rb | 51 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/app/models/ci/group.rb b/app/models/ci/group.rb index 87898b086c6..9c1046e8715 100644 --- a/app/models/ci/group.rb +++ b/app/models/ci/group.rb @@ -31,6 +31,14 @@ module Ci end end + def self.fabricate(stage) + stage.statuses.ordered.latest + .sort_by(&:sortable_name).group_by(&:group_name) + .map do |group_name, grouped_statuses| + self.new(stage, name: group_name, jobs: grouped_statuses) + end + end + private def commit_statuses diff --git a/app/models/ci/legacy_stage.rb b/app/models/ci/legacy_stage.rb index 9b536af672b..ce691875e42 100644 --- a/app/models/ci/legacy_stage.rb +++ b/app/models/ci/legacy_stage.rb @@ -16,11 +16,7 @@ module Ci end def groups - @groups ||= statuses.ordered.latest - .sort_by(&:sortable_name).group_by(&:group_name) - .map do |group_name, grouped_statuses| - Ci::Group.new(self, name: group_name, jobs: grouped_statuses) - end + @groups ||= Ci::Group.fabricate(self) end def to_param diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index faedb9c29fd..0044d1af106 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -81,11 +81,7 @@ module Ci end def groups - @groups ||= statuses.ordered.latest - .sort_by(&:sortable_name).group_by(&:group_name) - .map do |group_name, grouped_statuses| - Ci::Group.new(self, name: group_name, jobs: grouped_statuses) - end + @groups ||= Ci::Group.fabricate(self) end def has_warnings? diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb index 51123e73fe6..9052dd59369 100644 --- a/spec/models/ci/group_spec.rb +++ b/spec/models/ci/group_spec.rb @@ -41,4 +41,55 @@ describe Ci::Group do end end end + + describe '.fabricate' do + let(:pipeline) { create(:ci_empty_pipeline) } + let(:stage) { create(:ci_stage_entity, pipeline: pipeline) } + + before do + create_build(:ci_build, name: 'rspec 0 2') + create_build(:ci_build, name: 'rspec 0 1') + create_build(:ci_build, name: 'spinach 0 1') + create_build(:commit_status, name: 'aaaaa') + end + + it 'returns an array of three groups' do + expect(stage.groups).to be_a Array + expect(stage.groups).to all(be_a Ci::Group) + expect(stage.groups.size).to eq 3 + end + + it 'returns groups with correctly ordered statuses' do + expect(stage.groups.first.jobs.map(&:name)) + .to eq ['aaaaa'] + expect(stage.groups.second.jobs.map(&:name)) + .to eq ['rspec 0 1', 'rspec 0 2'] + expect(stage.groups.third.jobs.map(&:name)) + .to eq ['spinach 0 1'] + end + + it 'returns groups with correct names' do + expect(stage.groups.map(&:name)) + .to eq %w[aaaaa rspec spinach] + end + + context 'when a name is nil on legacy pipelines' do + before do + pipeline.builds.first.update_attribute(:name, nil) + end + + it 'returns an array of three groups' do + expect(stage.groups.map(&:name)) + .to eq ['', 'aaaaa', 'rspec', 'spinach'] + end + end + + def create_build(type, status: 'success', **opts) + create(type, pipeline: pipeline, + stage: stage.name, + status: status, + stage_id: stage.id, + **opts) + end + end end -- cgit v1.2.1 From dab3ae39a2093fa924daf9c1902a2784002cca63 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 23 May 2018 13:29:21 +0200 Subject: Do not paginate pipelines active relation twice --- app/controllers/projects/pipelines_controller.rb | 5 +---- app/serializers/pipeline_serializer.rb | 13 ++++++------- spec/controllers/projects/pipelines_controller_spec.rb | 2 +- spec/lib/gitlab/ci/pipeline/preloader_spec.rb | 10 ++++++++++ 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 2b758b960c2..768595ceeb4 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -15,7 +15,6 @@ class Projects::PipelinesController < Projects::ApplicationController @pipelines = PipelinesFinder .new(project, scope: @scope) .execute - .preload(:stages) .page(params[:page]) .per(30) @@ -24,8 +23,6 @@ class Projects::PipelinesController < Projects::ApplicationController @finished_count = limited_pipelines_count(project, 'finished') @pipelines_count = limited_pipelines_count(project) - Gitlab::Ci::Pipeline::Preloader.preload!(@pipelines) - respond_to do |format| format.html format.json do @@ -35,7 +32,7 @@ class Projects::PipelinesController < Projects::ApplicationController pipelines: PipelineSerializer .new(project: @project, current_user: @current_user) .with_pagination(request, response) - .represent(@pipelines, disable_coverage: true), + .represent(@pipelines, disable_coverage: true, preload: true), count: { all: @pipelines_count, running: @running_count, diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index 7181f8a6b04..68325cbffa8 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -1,14 +1,12 @@ class PipelineSerializer < BaseSerializer include WithPagination - - InvalidResourceError = Class.new(StandardError) - entity PipelineDetailsEntity def represent(resource, opts = {}) if resource.is_a?(ActiveRecord::Relation) resource = resource.preload([ + :stages, :retryable_builds, :cancelable_statuses, :trigger_requests, @@ -19,11 +17,12 @@ class PipelineSerializer < BaseSerializer ]) end - if paginated? - super(@paginator.paginate(resource), opts) - else - super(resource, opts) + if opts.delete(:preload) + resource = @paginator.paginate(resource) if paginated? + resource = Gitlab::Ci::Pipeline::Preloader.preload!(resource) end + + super(resource, opts) end def represent_status(resource) diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 2e0f9b00023..d7253c90ee5 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -36,7 +36,7 @@ describe Projects::PipelinesController do expect(json_response['count']['running']).to eq '1' expect(json_response['count']['pending']).to eq '1' expect(json_response['count']['finished']).to eq '2' - expect(queries.count).to be < 32 + expect(queries.count).to be_within(2).of(29) end it 'does not include coverage data for the pipelines' do diff --git a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb index 16d3631ec7c..7821d28ab06 100644 --- a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb @@ -18,5 +18,15 @@ describe Gitlab::Ci::Pipeline::Preloader do described_class.preload!([pipeline]) end + + it 'returns original collection' do + allow(commit).to receive(:lazy_author) + allow(pipeline).to receive(:number_of_warnings) + allow(stage).to receive(:number_of_warnings) + + pipelines = [pipeline, pipeline] + + expect(described_class.preload!(pipelines)).to eq pipelines + end end end -- cgit v1.2.1 From 92de5f778faa989fb25f9a5c93c5e23618f2d87b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 23 May 2018 13:30:34 +0200 Subject: Fix Rubocop offense in grouped statuses class specs --- spec/models/ci/group_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb index 9052dd59369..838fa63cb1f 100644 --- a/spec/models/ci/group_spec.rb +++ b/spec/models/ci/group_spec.rb @@ -55,7 +55,7 @@ describe Ci::Group do it 'returns an array of three groups' do expect(stage.groups).to be_a Array - expect(stage.groups).to all(be_a Ci::Group) + expect(stage.groups).to all(be_a described_class) expect(stage.groups.size).to eq 3 end -- cgit v1.2.1 From bc9a0e10b50430e0b253a15d1628b6776d0bd9fe Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 23 May 2018 14:22:22 +0200 Subject: Reduce amount of expected pipeline serialization queries in specs --- spec/controllers/projects/pipelines_controller_spec.rb | 2 +- spec/serializers/pipeline_serializer_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index d7253c90ee5..62eece8de4a 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -36,7 +36,7 @@ describe Projects::PipelinesController do expect(json_response['count']['running']).to eq '1' expect(json_response['count']['pending']).to eq '1' expect(json_response['count']['finished']).to eq '2' - expect(queries.count).to be_within(2).of(29) + expect(queries.count).to be < 30 end it 'does not include coverage data for the pipelines' do diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index b741308e2c5..5108eb4deec 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -120,7 +120,7 @@ describe PipelineSerializer do it 'verifies number of queries', :request_store do recorded = ActiveRecord::QueryRecorder.new { subject } - expect(recorded.count).to be_within(1).of(44) + expect(recorded.count).to be_within(1).of(38) expect(recorded.cached_count).to eq(0) end end @@ -139,7 +139,7 @@ describe PipelineSerializer do # pipeline. With the same ref this check is cached but if refs are # different then there is an extra query per ref # https://gitlab.com/gitlab-org/gitlab-ce/issues/46368 - expect(recorded.count).to be_within(1).of(51) + expect(recorded.count).to be_within(1).of(45) expect(recorded.cached_count).to eq(0) end end -- cgit v1.2.1 From aba153a46d99977b6d05b0d6fb93a796cf551ae3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 24 May 2018 02:49:14 +0100 Subject: update gitlab-svgs --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 45bea12fd9b..af1da9cffa8 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" }, "dependencies": { - "@gitlab-org/gitlab-svgs": "^1.18.0", + "@gitlab-org/gitlab-svgs": "1.23.0", "autosize": "^4.0.0", "axios": "^0.17.1", "babel-core": "^6.26.0", diff --git a/yarn.lock b/yarn.lock index 55a86a9a577..7cf8f11d2c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -54,9 +54,9 @@ lodash "^4.2.0" to-fast-properties "^2.0.0" -"@gitlab-org/gitlab-svgs@^1.18.0": - version "1.18.0" - resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.18.0.tgz#7829f0e6de0647dace54c1fcd597ee3424afb233" +"@gitlab-org/gitlab-svgs@1.23.0": + version "1.23.0" + resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.23.0.tgz#42047aeedcc06bc12d417ed1efadad1749af9670" "@types/jquery@^2.0.40": version "2.0.48" -- cgit v1.2.1 From d400c94e601df96b0a58a7e1098ee04b3e7459f5 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 24 May 2018 13:29:00 +0100 Subject: Fix accuracy of 'prioritized label' badge, when given different contexts --- app/views/groups/labels/index.html.haml | 2 +- app/views/projects/labels/index.html.haml | 2 +- app/views/shared/_label.html.haml | 4 +++- app/views/shared/_label_row.html.haml | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 44b64a31972..a72ea0681c8 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -24,7 +24,7 @@ - if can_admin_label %h5{ class: ('hide' if hide) } Labels %ul.content-list.manage-labels-list.js-other-labels - = render partial: 'shared/label', subject: @group, collection: @labels, as: :label + = render partial: 'shared/label', subject: @group, collection: @labels, as: :label, locals: { use_label_priority: true } = paginate @labels, theme: 'gitlab' - else = render 'shared/empty_states/labels' diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 725fd8107e8..4d423827832 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -28,7 +28,7 @@ #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } = render 'shared/empty_states/priority_labels' - if @prioritized_labels.present? - = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label + = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label, locals: { force_priority: true } - if @labels.present? .other-labels diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 6272189e289..6dfa1ffe9b5 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -1,13 +1,15 @@ - label_css_id = dom_id(label) - status = label_subscription_status(label, @project).inquiry if current_user - subject = local_assigns[:subject] +- use_label_priority = local_assigns.fetch(:use_label_priority, false) +- force_priority = local_assigns.fetch(:force_priority, use_label_priority ? label.priority? : false) - toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) - tooltip_title = "#{status.unsubscribed? ? 'Subscribe' : 'Unsubscribe'} at #{label.is_a?(ProjectLabel) ? 'project' : 'group'} level" %li.label-list-item{ id: label_css_id, data: { id: label.id } } - = render "shared/label_row", label: label, subject: subject + = render "shared/label_row", label: label, subject: subject, force_priority: force_priority %ul.label-actions-list - if controller.is_a?(Projects::LabelsController) %li.inline diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 578fe6bcd8a..42d9a97d3a1 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -1,4 +1,5 @@ - subject = local_assigns[:subject] +- force_priority = local_assigns.fetch(:force_priority, false) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) @@ -17,6 +18,6 @@ · %li.label-link-item.inline = link_to_label(label, subject: subject, type: :merge_request) { _('Merge requests') } - - if label.priority? + - if force_priority %li.label-link-item.js-priority-badge.inline.prepend-left-10 .label-badge.label-badge-blue= _('Prioritized label') \ No newline at end of file -- cgit v1.2.1 From f0d7445b88f6598db85198296c076bf59508188a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 24 May 2018 16:55:24 +0200 Subject: Reduce pipeline serialization queries when preloaded --- app/serializers/pipeline_serializer.rb | 8 +++++--- spec/serializers/pipeline_serializer_spec.rb | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index 68325cbffa8..d4b85f5aeb4 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -3,8 +3,11 @@ class PipelineSerializer < BaseSerializer entity PipelineDetailsEntity def represent(resource, opts = {}) - if resource.is_a?(ActiveRecord::Relation) + if paginated? && !resource.respond_to?(:page) + raise Gitlab::Serializer::Pagination::InvalidResourceError + end + if resource.is_a?(ActiveRecord::Relation) resource = resource.preload([ :stages, :retryable_builds, @@ -18,7 +21,6 @@ class PipelineSerializer < BaseSerializer end if opts.delete(:preload) - resource = @paginator.paginate(resource) if paginated? resource = Gitlab::Ci::Pipeline::Preloader.preload!(resource) end @@ -35,7 +37,7 @@ class PipelineSerializer < BaseSerializer def represent_stages(resource) return {} unless resource.present? - data = represent(resource, { only: [{ details: [:stages] }] }) + data = represent(resource, { only: [{ details: [:stages] }], preload: true }) data.dig(:details, :stages) || [] end end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 5108eb4deec..9319d29279a 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -99,7 +99,8 @@ describe PipelineSerializer do end end - context 'number of queries' do + describe 'number of queries when preloaded' do + subject { serializer.represent(resource, preload: true) } let(:resource) { Ci::Pipeline.all } before do @@ -120,7 +121,7 @@ describe PipelineSerializer do it 'verifies number of queries', :request_store do recorded = ActiveRecord::QueryRecorder.new { subject } - expect(recorded.count).to be_within(1).of(38) + expect(recorded.count).to be_within(1).of(31) expect(recorded.cached_count).to eq(0) end end @@ -139,7 +140,7 @@ describe PipelineSerializer do # pipeline. With the same ref this check is cached but if refs are # different then there is an extra query per ref # https://gitlab.com/gitlab-org/gitlab-ce/issues/46368 - expect(recorded.count).to be_within(1).of(45) + expect(recorded.count).to be_within(1).of(38) expect(recorded.cached_count).to eq(0) end end -- cgit v1.2.1 From 0e1b3dc41b61c8d9f6a1432c4f823253cbeb5c4d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 May 2018 11:31:18 +0200 Subject: Fix pipelines serializer with preloading pagination --- app/serializers/pipeline_serializer.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index d4b85f5aeb4..17a022539bb 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -3,10 +3,6 @@ class PipelineSerializer < BaseSerializer entity PipelineDetailsEntity def represent(resource, opts = {}) - if paginated? && !resource.respond_to?(:page) - raise Gitlab::Serializer::Pagination::InvalidResourceError - end - if resource.is_a?(ActiveRecord::Relation) resource = resource.preload([ :stages, @@ -20,6 +16,10 @@ class PipelineSerializer < BaseSerializer ]) end + if paginated? + resource = paginator.paginate(resource) + end + if opts.delete(:preload) resource = Gitlab::Ci::Pipeline::Preloader.preload!(resource) end -- cgit v1.2.1 From 887818d3cb58406555bc038ed36b75b7a4ce2631 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 May 2018 12:43:27 +0200 Subject: Do not update stage status when it is just created --- app/models/ci/stage.rb | 3 +-- spec/models/ci/stage_spec.rb | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 0044d1af106..abf73c00539 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -74,8 +74,7 @@ module Ci when 'failed' then drop when 'canceled' then cancel when 'manual' then block - when 'skipped' then skip - else skip + when 'skipped', nil then skip end end end diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index a00db1d2bfc..d82bf20b032 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -65,7 +65,31 @@ describe Ci::Stage, :models do end end - context 'when stage is skipped' do + context 'when stage has only created builds' do + let(:stage) { create(:ci_stage_entity, status: :created) } + + before do + create(:ci_build, :created, stage_id: stage.id) + end + + it 'updates status to skipped' do + expect(stage.reload.status).to eq 'created' + end + end + + context 'when stage is skipped because of skipped builds' do + before do + create(:ci_build, :skipped, stage_id: stage.id) + end + + it 'updates status to skipped' do + expect { stage.update_status } + .to change { stage.reload.status } + .to 'skipped' + end + end + + context 'when stage is skipped because is empty' do it 'updates status to skipped' do expect { stage.update_status } .to change { stage.reload.status } -- cgit v1.2.1 From c0827455ab20a156b86a20556c79139e4eb114e5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 May 2018 12:47:56 +0200 Subject: Add specs for stage detailed status and warnings --- spec/models/ci/stage_spec.rb | 104 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index d82bf20b032..b482e7de88b 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -112,7 +112,71 @@ describe Ci::Stage, :models do end end - describe '#index' do + + describe '#detailed_status' do + using RSpec::Parameterized::TableSyntax + + let(:user) { create(:user) } + let(:stage) { create(:ci_stage_entity, status: :created) } + subject { stage.detailed_status(user) } + + where(:statuses, :label) do + %w[created] | :created + %w[success] | :passed + %w[pending] | :pending + %w[skipped] | :skipped + %w[canceled] | :canceled + %w[success failed] | :failed + %w[running pending] | :running + end + + with_them do + before do + statuses.each do |status| + create(:commit_status, project: stage.project, + pipeline: stage.pipeline, + stage_id: stage.id, + status: status) + + stage.update_status + end + end + + it 'has a correct label' do + expect(subject.label).to eq label.to_s + end + end + + context 'when stage has warnings' do + before do + create(:ci_build, project: stage.project, + pipeline: stage.pipeline, + stage_id: stage.id, + status: :failed, + allow_failure: true) + + stage.update_status + end + + it 'is passed with warnings' do + expect(subject.label).to eq 'passed with warnings' + end + end + end + + describe '#groups' do + before do + create(:ci_build, stage_id: stage.id, name: 'rspec 0 1') + create(:ci_build, stage_id: stage.id, name: 'rspec 0 2') + end + + it 'groups stage builds by name' do + expect(stage.groups).to be_one + expect(stage.groups.first.name).to eq 'rspec' + end + end + + describe '#position' do context 'when stage has been imported and does not have position index set' do before do stage.update_column(:position, nil) @@ -143,4 +207,42 @@ describe Ci::Stage, :models do end end end + + context 'when stage has warnings' do + before do + create(:ci_build, :failed, :allowed_to_fail, stage_id: stage.id) + end + + describe '#has_warnings?' do + it 'returns true' do + expect(stage).to have_warnings + end + end + + describe '#number_of_warnings' do + it 'returns a lazy stage warnings counter' do + lazy_queries = ActiveRecord::QueryRecorder.new do + stage.number_of_warnings + end + + synced_queries = ActiveRecord::QueryRecorder.new do + stage.number_of_warnings.to_i + end + + expect(lazy_queries.count).to eq 0 + expect(synced_queries.count).to eq 1 + + expect(stage.number_of_warnings.inspect).to include 'BatchLoader' + expect(stage.number_of_warnings).to eq 1 + end + end + end + + context 'when stage does not have warnings' do + describe '#has_warnings?' do + it 'returns false' do + expect(stage).not_to have_warnings + end + end + end end -- cgit v1.2.1 From c896ae7be889dd3935795b1079ab7a11a12f52e0 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 25 May 2018 12:38:45 +0100 Subject: BE review --- app/controllers/groups/labels_controller.rb | 7 +++---- app/views/shared/_label.html.haml | 2 +- spec/features/projects/labels/update_prioritization_spec.rb | 2 ++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 0570edb51c2..863f50e8e66 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -2,17 +2,16 @@ class Groups::LabelsController < Groups::ApplicationController include ToggleSubscriptionAction before_action :label, only: [:edit, :update, :destroy] - before_action :find_labels, only: [:index] + before_action :available_labels, only: [:index] before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy] before_action :save_previous_label_path, only: [:edit] respond_to :html def index - @labels = @group.labels.page(params[:page]) - respond_to do |format| format.html do + @labels = @group.labels.page(params[:page]) end format.json do render json: LabelSerializer.new.represent_appearance(@available_labels) @@ -107,7 +106,7 @@ class Groups::LabelsController < Groups::ApplicationController session[:previous_labels_path] = URI(request.referer || '').path end - def find_labels + def available_labels @available_labels ||= LabelsFinder.new( current_user, diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 6dfa1ffe9b5..15e56b2cad9 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -2,7 +2,7 @@ - status = label_subscription_status(label, @project).inquiry if current_user - subject = local_assigns[:subject] - use_label_priority = local_assigns.fetch(:use_label_priority, false) -- force_priority = local_assigns.fetch(:force_priority, use_label_priority ? label.priority? : false) +- force_priority = local_assigns.fetch(:force_priority, use_label_priority ? label.priority.present? : false) - toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index ae8b1364ec7..9500e3f3311 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -23,8 +23,10 @@ feature 'Prioritize labels' do expect(page).to have_content('Star labels to start sorting by priority') page.within('.other-labels') do + screenshot_and_open_image all('.js-toggle-priority')[1].click wait_for_requests + screenshot_and_open_image expect(page).not_to have_content('feature') end -- cgit v1.2.1 From 22c1e96f4ee4cf7388fe8dc5aa0c3084ceb08d19 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 25 May 2018 12:38:56 +0100 Subject: last newline labels.scss --- app/assets/stylesheets/pages/labels.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 6348c5a878d..97985dc69c2 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -320,4 +320,4 @@ .priority-labels-empty-state .svg-content img { max-width: 114px; -} \ No newline at end of file +} -- cgit v1.2.1 From 1af2274b4126b3e910604ef321b58d327ea21e63 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 May 2018 13:43:11 +0200 Subject: Restore lazy loading of pipeline commits in preloader --- lib/gitlab/ci/pipeline/preloader.rb | 14 +++++++++----- spec/lib/gitlab/ci/pipeline/preloader_spec.rb | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/ci/pipeline/preloader.rb b/lib/gitlab/ci/pipeline/preloader.rb index 6db6554a606..db0a1ea4dab 100644 --- a/lib/gitlab/ci/pipeline/preloader.rb +++ b/lib/gitlab/ci/pipeline/preloader.rb @@ -7,9 +7,16 @@ module Gitlab # authors. class Preloader def self.preload!(pipelines) + ## + # This preloads all commits at once, because `Ci::Pipeline#commit` is + # using a lazy batch loading, what results in only one batched Gitaly + # call. + # + pipelines.each(&:commit) + pipelines.each do |pipeline| self.new(pipeline).tap do |preloader| - preloader.preload_commits + preloader.preload_commit_authors preloader.preload_pipeline_warnings preloader.preload_stages_warnings end @@ -20,10 +27,7 @@ module Gitlab @pipeline = pipeline end - def preload_commits - # This ensures that all the pipeline commits are eager loaded before we - # start using them. - # + def preload_commit_authors # This also preloads the author of every commit. We're using "lazy_author" # here since "author" immediately loads the data on the first call. @pipeline.commit.try(:lazy_author) diff --git a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb index 7821d28ab06..40dfd893465 100644 --- a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' describe Gitlab::Ci::Pipeline::Preloader do let(:stage) { double(:stage) } @@ -11,6 +11,23 @@ describe Gitlab::Ci::Pipeline::Preloader do end describe '.preload!' do + context 'when preloading multiple commits' do + let(:project) { create(:project, :repository) } + + it 'preloads all commits once' do + expect(Commit).to receive(:decorate).once.and_call_original + + pipelines = [build_pipeline(ref: 'HEAD'), + build_pipeline(ref: 'HEAD~1')] + + described_class.preload!(pipelines) + end + + def build_pipeline(ref:) + build_stubbed(:ci_pipeline, project: project, sha: project.commit(ref).id) + end + end + it 'preloads commit authors and number of warnings' do expect(commit).to receive(:lazy_author) expect(pipeline).to receive(:number_of_warnings) -- cgit v1.2.1 From 8a3aa3a6365f511a2620b56946721f1584bade99 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 May 2018 13:47:22 +0200 Subject: Fix Rubocop offenses in pipeline stage specs --- spec/models/ci/stage_spec.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index b482e7de88b..b40496252b4 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -112,7 +112,6 @@ describe Ci::Stage, :models do end end - describe '#detailed_status' do using RSpec::Parameterized::TableSyntax @@ -134,9 +133,9 @@ describe Ci::Stage, :models do before do statuses.each do |status| create(:commit_status, project: stage.project, - pipeline: stage.pipeline, - stage_id: stage.id, - status: status) + pipeline: stage.pipeline, + stage_id: stage.id, + status: status) stage.update_status end @@ -149,13 +148,13 @@ describe Ci::Stage, :models do context 'when stage has warnings' do before do - create(:ci_build, project: stage.project, - pipeline: stage.pipeline, - stage_id: stage.id, - status: :failed, - allow_failure: true) + create(:ci_build, project: stage.project, + pipeline: stage.pipeline, + stage_id: stage.id, + status: :failed, + allow_failure: true) - stage.update_status + stage.update_status end it 'is passed with warnings' do -- cgit v1.2.1 From 8c2640a553388f12be39eaf499a63662e19f86b8 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 28 May 2018 11:39:34 +0100 Subject: missing i18n --- app/views/groups/labels/index.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index a72ea0681c8..5e37fcf0fee 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -8,16 +8,16 @@ - content_for(:header_content) do .nav-controls = link_to new_group_label_path(@group), class: "btn btn-new" do - New label + = _('New label') - if @labels.exists? #promote-label-modal %div{ class: container_class } .top-area.adjust .nav-text - Labels can be applied to issues, merge requests and epics. Group labels are available for any project within the group. + = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuables.to_sentence } - if can_admin_label - Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. + = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.') .labels-container.prepend-top-5 .other-labels -- cgit v1.2.1 From e56c0a4763c5b1718bbaace8cd8189f1cbf27a2f Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 28 May 2018 11:39:42 +0100 Subject: newline --- app/views/projects/labels/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 4d423827832..134b212298e 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -42,4 +42,4 @@ %template#js-badge-item-template %li.label-link-item.js-priority-badge.inline.prepend-left-10 - .label-badge.label-badge-blue Prioritized label \ No newline at end of file + .label-badge.label-badge-blue Prioritized label -- cgit v1.2.1 From 778e9d90da9ccc7a4b79860a98e84356dd3adaf2 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 28 May 2018 12:05:40 +0100 Subject: Fix tooltips --- app/assets/javascripts/group_label_subscription.js | 11 +++++++++++ app/assets/javascripts/project_label_subscription.js | 15 +++++++++++---- app/views/shared/_label.html.haml | 4 ++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/group_label_subscription.js b/app/assets/javascripts/group_label_subscription.js index 5648cb9a888..a0716cdfdbe 100644 --- a/app/assets/javascripts/group_label_subscription.js +++ b/app/assets/javascripts/group_label_subscription.js @@ -35,6 +35,7 @@ export default class GroupLabelSubscription { this.$unsubscribeButtons.attr('data-url', url); axios.post(url) + .then(() => GroupLabelSubscription.setNewTooltip($btn)) .then(() => this.toggleSubscriptionButtons()) .catch(() => flash(__('There was an error when subscribing to this label.'))); } @@ -44,4 +45,14 @@ export default class GroupLabelSubscription { this.$subscribeButtons.toggleClass('hidden'); this.$unsubscribeButtons.toggleClass('hidden'); } + + static setNewTooltip($button) { + if (!$button.hasClass('js-subscribe-button')) return; + + const type = $button.hasClass('js-group-level') ? 'group' : 'project'; + const title = `Unsubscribe at ${type} level`; + const $unsubscribeButton = $('.js-unsubscribe-button', $button.closest('.label-actions-list')); + + $unsubscribeButton.tooltip('hide').attr('title', title).tooltip('fixTitle'); + } } diff --git a/app/assets/javascripts/project_label_subscription.js b/app/assets/javascripts/project_label_subscription.js index c5607a3cada..72f03b02131 100644 --- a/app/assets/javascripts/project_label_subscription.js +++ b/app/assets/javascripts/project_label_subscription.js @@ -35,15 +35,22 @@ export default class ProjectLabelSubscription { this.$buttons.attr('data-status', newStatus); this.$buttons.find('> span').text(newAction); - this.$buttons.map((button) => { + this.$buttons.map((i, button) => { const $button = $(button); + const originalTitle = $button.attr('data-original-title'); - if ($button.attr('data-original-title')) { - $button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle'); - } + if (originalTitle) ProjectLabelSubscription.setNewTitle($button, originalTitle, newStatus, newAction); return button; }); }).catch(() => flash(__('There was an error subscribing to this label.'))); } + + static setNewTitle($button, originalTitle, newStatus, newAction) { + const newStatusVerb = newStatus.slice(0, -1); + const actionRegexp = new RegExp(newStatusVerb, 'i'); + const newTitle = originalTitle.replace(actionRegexp, newAction); + + $button.tooltip('hide').attr('data-original-title', newTitle).tooltip('fixTitle'); + } } diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 15e56b2cad9..71215526182 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -6,7 +6,7 @@ - toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) -- tooltip_title = "#{status.unsubscribed? ? 'Subscribe' : 'Unsubscribe'} at #{label.is_a?(ProjectLabel) ? 'project' : 'group'} level" +- tooltip_title = "#{status.unsubscribed? ? 'Subscribe' : 'Unsubscribe'} at #{status.sub('-', ' ')}" %li.label-list-item{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label, subject: subject, force_priority: force_priority @@ -61,7 +61,7 @@ %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_project_label_path(@project, label) } } %span= _('Subscribe at project level') %li - %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } } + %button.js-subscribe-button.js-group-level.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } } %span= _('Subscribe at group level') - else %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } -- cgit v1.2.1 From 0f925d626af5206820462d489edb53d15c25cdea Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 28 May 2018 12:05:52 +0100 Subject: Remove unneeded class --- app/views/shared/_label.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 71215526182..cf11cf795c3 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -48,7 +48,7 @@ - if current_user %li.inline.label-subscription - if can_subscribe_to_label_in_different_levels?(label) - %button.js-unsubscribe-button.test.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } + %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } %span= _('Unsubscribe') .dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) } %button.label-subscribe-button.btn.btn-default{ data: { toggle: 'dropdown' } } -- cgit v1.2.1 From 2ed974d22b386def4b7a3b0ab7881ffde8b8528a Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 28 May 2018 12:13:48 +0100 Subject: Fix features --- app/views/groups/labels/index.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 5e37fcf0fee..215e782d3da 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -3,6 +3,7 @@ - can_admin_label = can?(current_user, :admin_label, @group) - hide_class = '' - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') +- issuables = ['issues', 'merge requests'] - if can_admin_label - content_for(:header_content) do -- cgit v1.2.1 From 3984067c5a1f6da34fd03f760f7329405e346bc9 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 28 May 2018 12:14:01 +0100 Subject: Temp revert to label.priority? need to fix group page --- app/views/shared/_label.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index cf11cf795c3..8e3101e6248 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -2,7 +2,7 @@ - status = label_subscription_status(label, @project).inquiry if current_user - subject = local_assigns[:subject] - use_label_priority = local_assigns.fetch(:use_label_priority, false) -- force_priority = local_assigns.fetch(:force_priority, use_label_priority ? label.priority.present? : false) +- force_priority = local_assigns.fetch(:force_priority, use_label_priority ? label.priority? : false) - toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) -- cgit v1.2.1 From a3e472e0a7a6b6ec5654edd20b947ba660ed2dc3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 28 May 2018 12:22:30 +0100 Subject: Fix tooltip_title --- app/views/shared/_label.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 8e3101e6248..fae264ef3cc 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -6,7 +6,7 @@ - toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) -- tooltip_title = "#{status.unsubscribed? ? 'Subscribe' : 'Unsubscribe'} at #{status.sub('-', ' ')}" +- tooltip_title = status.unsubscribed? ? "Subscribe at #{label.is_a?(ProjectLabel) ? 'project' : 'group'} level" : "Unsubscribe at #{status.sub('-', ' ')}" %li.label-list-item{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label, subject: subject, force_priority: force_priority -- cgit v1.2.1 From aac8d1f3363a87f0bcd31009aad41d577f0a3f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Mon, 28 May 2018 21:10:51 +0200 Subject: Add check for nil auto_devops in Projects::UpdateService --- app/services/projects/update_service.rb | 2 +- spec/services/projects/update_service_spec.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 679f4a9cb62..1cea110d555 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -36,7 +36,7 @@ module Projects end def run_auto_devops_pipeline? - return false if project.repository.gitlab_ci_yml || !project.auto_devops.previous_changes.include?('enabled') + return false if project.repository.gitlab_ci_yml || project.auto_devops.nil? || !project.auto_devops.previous_changes.include?('enabled') project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?) end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 3e6073b9861..1f761bcbbad 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -275,6 +275,10 @@ describe Projects::UpdateService do it { is_expected.to eq(false) } end + context 'when auto devops is nil' do + it { is_expected.to eq(false) } + end + context 'when auto devops is explicitly enabled' do before do project.create_auto_devops!(enabled: true) -- cgit v1.2.1 From ca1d6d2f6dfd203e8b25e19ca1709ba722d922d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Mon, 28 May 2018 21:14:19 +0200 Subject: Add CHANGELOG entry --- ...ethoderror-undefined-method-previous_changes-for-nil-nilclass.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/46452-nomethoderror-undefined-method-previous_changes-for-nil-nilclass.yml diff --git a/changelogs/unreleased/46452-nomethoderror-undefined-method-previous_changes-for-nil-nilclass.yml b/changelogs/unreleased/46452-nomethoderror-undefined-method-previous_changes-for-nil-nilclass.yml new file mode 100644 index 00000000000..89dee65f5a8 --- /dev/null +++ b/changelogs/unreleased/46452-nomethoderror-undefined-method-previous_changes-for-nil-nilclass.yml @@ -0,0 +1,5 @@ +--- +title: Check for nil AutoDevOps when saving project CI/CD settings. +merge_request: 19190 +author: +type: fixed -- cgit v1.2.1 From fad343ca5b1fcdd4d01e415f31ee4a9fddb4eb99 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 29 May 2018 09:53:33 +0100 Subject: Remove screenshots --- spec/features/projects/labels/update_prioritization_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index 9500e3f3311..ae8b1364ec7 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -23,10 +23,8 @@ feature 'Prioritize labels' do expect(page).to have_content('Star labels to start sorting by priority') page.within('.other-labels') do - screenshot_and_open_image all('.js-toggle-priority')[1].click wait_for_requests - screenshot_and_open_image expect(page).not_to have_content('feature') end -- cgit v1.2.1 From c9e4c1760dae5e1916bb5ad22f260b7fd0b58475 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 29 May 2018 10:25:21 +0100 Subject: Fix project subscription_spec --- spec/features/projects/labels/subscription_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb index 70e8d436dcb..d5f5036ec72 100644 --- a/spec/features/projects/labels/subscription_spec.rb +++ b/spec/features/projects/labels/subscription_spec.rb @@ -36,7 +36,7 @@ feature 'Labels subscription' do within "#group_label_#{feature.id}" do expect(page).not_to have_button 'Unsubscribe' - click_link_on_dropdown('Group level') + click_link_on_dropdown('Subscribe at group level') expect(page).not_to have_selector('.dropdown-group-label') expect(page).to have_button 'Unsubscribe' @@ -45,7 +45,7 @@ feature 'Labels subscription' do expect(page).to have_selector('.dropdown-group-label') - click_link_on_dropdown('Project level') + click_link_on_dropdown('Subscribe at project level') expect(page).not_to have_selector('.dropdown-group-label') expect(page).to have_button 'Unsubscribe' @@ -67,6 +67,8 @@ feature 'Labels subscription' do def click_link_on_dropdown(text) find('.dropdown-group-label').click + screenshot_and_open_image + page.within('.dropdown-group-label') do find('a.js-subscribe-button', text: text).click end -- cgit v1.2.1 From 5e5828a41a95e06e7aa66368efbd296846aa1e8b Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 29 May 2018 10:25:45 +0100 Subject: Tidy tooltip_title and fix for signed out user --- app/helpers/labels_helper.rb | 10 ++++++++++ app/views/shared/_label.html.haml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index e1b0e7a4a3e..7fddf74c654 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -211,6 +211,16 @@ module LabelsHelper end end + def label_status_tooltip(status) + return '' unless status + + if status.unsubscribed? + "Subscribe at #{label.is_a?(ProjectLabel) ? 'project' : 'group'} level" + else + "Unsubscribe at #{status.sub('-', ' ')}" + end + end + # Required for Banzai::Filter::LabelReferenceFilter module_function :render_colored_label, :text_color_for_bg, :escape_once end diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index fae264ef3cc..d8ab63e053c 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -6,7 +6,7 @@ - toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) -- tooltip_title = status.unsubscribed? ? "Subscribe at #{label.is_a?(ProjectLabel) ? 'project' : 'group'} level" : "Unsubscribe at #{status.sub('-', ' ')}" +- tooltip_title = label_status_tooltip(status) %li.label-list-item{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label, subject: subject, force_priority: force_priority -- cgit v1.2.1 From 4a1d19a61d1c3d8908487dfc4a30b6962206a21b Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 29 May 2018 10:35:26 +0100 Subject: Tidy tooltip_title and fix dropdown open left --- app/helpers/labels_helper.rb | 12 +++++------- app/views/shared/_label.html.haml | 6 +++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 7fddf74c654..c7df25cecef 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -211,14 +211,12 @@ module LabelsHelper end end - def label_status_tooltip(status) - return '' unless status + def label_status_tooltip(label, status) + type = label.is_a?(ProjectLabel) ? 'project' : 'group' + level = status.unsubscribed? ? type : status.sub('-level', '') + action = status.unsubscribed? ? 'Subscribe' : 'Unsubscribe' - if status.unsubscribed? - "Subscribe at #{label.is_a?(ProjectLabel) ? 'project' : 'group'} level" - else - "Unsubscribe at #{status.sub('-', ' ')}" - end + "#{action} at #{level} level" end # Required for Banzai::Filter::LabelReferenceFilter diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index d8ab63e053c..296988ddddf 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -6,7 +6,7 @@ - toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) -- tooltip_title = label_status_tooltip(status) +- tooltip_title = label_status_tooltip(label, status) if status %li.label-list-item{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label, subject: subject, force_priority: force_priority @@ -29,7 +29,7 @@ .dropdown %button{ type: 'button', class: 'btn btn-transparent js-label-options-dropdown label-action', data: { toggle: 'dropdown' } } = sprite_icon('ellipsis_v') - .dropdown-menu.dropdown-menu-align-right + .dropdown-menu.dropdown-open-left %ul - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) %li @@ -55,7 +55,7 @@ %span = _('Subscribe') = sprite_icon('chevron-down') - .dropdown-menu.dropdown-menu-align-right + .dropdown-menu.dropdown-open-left %ul %li %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_project_label_path(@project, label) } } -- cgit v1.2.1 From 97a2f2968bdea16af21c6649319349dfde0b5d46 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 29 May 2018 10:47:58 +0100 Subject: Correct fixTitle --- app/assets/javascripts/group_label_subscription.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/group_label_subscription.js b/app/assets/javascripts/group_label_subscription.js index a0716cdfdbe..4683cc43017 100644 --- a/app/assets/javascripts/group_label_subscription.js +++ b/app/assets/javascripts/group_label_subscription.js @@ -53,6 +53,6 @@ export default class GroupLabelSubscription { const title = `Unsubscribe at ${type} level`; const $unsubscribeButton = $('.js-unsubscribe-button', $button.closest('.label-actions-list')); - $unsubscribeButton.tooltip('hide').attr('title', title).tooltip('fixTitle'); + $unsubscribeButton.tooltip('hide').attr('title', title).tooltip('_fixTitle'); } } -- cgit v1.2.1 From 35b5c830a783eb36f86d9f5e8f8c08595fd62c54 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 29 May 2018 10:48:26 +0100 Subject: Further fixes for project subscription_spec --- spec/features/projects/labels/subscription_spec.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb index d5f5036ec72..fafd338e448 100644 --- a/spec/features/projects/labels/subscription_spec.rb +++ b/spec/features/projects/labels/subscription_spec.rb @@ -67,10 +67,8 @@ feature 'Labels subscription' do def click_link_on_dropdown(text) find('.dropdown-group-label').click - screenshot_and_open_image - page.within('.dropdown-group-label') do - find('a.js-subscribe-button', text: text).click + find('.js-subscribe-button', text: text).click end end end -- cgit v1.2.1 From 3b3645136bd524a5e9f79558ab16be976cfe5282 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 29 May 2018 11:07:48 +0100 Subject: Fix update_prioritization_spec --- app/views/shared/_label.html.haml | 15 +++++++-------- .../projects/labels/update_prioritization_spec.rb | 8 ++++---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 296988ddddf..c9ff5defdc7 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -14,14 +14,13 @@ - if controller.is_a?(Projects::LabelsController) %li.inline .label-badge.label-badge-gray= label.model_name.human.capitalize - - if can?(current_user, :admin_label, label) - - if @project.present? - %li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), - dom_id: dom_id(label), type: label.type } } - %button.label-action.add-priority.btn.btn-transparent.has-tooltip{ title: _('Prioritize'), type: 'button', :'data-placement' => 'top' } - = sprite_icon('star-o') - %button.label-action.remove-priority.btn.btn-transparent.has-tooltip{ title: _('Remove priority'), type: 'button', :'data-placement' => 'top' } - = sprite_icon('star') + - if can?(current_user, :admin_label, @project) + %li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), + dom_id: dom_id(label), type: label.type } } + %button.label-action.add-priority.btn.btn-transparent.has-tooltip{ title: _('Prioritize'), type: 'button', :'data-placement' => 'top' } + = sprite_icon('star-o') + %button.label-action.remove-priority.btn.btn-transparent.has-tooltip{ title: _('Remove priority'), type: 'button', :'data-placement' => 'top' } + = sprite_icon('star') %li.inline = link_to edit_label_path(label), class: 'btn btn-transparent label-action' do = sprite_icon('pencil') diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index ae8b1364ec7..359381c391c 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -102,16 +102,16 @@ feature 'Prioritize labels' do drag_to(selector: '.label-list-item', from_index: 1, to_index: 2) page.within('.prioritized-labels') do - expect(first('li')).to have_content('feature') - expect(page.all('li').last).to have_content('bug') + expect(first('.label-list-item')).to have_content('feature') + expect(page.all('.label-list-item').last).to have_content('bug') end refresh wait_for_requests page.within('.prioritized-labels') do - expect(first('li')).to have_content('feature') - expect(page.all('li').last).to have_content('bug') + expect(first('.label-list-item')).to have_content('feature') + expect(page.all('.label-list-item').last).to have_content('bug') end end -- cgit v1.2.1 From cd0be3ba456f5cf61ed72f1c6b36bef799d7eaaf Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 29 May 2018 12:11:14 +0100 Subject: Fix user_removes_labels_spec --- app/views/shared/_label.html.haml | 3 ++- .../projects/labels/user_removes_labels_spec.rb | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index c9ff5defdc7..391021a7665 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -43,7 +43,8 @@ toggle: 'modal' } } = _('Promote to group label') %li - = link_to _('Delete'), destroy_label_path(label), title: 'Delete', method: :delete, data: { confirm: _('Remove this label? Are you sure?') }, class: 'text-danger' + %span{ data: { toggle: 'modal', target: "#modal-delete-label-#{label.id}" } } + = button_tag 'Delete', type: 'button', class: 'text-danger remove-row' - if current_user %li.inline.label-subscription - if can_subscribe_to_label_in_different_levels?(label) diff --git a/spec/features/projects/labels/user_removes_labels_spec.rb b/spec/features/projects/labels/user_removes_labels_spec.rb index f4fda6de465..120c618b530 100644 --- a/spec/features/projects/labels/user_removes_labels_spec.rb +++ b/spec/features/projects/labels/user_removes_labels_spec.rb @@ -17,8 +17,9 @@ describe "User removes labels" do end it "removes label" do - page.within(".labels") do + page.within(".other-labels") do page.first(".label-list-item") do + first('.js-label-options-dropdown').click first(".remove-row").click first(:link, "Delete label").click end @@ -36,17 +37,15 @@ describe "User removes labels" do end it "removes all labels" do - page.within(".labels") do - loop do - li = page.first(".label-list-item") - break unless li - - li.click_link("Delete") - click_link("Delete label") - end - - expect(page).to have_content("Generate a default set of labels").and have_content("New label") + loop do + li = page.first(".label-list-item") + break unless li + li.find('.js-label-options-dropdown').click + li.click_button("Delete") + click_link("Delete label") end + + expect(page).to have_content("Generate a default set of labels").and have_content("New label") end end end -- cgit v1.2.1 From dcacfaa197d913633935dddaa9bfba5cf295c570 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 29 May 2018 12:11:20 +0100 Subject: Fix lint --- app/assets/stylesheets/pages/labels.scss | 4 ++-- app/views/shared/_label_row.html.haml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index c25e6ce23bb..bbde68cd08f 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -267,8 +267,8 @@ } .label-list-item { - .content-list &:before, - .content-list &:after { + .content-list &::before, + .content-list &::after { content: none; display: block; } diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 42d9a97d3a1..ed23db156bc 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -20,4 +20,4 @@ = link_to_label(label, subject: subject, type: :merge_request) { _('Merge requests') } - if force_priority %li.label-link-item.js-priority-badge.inline.prepend-left-10 - .label-badge.label-badge-blue= _('Prioritized label') \ No newline at end of file + .label-badge.label-badge-blue= _('Prioritized label') -- cgit v1.2.1 From 9b9cdc984cb785ceee4ed1902625c65492640621 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 29 May 2018 12:50:29 +0100 Subject: fix lint --- spec/features/projects/labels/user_removes_labels_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/projects/labels/user_removes_labels_spec.rb b/spec/features/projects/labels/user_removes_labels_spec.rb index 120c618b530..efa74015c6e 100644 --- a/spec/features/projects/labels/user_removes_labels_spec.rb +++ b/spec/features/projects/labels/user_removes_labels_spec.rb @@ -40,6 +40,7 @@ describe "User removes labels" do loop do li = page.first(".label-list-item") break unless li + li.find('.js-label-options-dropdown').click li.click_button("Delete") click_link("Delete label") -- cgit v1.2.1 From 64679a0d9a654d4df88af45b7fdd4e322091f51e Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 29 May 2018 15:25:43 +0300 Subject: Backport of https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5876 --- config/initializers/rack_attack_global.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/initializers/rack_attack_global.rb b/config/initializers/rack_attack_global.rb index a90516eee7d..45963831c41 100644 --- a/config/initializers/rack_attack_global.rb +++ b/config/initializers/rack_attack_global.rb @@ -26,7 +26,7 @@ class Rack::Attack throttle('throttle_unauthenticated', Gitlab::Throttle.unauthenticated_options) do |req| Gitlab::Throttle.settings.throttle_unauthenticated_enabled && req.unauthenticated? && - !req.api_internal_request? && + !req.should_be_skipped? && req.ip end @@ -59,6 +59,10 @@ class Rack::Attack path =~ %r{^/api/v\d+/internal/} end + def should_be_skipped? + api_internal_request? + end + def web_request? !api_request? end -- cgit v1.2.1 From fc3d214130f2038b3c8bdf142506c9116f373244 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 29 May 2018 15:20:10 +0200 Subject: Add a feature flag for switching pipeline stages --- app/models/ci/pipeline.rb | 16 +++--- app/serializers/pipeline_details_entity.rb | 2 +- spec/models/ci/pipeline_spec.rb | 81 ++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 7 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 9dac56fcd57..4b8c23a393d 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -250,13 +250,17 @@ module Ci end ## - # TODO consider switching to persisted stages only in pipelines table - # (not necessairly in the show pipeline page because of #23257. - # Hide this behind two feature flags - enabled / disabled and only - # gitlab-ce / everywhere. + # TODO We do not completely switch to persisted stages because of + # race conditions with setting statuses gitlab-ce#23257. # - def stages - super + def ordered_stages + return legacy_stages unless complete? + + if Feature.enabled?('ci_pipeline_persisted_stages') + stages + else + legacy_stages + end end def legacy_stages diff --git a/app/serializers/pipeline_details_entity.rb b/app/serializers/pipeline_details_entity.rb index d58572a5f87..8ba9cac53c4 100644 --- a/app/serializers/pipeline_details_entity.rb +++ b/app/serializers/pipeline_details_entity.rb @@ -1,6 +1,6 @@ class PipelineDetailsEntity < PipelineEntity expose :details do - expose :stages, using: StageEntity + expose :ordered_stages, as: :stages, using: StageEntity expose :artifacts, using: BuildArtifactEntity expose :manual_actions, using: BuildActionEntity end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index e7845b693a1..f3725e24470 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -500,6 +500,87 @@ describe Ci::Pipeline, :mailer do end end end + + describe '#stages' do + before do + create(:ci_stage_entity, project: project, + pipeline: pipeline, + name: 'build') + end + + it 'returns persisted stages' do + expect(pipeline.stages).not_to be_empty + expect(pipeline.stages).to all(be_persisted) + end + end + + describe '#ordered_stages' do + before do + create(:ci_stage_entity, project: project, + pipeline: pipeline, + position: 4, + name: 'deploy') + + create(:ci_build, project: project, + pipeline: pipeline, + stage: 'test', + stage_idx: 3, + name: 'test') + + create(:ci_build, project: project, + pipeline: pipeline, + stage: 'build', + stage_idx: 2, + name: 'build') + + create(:ci_stage_entity, project: project, + pipeline: pipeline, + position: 1, + name: 'sanity') + + create(:ci_stage_entity, project: project, + pipeline: pipeline, + position: 5, + name: 'cleanup') + end + + subject { pipeline.ordered_stages } + + context 'when using legacy stages' do + before do + stub_feature_flags(ci_pipeline_persisted_stages: false) + end + + it 'returns legacy stages in valid order' do + expect(subject.map(&:name)).to eq %w[build test] + end + end + + context 'when using persisted stages' do + before do + stub_feature_flags(ci_pipeline_persisted_stages: true) + end + + context 'when pipelines is not complete' do + it 'still returns legacy stages' do + expect(subject).to all(be_a Ci::LegacyStage) + expect(subject.map(&:name)).to eq %w[build test] + end + end + + context 'when pipeline is complete' do + before do + pipeline.succeed! + end + + it 'returns stages in valid order' do + expect(subject).to all(be_a Ci::Stage) + expect(subject.map(&:name)) + .to eq %w[sanity build test deploy cleanup] + end + end + end + end end describe 'state machine' do -- cgit v1.2.1 From 3662f147692749b20c5c8ad8ea391fd94e43ed38 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 29 May 2018 17:19:39 +0200 Subject: Improve specs for pipelines controller --- .../projects/pipelines_controller_spec.rb | 67 +++++++++++++++++----- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 62eece8de4a..92886e93077 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -17,26 +17,65 @@ describe Projects::PipelinesController do describe 'GET index.json' do before do - %w(pending running success failed).each_with_index do |status, index| + %w(pending running success failed canceled).each_with_index do |status, index| create_pipeline(status, project.commit("HEAD~#{index}")) end end - it 'returns JSON with serialized pipelines', :request_store do - queries = ActiveRecord::QueryRecorder.new do - get_pipelines_index_json + context 'when using persisted stages', :request_store do + before do + stub_feature_flags(ci_pipeline_persisted_stages: true) end - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('pipeline') - - expect(json_response).to include('pipelines') - expect(json_response['pipelines'].count).to eq 4 - expect(json_response['count']['all']).to eq '4' - expect(json_response['count']['running']).to eq '1' - expect(json_response['count']['pending']).to eq '1' - expect(json_response['count']['finished']).to eq '2' - expect(queries.count).to be < 30 + it 'returns serialized pipelines', :request_store do + queries = ActiveRecord::QueryRecorder.new do + get_pipelines_index_json + end + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('pipeline') + + expect(json_response).to include('pipelines') + expect(json_response['pipelines'].count).to eq 5 + expect(json_response['count']['all']).to eq '5' + expect(json_response['count']['running']).to eq '1' + expect(json_response['count']['pending']).to eq '1' + expect(json_response['count']['finished']).to eq '3' + + json_response.dig('pipelines', 0, 'details', 'stages').tap do |stages| + expect(stages.count).to eq 3 + end + + expect(queries.count).to be + end + end + + context 'when using legacy stages', :request_store do + before do + stub_feature_flags(ci_pipeline_persisted_stages: false) + end + + it 'returns JSON with serialized pipelines', :request_store do + queries = ActiveRecord::QueryRecorder.new do + get_pipelines_index_json + end + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('pipeline') + + expect(json_response).to include('pipelines') + expect(json_response['pipelines'].count).to eq 5 + expect(json_response['count']['all']).to eq '5' + expect(json_response['count']['running']).to eq '1' + expect(json_response['count']['pending']).to eq '1' + expect(json_response['count']['finished']).to eq '3' + + json_response.dig('pipelines', 0, 'details', 'stages').tap do |stages| + expect(stages.count).to eq 3 + end + + expect(queries.count).to be_within(3).of(30) + end end it 'does not include coverage data for the pipelines' do -- cgit v1.2.1 From 162cc55e49e155d64ee862dfebefd97cec7d272c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 29 May 2018 17:23:49 +0200 Subject: Use persisted stage feature to serialize pipelines --- spec/serializers/pipeline_serializer_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 9319d29279a..7c7cdaedf10 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -8,6 +8,10 @@ describe PipelineSerializer do described_class.new(current_user: user) end + before do + stub_feature_flags(ci_pipeline_persisted_stages: true) + end + subject { serializer.represent(resource) } describe '#represent' do -- cgit v1.2.1 From 59e1e9710898c4547c79a3d5eef39a3f88d3bd7a Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 30 May 2018 15:17:09 +0900 Subject: Add build_relations method in Chain::Populate --- lib/gitlab/ci/pipeline/chain/create.rb | 3 --- lib/gitlab/ci/pipeline/chain/populate.rb | 17 +++++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb index 918a0d151fc..f4c8d5342c1 100644 --- a/lib/gitlab/ci/pipeline/chain/create.rb +++ b/lib/gitlab/ci/pipeline/chain/create.rb @@ -6,9 +6,6 @@ module Gitlab include Chain::Helpers def perform! - # Allocate next IID outside of transaction - pipeline.ensure_project_iid! - ::Ci::Pipeline.transaction do pipeline.save! diff --git a/lib/gitlab/ci/pipeline/chain/populate.rb b/lib/gitlab/ci/pipeline/chain/populate.rb index 69b8a8fc68f..7a2a1c6a80b 100644 --- a/lib/gitlab/ci/pipeline/chain/populate.rb +++ b/lib/gitlab/ci/pipeline/chain/populate.rb @@ -8,10 +8,7 @@ module Gitlab PopulateError = Class.new(StandardError) def perform! - ## - # Populate pipeline with block argument of CreatePipelineService#execute. - # - @command.seeds_block&.call(pipeline) + build_relations ## # Populate pipeline with all stages, and stages with builds. @@ -34,6 +31,18 @@ module Gitlab def break? pipeline.errors.any? end + + private + + def build_relations + ## + # Populate pipeline with block argument of CreatePipelineService#execute. + # + @command.seeds_block&.call(pipeline) + + # Allocate next IID. This operation must be outside of transactions of pipeline creations. + pipeline.ensure_project_iid! + end end end end -- cgit v1.2.1 From 0e22b50df8b269ccae32ab68b9ba26e7eea861b0 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 30 May 2018 16:42:55 +0900 Subject: Add spec for variables expression --- spec/lib/gitlab/ci/pipeline/chain/create_spec.rb | 32 ++++++++++------------ spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb | 24 ++++++++++++++++ spec/models/ci/pipeline_spec.rb | 14 ++++++++++ 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb index de69a65aee4..0edc3f315bb 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb @@ -37,23 +37,21 @@ describe Gitlab::Ci::Pipeline::Chain::Create do end context 'when pipeline has validation errors' do - context 'when ref is nil' do - let(:pipeline) do - build(:ci_pipeline, project: project, ref: nil) - end - - before do - step.perform! - end - - it 'breaks the chain' do - expect(step.break?).to be true - end - - it 'appends validation error' do - expect(pipeline.errors.to_a) - .to include /Failed to persist the pipeline/ - end + let(:pipeline) do + build(:ci_pipeline, project: project, ref: nil) + end + + before do + step.perform! + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + + it 'appends validation error' do + expect(pipeline.errors.to_a) + .to include /Failed to persist the pipeline/ end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index 4d7d6951a51..bcfa9f0c282 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -42,6 +42,10 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do it 'correctly assigns user' do expect(pipeline.builds).to all(have_attributes(user: user)) end + + it 'has pipeline iid' do + expect(pipeline.iid).to be > 0 + end end context 'when pipeline is empty' do @@ -68,6 +72,10 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do expect(pipeline.errors.to_a) .to include 'No stages / jobs for this pipeline.' end + + it 'wastes pipeline iid' do + expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0 + end end context 'when pipeline has validation errors' do @@ -87,6 +95,10 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do expect(pipeline.errors.to_a) .to include 'Failed to build the pipeline!' end + + it 'wastes pipeline iid' do + expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0 + end end context 'when there is a seed blocks present' do @@ -111,6 +123,12 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do expect(pipeline.variables.first.key).to eq 'VAR' expect(pipeline.variables.first.value).to eq '123' end + + it 'has pipeline iid' do + step.perform! + + expect(pipeline.iid).to be > 0 + end end context 'when seeds block tries to persist some resources' do @@ -121,6 +139,12 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do it 'raises exception' do expect { step.perform! }.to raise_error(ActiveRecord::RecordNotSaved) end + + it 'does not waste pipeline iid' do + step.perform rescue nil + + expect(InternalId.ci_pipelines.where(project_id: project.id).exists?).to be_falsy + end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 314cb3a28ed..7d28f2eb86b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -397,6 +397,20 @@ describe Ci::Pipeline, :mailer do expect(seeds.size).to eq 1 expect(seeds.dig(0, 0, :name)).to eq 'unit' end + + context "when pipeline iid is used for 'only' keyword" do + let(:config) do + { rspec: { script: 'rspec', only: { variables: ['$CI_PIPELINE_IID == 2'] } }, + prod: { script: 'cap prod', only: { variables: ['$CI_PIPELINE_IID == 1'] } } } + end + + it 'returns stage seeds only when variables expression is truthy' do + seeds = pipeline.stage_seeds + + expect(seeds.size).to eq 1 + expect(seeds.dig(0, 0, :name)).to eq 'prod' + end + end end end -- cgit v1.2.1 From 74caf54704704e30b8ed45e2459828890f416b08 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 30 May 2018 15:49:55 +0200 Subject: Fix pipeline serializer queries count specs --- spec/serializers/pipeline_serializer_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 7c7cdaedf10..e0e6eecb300 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -112,7 +112,7 @@ describe PipelineSerializer do # gitaly calls in this block # Issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/37772 Gitlab::GitalyClient.allow_n_plus_1_calls do - Ci::Pipeline::AVAILABLE_STATUSES.each do |status| + Ci::Pipeline::COMPLETED_STATUSES.each do |status| create_pipeline(status) end end @@ -125,7 +125,7 @@ describe PipelineSerializer do it 'verifies number of queries', :request_store do recorded = ActiveRecord::QueryRecorder.new { subject } - expect(recorded.count).to be_within(1).of(31) + expect(recorded.count).to be_within(2).of(27) expect(recorded.cached_count).to eq(0) end end @@ -144,7 +144,7 @@ describe PipelineSerializer do # pipeline. With the same ref this check is cached but if refs are # different then there is an extra query per ref # https://gitlab.com/gitlab-org/gitlab-ce/issues/46368 - expect(recorded.count).to be_within(1).of(38) + expect(recorded.count).to be_within(2).of(30) expect(recorded.cached_count).to eq(0) end end -- cgit v1.2.1 From e542583e91dc53698048686cf4268eaeae807973 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 30 May 2018 16:04:52 +0200 Subject: Add a unique index to stages on pipeline / position --- db/migrate/20180530135500_add_index_to_stages_position.rb | 15 +++++++++++++++ db/schema.rb | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20180530135500_add_index_to_stages_position.rb diff --git a/db/migrate/20180530135500_add_index_to_stages_position.rb b/db/migrate/20180530135500_add_index_to_stages_position.rb new file mode 100644 index 00000000000..e60b742a59b --- /dev/null +++ b/db/migrate/20180530135500_add_index_to_stages_position.rb @@ -0,0 +1,15 @@ +class AddIndexToStagesPosition < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :ci_stages, [:pipeline_id, :position], unique: true + end + + def down + remove_concurrent_index :ci_stages, [:pipeline_id, :position] + end +end diff --git a/db/schema.rb b/db/schema.rb index 932b7f8da02..a3f27f87ff1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180529093006) do +ActiveRecord::Schema.define(version: 20180530135500) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -518,6 +518,7 @@ ActiveRecord::Schema.define(version: 20180529093006) do end add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true, using: :btree + add_index "ci_stages", ["pipeline_id", "position"], name: "index_ci_stages_on_pipeline_id_and_position", unique: true, using: :btree add_index "ci_stages", ["pipeline_id"], name: "index_ci_stages_on_pipeline_id", using: :btree add_index "ci_stages", ["project_id"], name: "index_ci_stages_on_project_id", using: :btree -- cgit v1.2.1 From d10a70a0c56e648d961f4246ab4f64dcee0da5ac Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 30 May 2018 09:47:38 -0700 Subject: Fix missing squash parameter in doc/api/merge_requests.md --- doc/api/merge_requests.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 8849f490c4f..8dbe35d4ac6 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -476,6 +476,7 @@ Parameters: "changes_count": "1", "should_remove_source_branch": true, "force_remove_source_branch": false, + "squash": false, "web_url": "http://example.com/example/example/merge_requests/1", "discussion_locked": false, "time_stats": { -- cgit v1.2.1 From 93241149e646f546e9a4406e85ced66c8d54099f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 31 May 2018 16:05:40 +0200 Subject: Make pipeline / stage position index not unique --- db/migrate/20180530135500_add_index_to_stages_position.rb | 2 +- db/schema.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/db/migrate/20180530135500_add_index_to_stages_position.rb b/db/migrate/20180530135500_add_index_to_stages_position.rb index e60b742a59b..61150f33a25 100644 --- a/db/migrate/20180530135500_add_index_to_stages_position.rb +++ b/db/migrate/20180530135500_add_index_to_stages_position.rb @@ -6,7 +6,7 @@ class AddIndexToStagesPosition < ActiveRecord::Migration disable_ddl_transaction! def up - add_concurrent_index :ci_stages, [:pipeline_id, :position], unique: true + add_concurrent_index :ci_stages, [:pipeline_id, :position] end def down diff --git a/db/schema.rb b/db/schema.rb index a3f27f87ff1..ebb4fa6c533 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -518,7 +518,7 @@ ActiveRecord::Schema.define(version: 20180530135500) do end add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true, using: :btree - add_index "ci_stages", ["pipeline_id", "position"], name: "index_ci_stages_on_pipeline_id_and_position", unique: true, using: :btree + add_index "ci_stages", ["pipeline_id", "position"], name: "index_ci_stages_on_pipeline_id_and_position", using: :btree add_index "ci_stages", ["pipeline_id"], name: "index_ci_stages_on_pipeline_id", using: :btree add_index "ci_stages", ["project_id"], name: "index_ci_stages_on_project_id", using: :btree -- cgit v1.2.1 From b6caf937ccc780bd2abd7c4518f85ebcad12548f Mon Sep 17 00:00:00 2001 From: Maxime Guyot Date: Thu, 31 May 2018 22:10:56 +0200 Subject: Update Doorkeeper OpenID for Kubernetes support --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4d2bd62bec0..9fc895e42be 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -172,7 +172,7 @@ GEM unf (>= 0.0.5, < 1.0.0) doorkeeper (4.3.2) railties (>= 4.2) - doorkeeper-openid_connect (1.3.0) + doorkeeper-openid_connect (1.4.0) doorkeeper (~> 4.3) json-jwt (~> 1.6) dropzonejs-rails (0.7.2) -- cgit v1.2.1 From fbc9a1ac69145a3b6f8d4d4f96363b0f81225652 Mon Sep 17 00:00:00 2001 From: Andrew Winata Date: Fri, 1 Jun 2018 15:02:52 +1000 Subject: Docs clarify API to share project to a group --- doc/api/members.md | 4 ++++ doc/api/projects.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/api/members.md b/doc/api/members.md index 3234f833eae..fca5ce16baf 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -173,3 +173,7 @@ DELETE /projects/:id/members/:user_id curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/members/:user_id curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/members/:user_id ``` + +## Give a group access to a project + +Look at [share project with group](projects.md#share-project-with-group) diff --git a/doc/api/projects.md b/doc/api/projects.md index d3e95926322..30a41839f28 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1198,7 +1198,7 @@ POST /projects/:id/share | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | | `group_id` | integer | yes | The ID of the group to share with | -| `group_access` | integer | yes | The permissions level to grant the group | +| `group_access` | integer | yes | The [permissions level](members.md) to grant the group | | `expires_at` | string | no | Share expiration date in ISO 8601 format: 2016-09-26 | ## Delete a shared project link within a group -- cgit v1.2.1 From 0d44f4d50ef175997fe1f90de9e622a4f3b867e3 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Wed, 23 May 2018 09:54:57 +0800 Subject: Rephrase "maintainer" to more precise "members who can merge to the target branch" "Maintainer" will be freed to be used for #42751 --- .../vue_merge_request_widget/mr_widget_options.vue | 4 +- .../stores/mr_widget_store.js | 2 +- .../merge_requests/application_controller.rb | 2 +- app/helpers/merge_requests_helper.rb | 4 +- app/models/merge_request.rb | 12 +-- app/models/project.rb | 14 ++-- app/policies/ci/build_policy.rb | 6 +- app/policies/ci/pipeline_policy.rb | 6 +- app/serializers/merge_request_widget_entity.rb | 2 +- app/services/merge_requests/base_service.rb | 4 +- .../shared/issuable/form/_contribution.html.haml | 10 +-- .../shared/projects/_edit_information.html.haml | 2 +- .../unreleased/42751-rename-mr-maintainer-push.yml | 5 ++ ...name_merge_requests_allow_maintainer_to_push.rb | 15 ++++ ...rge_requests_allow_maintainer_to_push_rename.rb | 15 ++++ db/schema.rb | 2 +- doc/api/merge_requests.md | 8 +- .../project/merge_requests/allow_collaboration.md | 20 +++++ .../merge_requests/img/allow_collaboration.png | Bin 0 -> 39513 bytes .../merge_requests/img/allow_maintainer_push.png | Bin 49216 -> 0 bytes doc/user/project/merge_requests/index.md | 2 +- .../project/merge_requests/maintainer_access.md | 21 +---- lib/api/entities.rb | 4 +- lib/api/merge_requests.rb | 3 +- lib/gitlab/user_access.rb | 2 +- locale/gitlab.pot | 4 +- .../merge_request/maintainer_edits_fork_spec.rb | 2 +- .../user_allows_a_maintainer_to_push_spec.rb | 85 --------------------- ...ows_commits_from_memebers_who_can_merge_spec.rb | 85 +++++++++++++++++++++ .../api/schemas/entities/merge_request_basic.json | 1 + .../api/schemas/entities/merge_request_widget.json | 2 +- .../api/schemas/public_api/v4/merge_requests.json | 1 + .../gitlab/import_export/safe_model_attributes.yml | 2 +- spec/lib/gitlab/user_access_spec.rb | 2 +- spec/models/merge_request_spec.rb | 28 +++---- spec/models/project_spec.rb | 22 +++--- spec/policies/ci/build_policy_spec.rb | 2 +- spec/policies/ci/pipeline_policy_spec.rb | 2 +- spec/policies/project_policy_spec.rb | 2 +- spec/requests/api/merge_requests_spec.rb | 12 +-- spec/services/ci/retry_pipeline_service_spec.rb | 4 +- .../services/merge_requests/update_service_spec.rb | 12 +-- 42 files changed, 240 insertions(+), 193 deletions(-) create mode 100644 changelogs/unreleased/42751-rename-mr-maintainer-push.yml create mode 100644 db/migrate/20180523042841_rename_merge_requests_allow_maintainer_to_push.rb create mode 100644 db/post_migrate/20180523125103_cleanup_merge_requests_allow_maintainer_to_push_rename.rb create mode 100644 doc/user/project/merge_requests/allow_collaboration.md create mode 100644 doc/user/project/merge_requests/img/allow_collaboration.png delete mode 100644 doc/user/project/merge_requests/img/allow_maintainer_push.png delete mode 100644 spec/features/merge_request/user_allows_a_maintainer_to_push_spec.rb create mode 100644 spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index f69fe03fcb3..c20d07a169d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -265,10 +265,10 @@ export default { /> Gitlab::VisibilityLevel::PRIVATE && source_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE && !ProtectedBranch.protected?(source_project, source_branch) end - def can_allow_maintainer_to_push?(user) - maintainer_push_possible? && + def can_allow_collaboration?(user) + collaborative_push_possible? && Ability.allowed?(user, :push_code, source_project) end diff --git a/app/models/project.rb b/app/models/project.rb index e275ac4dc6f..73b808b4cb5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1973,18 +1973,18 @@ class Project < ActiveRecord::Base .limit(1) .select(1) source_of_merge_requests.opened - .where(allow_maintainer_to_push: true) + .where(allow_collaboration: true) .where('EXISTS (?)', developer_access_exists) end - def branch_allows_maintainer_push?(user, branch_name) + def branch_allows_collaboration?(user, branch_name) return false unless user cache_key = "user:#{user.id}:#{branch_name}:branch_allows_push" - memoized_results = strong_memoize(:branch_allows_maintainer_push) do + memoized_results = strong_memoize(:branch_allows_collaboration) do Hash.new do |result, cache_key| - result[cache_key] = fetch_branch_allows_maintainer_push?(user, branch_name) + result[cache_key] = fetch_branch_allows_collaboration?(user, branch_name) end end @@ -2126,18 +2126,18 @@ class Project < ActiveRecord::Base raise ex end - def fetch_branch_allows_maintainer_push?(user, branch_name) + def fetch_branch_allows_collaboration?(user, branch_name) check_access = -> do next false if empty_repo? merge_request = source_of_merge_requests.opened - .where(allow_maintainer_to_push: true) + .where(allow_collaboration: true) .find_by(source_branch: branch_name) merge_request&.can_be_merged_by?(user) end if RequestStore.active? - RequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_maintainer_push") do + RequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_collaboration") do check_access.call end else diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb index 8b65758f3e8..1c0cc7425ec 100644 --- a/app/policies/ci/build_policy.rb +++ b/app/policies/ci/build_policy.rb @@ -14,8 +14,8 @@ module Ci @subject.triggered_by?(@user) end - condition(:branch_allows_maintainer_push) do - @subject.project.branch_allows_maintainer_push?(@user, @subject.ref) + condition(:branch_allows_collaboration) do + @subject.project.branch_allows_collaboration?(@user, @subject.ref) end rule { protected_ref }.policy do @@ -25,7 +25,7 @@ module Ci rule { can?(:admin_build) | (can?(:update_build) & owner_of_job) }.enable :erase_build - rule { can?(:public_access) & branch_allows_maintainer_push }.policy do + rule { can?(:public_access) & branch_allows_collaboration }.policy do enable :update_build enable :update_commit_status end diff --git a/app/policies/ci/pipeline_policy.rb b/app/policies/ci/pipeline_policy.rb index 540e4235299..b81329d0625 100644 --- a/app/policies/ci/pipeline_policy.rb +++ b/app/policies/ci/pipeline_policy.rb @@ -4,13 +4,13 @@ module Ci condition(:protected_ref) { ref_protected?(@user, @subject.project, @subject.tag?, @subject.ref) } - condition(:branch_allows_maintainer_push) do - @subject.project.branch_allows_maintainer_push?(@user, @subject.ref) + condition(:branch_allows_collaboration) do + @subject.project.branch_allows_collaboration?(@user, @subject.ref) end rule { protected_ref }.prevent :update_pipeline - rule { can?(:public_access) & branch_allows_maintainer_push }.policy do + rule { can?(:public_access) & branch_allows_collaboration }.policy do enable :update_pipeline end diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index 141070aef45..8260c6c7b84 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -13,7 +13,7 @@ class MergeRequestWidgetEntity < IssuableEntity expose :squash expose :target_branch expose :target_project_id - expose :allow_maintainer_to_push + expose :allow_collaboration expose :should_be_rebased?, as: :should_be_rebased expose :ff_only_enabled do |merge_request| diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 231ab76fde4..4c420b38258 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -38,8 +38,8 @@ module MergeRequests def filter_params(merge_request) super - unless merge_request.can_allow_maintainer_to_push?(current_user) - params.delete(:allow_maintainer_to_push) + unless merge_request.can_allow_collaboration?(current_user) + params.delete(:allow_collaboration) end end diff --git a/app/views/shared/issuable/form/_contribution.html.haml b/app/views/shared/issuable/form/_contribution.html.haml index b34549240e0..519b5fae846 100644 --- a/app/views/shared/issuable/form/_contribution.html.haml +++ b/app/views/shared/issuable/form/_contribution.html.haml @@ -12,9 +12,9 @@ = _('Contribution') .col-sm-10 .form-check - = form.check_box :allow_maintainer_to_push, disabled: !issuable.can_allow_maintainer_to_push?(current_user), class: 'form-check-input' - = form.label :allow_maintainer_to_push, class: 'form-check-label' do - = _('Allow edits from maintainers.') - = link_to 'About this feature', help_page_path('user/project/merge_requests/maintainer_access') + = form.check_box :allow_collaboration, disabled: !issuable.can_allow_collaboration?(current_user), class: 'form-check-input' + = form.label :allow_collaboration, class: 'form-check-label' do + = _('Allow commits from members who can merge to the target branch.') + = link_to 'About this feature', help_page_path('user/project/merge_requests/allow_collaboration') .form-text.text-muted - = allow_maintainer_push_unavailable_reason(issuable) + = allow_collaboration_unavailable_reason(issuable) diff --git a/app/views/shared/projects/_edit_information.html.haml b/app/views/shared/projects/_edit_information.html.haml index ec9dc8f62c2..9230e045a81 100644 --- a/app/views/shared/projects/_edit_information.html.haml +++ b/app/views/shared/projects/_edit_information.html.haml @@ -1,6 +1,6 @@ - unless can?(current_user, :push_code, @project) .inline.prepend-left-10 - - if @project.branch_allows_maintainer_push?(current_user, selected_branch) + - if @project.branch_allows_collaboration?(current_user, selected_branch) = commit_in_single_accessible_branch - else = commit_in_fork_help diff --git a/changelogs/unreleased/42751-rename-mr-maintainer-push.yml b/changelogs/unreleased/42751-rename-mr-maintainer-push.yml new file mode 100644 index 00000000000..aa29f6ed4b7 --- /dev/null +++ b/changelogs/unreleased/42751-rename-mr-maintainer-push.yml @@ -0,0 +1,5 @@ +--- +title: Rephrasing Merge Request's 'allow edits from maintainer' functionality +merge_request: 19061 +author: +type: deprecated diff --git a/db/migrate/20180523042841_rename_merge_requests_allow_maintainer_to_push.rb b/db/migrate/20180523042841_rename_merge_requests_allow_maintainer_to_push.rb new file mode 100644 index 00000000000..975bdfe70f4 --- /dev/null +++ b/db/migrate/20180523042841_rename_merge_requests_allow_maintainer_to_push.rb @@ -0,0 +1,15 @@ +class RenameMergeRequestsAllowMaintainerToPush < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + rename_column_concurrently :merge_requests, :allow_maintainer_to_push, :allow_collaboration + end + + def down + cleanup_concurrent_column_rename :merge_requests, :allow_collaboration, :allow_maintainer_to_push + end +end diff --git a/db/post_migrate/20180523125103_cleanup_merge_requests_allow_maintainer_to_push_rename.rb b/db/post_migrate/20180523125103_cleanup_merge_requests_allow_maintainer_to_push_rename.rb new file mode 100644 index 00000000000..b9ce4600675 --- /dev/null +++ b/db/post_migrate/20180523125103_cleanup_merge_requests_allow_maintainer_to_push_rename.rb @@ -0,0 +1,15 @@ +class CleanupMergeRequestsAllowMaintainerToPushRename < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + cleanup_concurrent_column_rename :merge_requests, :allow_maintainer_to_push, :allow_collaboration + end + + def down + rename_column_concurrently :merge_requests, :allow_collaboration, :allow_maintainer_to_push + end +end diff --git a/db/schema.rb b/db/schema.rb index f8663574580..a53b09bfcb7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1216,7 +1216,7 @@ ActiveRecord::Schema.define(version: 20180529093006) do t.boolean "discussion_locked" t.integer "latest_merge_request_diff_id" t.string "rebase_commit_sha" - t.boolean "allow_maintainer_to_push" + t.boolean "allow_collaboration" t.boolean "squash", default: false, null: false end diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 051d2a10bc6..c31de9c3595 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -650,7 +650,8 @@ POST /projects/:id/merge_requests | `labels` | string | no | Labels for MR as a comma-separated list | | `milestone_id` | integer | no | The global ID of a milestone | | `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging | -| `allow_maintainer_to_push` | boolean | no | Whether or not a maintainer of the target project can push to the source branch | +| `allow_collaboration` | boolean | no | Allow commits from members who can merge to the target branch | +| `allow_maintainer_to_push` | boolean | no | Deprecated, see allow_collaboration | | `squash` | boolean | no | Squash commits into a single commit when merging | ```json @@ -708,6 +709,7 @@ POST /projects/:id/merge_requests "squash": false, "web_url": "http://example.com/example/example/merge_requests/1", "discussion_locked": false, + "allow_collaboration": false, "allow_maintainer_to_push": false, "time_stats": { "time_estimate": 0, @@ -740,7 +742,8 @@ PUT /projects/:id/merge_requests/:merge_request_iid | `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging | | `squash` | boolean | no | Squash commits into a single commit when merging | | `discussion_locked` | boolean | no | Flag indicating if the merge request's discussion is locked. If the discussion is locked only project members can add, edit or resolve comments. | -| `allow_maintainer_to_push` | boolean | no | Whether or not a maintainer of the target project can push to the source branch | +| `allow_collaboration` | boolean | no | Allow commits from members who can merge to the target branch | +| `allow_maintainer_to_push` | boolean | no | Deprecated, see allow_collaboration | Must include at least one non-required attribute from above. @@ -798,6 +801,7 @@ Must include at least one non-required attribute from above. "squash": false, "web_url": "http://example.com/example/example/merge_requests/1", "discussion_locked": false, + "allow_collaboration": false, "allow_maintainer_to_push": false, "time_stats": { "time_estimate": 0, diff --git a/doc/user/project/merge_requests/allow_collaboration.md b/doc/user/project/merge_requests/allow_collaboration.md new file mode 100644 index 00000000000..859ac92ef89 --- /dev/null +++ b/doc/user/project/merge_requests/allow_collaboration.md @@ -0,0 +1,20 @@ +# Allow collaboration on merge requests across forks + +> [Introduced][ce-17395] in GitLab 10.6. + +This feature is available for merge requests across forked projects that are +publicly accessible. It makes it easier for members of projects to +collaborate on merge requests across forks. + +When enabled for a merge request, members with merge access to the target +branch of the project will be granted write permissions to the source branch +of the merge request. + +The feature can only be enabled by users who already have push access to the +source project, and only lasts while the merge request is open. + +Enable this functionality while creating or editing a merge request: + +![Enable collaboration](./img/allow_collaboration.png) + +[ce-17395]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395 diff --git a/doc/user/project/merge_requests/img/allow_collaboration.png b/doc/user/project/merge_requests/img/allow_collaboration.png new file mode 100644 index 00000000000..75596e7d9ad Binary files /dev/null and b/doc/user/project/merge_requests/img/allow_collaboration.png differ diff --git a/doc/user/project/merge_requests/img/allow_maintainer_push.png b/doc/user/project/merge_requests/img/allow_maintainer_push.png deleted file mode 100644 index 91cc399f4ff..00000000000 Binary files a/doc/user/project/merge_requests/img/allow_maintainer_push.png and /dev/null differ diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index b75bcacc9d7..50979e82097 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -28,7 +28,7 @@ With GitLab merge requests, you can: - Enable [fast-forward merge requests](#fast-forward-merge-requests) - Enable [semi-linear history merge requests](#semi-linear-history-merge-requests) as another security layer to guarantee the pipeline is passing in the target branch - [Create new merge requests by email](#create-new-merge-requests-by-email) -- Allow maintainers of the target project to push directly to the fork by [allowing edits from maintainers](maintainer_access.md) +- [Allow collaboration](allow_collaboration.md) so members of the target project can push directly to the fork - [Squash and merge](squash_and_merge.md) for a cleaner commit history With **[GitLab Enterprise Edition][ee]**, you can also: diff --git a/doc/user/project/merge_requests/maintainer_access.md b/doc/user/project/merge_requests/maintainer_access.md index 89f71e16a50..d59afecd375 100644 --- a/doc/user/project/merge_requests/maintainer_access.md +++ b/doc/user/project/merge_requests/maintainer_access.md @@ -1,20 +1 @@ -# Allow maintainer pushes for merge requests across forks - -> [Introduced][ce-17395] in GitLab 10.6. - -This feature is available for merge requests across forked projects that are -publicly accessible. It makes it easier for maintainers of projects to -collaborate on merge requests across forks. - -When enabled for a merge request, members with merge access to the target -branch of the project will be granted write permissions to the source branch -of the merge request. - -The feature can only be enabled by users who already have push access to the -source project, and only lasts while the merge request is open. - -Enable this functionality while creating a merge request: - -![Enable maintainer edits](./img/allow_maintainer_push.png) - -[ce-17395]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395 +This document was moved to [another location](allow_collaboration.md). diff --git a/lib/api/entities.rb b/lib/api/entities.rb index c4537036a3a..c76d3ff45d0 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -559,7 +559,9 @@ module API expose :discussion_locked expose :should_remove_source_branch?, as: :should_remove_source_branch expose :force_remove_source_branch?, as: :force_remove_source_branch - expose :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? } + expose :allow_collaboration, if: -> (merge_request, _) { merge_request.for_fork? } + # Deprecated + expose :allow_collaboration, as: :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? } expose :web_url do |merge_request, options| Gitlab::UrlBuilder.build(merge_request) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index b1e510d72de..55e83303536 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -162,7 +162,8 @@ module API optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request' optional :labels, type: String, desc: 'Comma-separated list of label names' optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging' - optional :allow_maintainer_to_push, type: Boolean, desc: 'Whether a maintainer of the target project can push to the source project' + optional :allow_collaboration, type: Boolean, desc: 'Allow commits from members who can merge to the target branch' + optional :allow_maintainer_to_push, type: Boolean, as: :allow_collaboration, desc: '[deprecated] See allow_collaboration' optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge' use :optional_params_ee diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb index 8cf5d636743..27560abfb96 100644 --- a/lib/gitlab/user_access.rb +++ b/lib/gitlab/user_access.rb @@ -65,7 +65,7 @@ module Gitlab return false unless can_access_git? return false unless project - return false if !user.can?(:push_code, project) && !project.branch_allows_maintainer_push?(user, ref) + return false if !user.can?(:push_code, project) && !project.branch_allows_collaboration?(user, ref) if protected?(ProtectedBranch, project, ref) protected_branch_accessible_to?(ref, action: :push) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 035a2275d9f..8ae04cd2f88 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -331,7 +331,7 @@ msgstr "" msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings." msgstr "" -msgid "Allow edits from maintainers." +msgid "Allow commits from members who can merge to the target branch." msgstr "" msgid "Allow rendering of PlantUML diagrams in Asciidoc documents." @@ -4894,7 +4894,7 @@ msgstr "" msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB" msgstr "" -msgid "mrWidget|Allows edits from maintainers" +msgid "mrWidget|Allows commits from members who can merge to the target branch" msgstr "" msgid "mrWidget|Cancel automatic merge" diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb index a3323da1b1f..1808d0c0a0c 100644 --- a/spec/features/merge_request/maintainer_edits_fork_spec.rb +++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb @@ -14,7 +14,7 @@ describe 'a maintainer edits files on a source-branch of an MR from a fork', :js source_branch: 'fix', target_branch: 'master', author: author, - allow_maintainer_to_push: true) + allow_collaboration: true) end before do diff --git a/spec/features/merge_request/user_allows_a_maintainer_to_push_spec.rb b/spec/features/merge_request/user_allows_a_maintainer_to_push_spec.rb deleted file mode 100644 index eb41d7de8ed..00000000000 --- a/spec/features/merge_request/user_allows_a_maintainer_to_push_spec.rb +++ /dev/null @@ -1,85 +0,0 @@ -require 'spec_helper' - -describe 'create a merge request that allows maintainers to push', :js do - include ProjectForksHelper - let(:user) { create(:user) } - let(:target_project) { create(:project, :public, :repository) } - let(:source_project) { fork_project(target_project, user, repository: true, namespace: user.namespace) } - - def visit_new_merge_request - visit project_new_merge_request_path( - source_project, - merge_request: { - source_project_id: source_project.id, - target_project_id: target_project.id, - source_branch: 'fix', - target_branch: 'master' - }) - end - - before do - sign_in(user) - end - - it 'allows setting maintainer push possible' do - visit_new_merge_request - - check 'Allow edits from maintainers' - - click_button 'Submit merge request' - - wait_for_requests - - expect(page).to have_content('Allows edits from maintainers') - end - - it 'shows a message when one of the projects is private' do - source_project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) - - visit_new_merge_request - - expect(page).to have_content('Not available for private projects') - end - - it 'shows a message when the source branch is protected' do - create(:protected_branch, project: source_project, name: 'fix') - - visit_new_merge_request - - expect(page).to have_content('Not available for protected branches') - end - - context 'when the merge request is being created within the same project' do - let(:source_project) { target_project } - - it 'hides the checkbox if the merge request is being created within the same project' do - target_project.add_developer(user) - - visit_new_merge_request - - expect(page).not_to have_content('Allows edits from maintainers') - end - end - - context 'when a maintainer tries to edit the option' do - let(:maintainer) { create(:user) } - let(:merge_request) do - create(:merge_request, - source_project: source_project, - target_project: target_project, - source_branch: 'fixes') - end - - before do - target_project.add_master(maintainer) - - sign_in(maintainer) - end - - it 'it hides the option from maintainers' do - visit edit_project_merge_request_path(target_project, merge_request) - - expect(page).not_to have_content('Allows edits from maintainers') - end - end -end diff --git a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb new file mode 100644 index 00000000000..0af37d76539 --- /dev/null +++ b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +describe 'create a merge request, allowing commits from members who can merge to the target branch', :js do + include ProjectForksHelper + let(:user) { create(:user) } + let(:target_project) { create(:project, :public, :repository) } + let(:source_project) { fork_project(target_project, user, repository: true, namespace: user.namespace) } + + def visit_new_merge_request + visit project_new_merge_request_path( + source_project, + merge_request: { + source_project_id: source_project.id, + target_project_id: target_project.id, + source_branch: 'fix', + target_branch: 'master' + }) + end + + before do + sign_in(user) + end + + it 'allows setting possible' do + visit_new_merge_request + + check 'Allow commits from members who can merge to the target branch' + + click_button 'Submit merge request' + + wait_for_requests + + expect(page).to have_content('Allows commits from members who can merge to the target branch') + end + + it 'shows a message when one of the projects is private' do + source_project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + + visit_new_merge_request + + expect(page).to have_content('Not available for private projects') + end + + it 'shows a message when the source branch is protected' do + create(:protected_branch, project: source_project, name: 'fix') + + visit_new_merge_request + + expect(page).to have_content('Not available for protected branches') + end + + context 'when the merge request is being created within the same project' do + let(:source_project) { target_project } + + it 'hides the checkbox if the merge request is being created within the same project' do + target_project.add_developer(user) + + visit_new_merge_request + + expect(page).not_to have_content('Allows commits from members who can merge to the target branch') + end + end + + context 'when a member who can merge tries to edit the option' do + let(:member) { create(:user) } + let(:merge_request) do + create(:merge_request, + source_project: source_project, + target_project: target_project, + source_branch: 'fixes') + end + + before do + target_project.add_master(member) + + sign_in(member) + end + + it 'it hides the option from members' do + visit edit_project_merge_request_path(target_project, merge_request) + + expect(page).not_to have_content('Allows commits from members who can merge to the target branch') + end + end +end diff --git a/spec/fixtures/api/schemas/entities/merge_request_basic.json b/spec/fixtures/api/schemas/entities/merge_request_basic.json index 46031961cca..f7bc137c90c 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_basic.json +++ b/spec/fixtures/api/schemas/entities/merge_request_basic.json @@ -13,6 +13,7 @@ "assignee_id": { "type": ["integer", "null"] }, "subscribed": { "type": ["boolean", "null"] }, "participants": { "type": "array" }, + "allow_collaboration": { "type": "boolean"}, "allow_maintainer_to_push": { "type": "boolean"} }, "additionalProperties": false diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index 7be8c9e3e67..ee5588fa6c6 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -31,7 +31,7 @@ "source_project_id": { "type": "integer" }, "target_branch": { "type": "string" }, "target_project_id": { "type": "integer" }, - "allow_maintainer_to_push": { "type": "boolean"}, + "allow_collaboration": { "type": "boolean"}, "metrics": { "oneOf": [ { "type": "null" }, diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json index f97461ce9cc..f7adc4e0b91 100644 --- a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json +++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json @@ -82,6 +82,7 @@ "human_time_estimate": { "type": ["string", "null"] }, "human_total_time_spent": { "type": ["string", "null"] } }, + "allow_collaboration": { "type": ["boolean", "null"] }, "allow_maintainer_to_push": { "type": ["boolean", "null"] } }, "required": [ diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 74e7a45fd6c..d389dcb4e80 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -170,7 +170,7 @@ MergeRequest: - last_edited_by_id - head_pipeline_id - discussion_locked -- allow_maintainer_to_push +- allow_collaboration MergeRequestDiff: - id - state diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb index 97b6069f64d..0469d984a40 100644 --- a/spec/lib/gitlab/user_access_spec.rb +++ b/spec/lib/gitlab/user_access_spec.rb @@ -142,7 +142,7 @@ describe Gitlab::UserAccess do target_project: canonical_project, source_project: project, source_branch: 'awesome-feature', - allow_maintainer_to_push: true + allow_collaboration: true ) end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 9ffa91fc265..8718fe50769 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -2236,25 +2236,25 @@ describe MergeRequest do end end - describe '#allow_maintainer_to_push' do + describe '#allow_collaboration' do let(:merge_request) do - build(:merge_request, source_branch: 'fixes', allow_maintainer_to_push: true) + build(:merge_request, source_branch: 'fixes', allow_collaboration: true) end it 'is false when pushing by a maintainer is not possible' do - expect(merge_request).to receive(:maintainer_push_possible?) { false } + expect(merge_request).to receive(:collaborative_push_possible?) { false } - expect(merge_request.allow_maintainer_to_push).to be_falsy + expect(merge_request.allow_collaboration).to be_falsy end it 'is true when pushing by a maintainer is possible' do - expect(merge_request).to receive(:maintainer_push_possible?) { true } + expect(merge_request).to receive(:collaborative_push_possible?) { true } - expect(merge_request.allow_maintainer_to_push).to be_truthy + expect(merge_request.allow_collaboration).to be_truthy end end - describe '#maintainer_push_possible?' do + describe '#collaborative_push_possible?' do let(:merge_request) do build(:merge_request, source_branch: 'fixes') end @@ -2266,14 +2266,14 @@ describe MergeRequest do it 'does not allow maintainer to push if the source project is the same as the target' do merge_request.target_project = merge_request.source_project = create(:project, :public) - expect(merge_request.maintainer_push_possible?).to be_falsy + expect(merge_request.collaborative_push_possible?).to be_falsy end it 'allows maintainer to push when both source and target are public' do merge_request.target_project = build(:project, :public) merge_request.source_project = build(:project, :public) - expect(merge_request.maintainer_push_possible?).to be_truthy + expect(merge_request.collaborative_push_possible?).to be_truthy end it 'is not available for protected branches' do @@ -2284,11 +2284,11 @@ describe MergeRequest do .with(merge_request.source_project, 'fixes') .and_return(true) - expect(merge_request.maintainer_push_possible?).to be_falsy + expect(merge_request.collaborative_push_possible?).to be_falsy end end - describe '#can_allow_maintainer_to_push?' do + describe '#can_allow_collaboration?' do let(:target_project) { create(:project, :public) } let(:source_project) { fork_project(target_project) } let(:merge_request) do @@ -2300,17 +2300,17 @@ describe MergeRequest do let(:user) { create(:user) } before do - allow(merge_request).to receive(:maintainer_push_possible?) { true } + allow(merge_request).to receive(:collaborative_push_possible?) { true } end it 'is false if the user does not have push access to the source project' do - expect(merge_request.can_allow_maintainer_to_push?(user)).to be_falsy + expect(merge_request.can_allow_collaboration?(user)).to be_falsy end it 'is true when the user has push access to the source project' do source_project.add_developer(user) - expect(merge_request.can_allow_maintainer_to_push?(user)).to be_truthy + expect(merge_request.can_allow_collaboration?(user)).to be_truthy end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index af2240f4f89..b0cbf8796e3 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3583,7 +3583,7 @@ describe Project do target_branch: 'target-branch', source_project: project, source_branch: 'awesome-feature-1', - allow_maintainer_to_push: true + allow_collaboration: true ) end @@ -3620,9 +3620,9 @@ describe Project do end end - describe '#branch_allows_maintainer_push?' do + describe '#branch_allows_collaboration_push?' do it 'allows access if the user can merge the merge request' do - expect(project.branch_allows_maintainer_push?(user, 'awesome-feature-1')) + expect(project.branch_allows_collaboration?(user, 'awesome-feature-1')) .to be_truthy end @@ -3630,7 +3630,7 @@ describe Project do guest = create(:user) target_project.add_guest(guest) - expect(project.branch_allows_maintainer_push?(guest, 'awesome-feature-1')) + expect(project.branch_allows_collaboration?(guest, 'awesome-feature-1')) .to be_falsy end @@ -3640,31 +3640,31 @@ describe Project do target_branch: 'target-branch', source_project: project, source_branch: 'rejected-feature-1', - allow_maintainer_to_push: true) + allow_collaboration: true) - expect(project.branch_allows_maintainer_push?(user, 'rejected-feature-1')) + expect(project.branch_allows_collaboration?(user, 'rejected-feature-1')) .to be_falsy end it 'does not allow access if the user cannot merge the merge request' do create(:protected_branch, :masters_can_push, project: target_project, name: 'target-branch') - expect(project.branch_allows_maintainer_push?(user, 'awesome-feature-1')) + expect(project.branch_allows_collaboration?(user, 'awesome-feature-1')) .to be_falsy end it 'caches the result' do - control = ActiveRecord::QueryRecorder.new { project.branch_allows_maintainer_push?(user, 'awesome-feature-1') } + control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') } - expect { 3.times { project.branch_allows_maintainer_push?(user, 'awesome-feature-1') } } + expect { 3.times { project.branch_allows_collaboration?(user, 'awesome-feature-1') } } .not_to exceed_query_limit(control) end context 'when the requeststore is active', :request_store do it 'only queries per project across instances' do - control = ActiveRecord::QueryRecorder.new { project.branch_allows_maintainer_push?(user, 'awesome-feature-1') } + control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') } - expect { 2.times { described_class.find(project.id).branch_allows_maintainer_push?(user, 'awesome-feature-1') } } + expect { 2.times { described_class.find(project.id).branch_allows_collaboration?(user, 'awesome-feature-1') } } .not_to exceed_query_limit(control).with_threshold(2) end end diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb index 9ca156deaa0..eead55d33ca 100644 --- a/spec/policies/ci/build_policy_spec.rb +++ b/spec/policies/ci/build_policy_spec.rb @@ -101,7 +101,7 @@ describe Ci::BuildPolicy do it 'enables update_build if user is maintainer' do allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false) - allow_any_instance_of(Project).to receive(:branch_allows_maintainer_push?).and_return(true) + allow_any_instance_of(Project).to receive(:branch_allows_collaboration?).and_return(true) expect(policy).to be_allowed :update_build expect(policy).to be_allowed :update_commit_status diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb index a5e509cfa0f..bd32faf06ef 100644 --- a/spec/policies/ci/pipeline_policy_spec.rb +++ b/spec/policies/ci/pipeline_policy_spec.rb @@ -69,7 +69,7 @@ describe Ci::PipelinePolicy, :models do it 'enables update_pipeline if user is maintainer' do allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false) - allow_any_instance_of(Project).to receive(:branch_allows_maintainer_push?).and_return(true) + allow_any_instance_of(Project).to receive(:branch_allows_collaboration?).and_return(true) expect(policy).to be_allowed :update_pipeline end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 6609f5f7afd..6ac151f92f3 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -400,7 +400,7 @@ describe ProjectPolicy do :merge_request, target_project: target_project, source_project: project, - allow_maintainer_to_push: true + allow_collaboration: true ) end let(:maintainer_abilities) do diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 605761867bf..d4ebfc3f782 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -386,12 +386,13 @@ describe API::MergeRequests do source_project: forked_project, target_project: project, source_branch: 'fixes', - allow_maintainer_to_push: true) + allow_collaboration: true) end - it 'includes the `allow_maintainer_to_push` field' do + it 'includes the `allow_collaboration` field' do get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user) + expect(json_response['allow_collaboration']).to be_truthy expect(json_response['allow_maintainer_to_push']).to be_truthy end end @@ -654,11 +655,12 @@ describe API::MergeRequests do expect(response).to have_gitlab_http_status(400) end - it 'allows setting `allow_maintainer_to_push`' do + it 'allows setting `allow_collaboration`' do post api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", - author: user2, target_project_id: project.id, allow_maintainer_to_push: true + title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", + author: user2, target_project_id: project.id, allow_collaboration: true expect(response).to have_gitlab_http_status(201) + expect(json_response['allow_collaboration']).to be_truthy expect(json_response['allow_maintainer_to_push']).to be_truthy end diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb index a73bd7a0268..688d3b8c038 100644 --- a/spec/services/ci/retry_pipeline_service_spec.rb +++ b/spec/services/ci/retry_pipeline_service_spec.rb @@ -280,12 +280,12 @@ describe Ci::RetryPipelineService, '#execute' do source_project: forked_project, target_project: project, source_branch: 'fixes', - allow_maintainer_to_push: true) + allow_collaboration: true) create_build('rspec 1', :failed, 1) end it 'allows to retry failed pipeline' do - allow_any_instance_of(Project).to receive(:fetch_branch_allows_maintainer_push?).and_return(true) + allow_any_instance_of(Project).to receive(:fetch_branch_allows_collaboration?).and_return(true) allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false) service.execute(pipeline) diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 5279ea6164e..cc26df725c7 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -541,7 +541,7 @@ describe MergeRequests::UpdateService, :mailer do let(:closed_issuable) { create(:closed_merge_request, source_project: project) } end - context 'setting `allow_maintainer_to_push`' do + context 'setting `allow_collaboration`' do let(:target_project) { create(:project, :public) } let(:source_project) { fork_project(target_project) } let(:user) { create(:user) } @@ -556,23 +556,23 @@ describe MergeRequests::UpdateService, :mailer do allow(ProtectedBranch).to receive(:protected?).with(source_project, 'fixes') { false } end - it 'does not allow a maintainer of the target project to set `allow_maintainer_to_push`' do + it 'does not allow a maintainer of the target project to set `allow_collaboration`' do target_project.add_developer(user) - update_merge_request(allow_maintainer_to_push: true, title: 'Updated title') + update_merge_request(allow_collaboration: true, title: 'Updated title') expect(merge_request.title).to eq('Updated title') - expect(merge_request.allow_maintainer_to_push).to be_falsy + expect(merge_request.allow_collaboration).to be_falsy end it 'is allowed by a user that can push to the source and can update the merge request' do merge_request.update!(assignee: user) source_project.add_developer(user) - update_merge_request(allow_maintainer_to_push: true, title: 'Updated title') + update_merge_request(allow_collaboration: true, title: 'Updated title') expect(merge_request.title).to eq('Updated title') - expect(merge_request.allow_maintainer_to_push).to be_truthy + expect(merge_request.allow_collaboration).to be_truthy end end end -- cgit v1.2.1 From 4beeb60255f228dc45dbe8f675a3cc59c0ea7773 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 1 Jun 2018 14:36:52 +0900 Subject: Fix populate_spec --- spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index bcfa9f0c282..feed7728f5a 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -75,7 +75,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do it 'wastes pipeline iid' do expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0 - end + end end context 'when pipeline has validation errors' do @@ -98,7 +98,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do it 'wastes pipeline iid' do expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0 - end + end end context 'when there is a seed blocks present' do @@ -144,7 +144,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do step.perform rescue nil expect(InternalId.ci_pipelines.where(project_id: project.id).exists?).to be_falsy - end + end end end -- cgit v1.2.1 From f7f60ab54ab69fb4d0c3a43406a9809edab7d762 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 1 Jun 2018 14:53:00 +0900 Subject: Add spec for variables expressions with pipeline iid --- spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb | 45 +++++++++++++++------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index feed7728f5a..6b18c615430 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -156,22 +156,41 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do end end - context 'when using only/except build policies' do - let(:config) do - { rspec: { script: 'rspec', stage: 'test', only: ['master'] }, - prod: { script: 'cap prod', stage: 'deploy', only: ['tags'] } } - end + context 'when variables policy is specified' do + context 'when using only/except build policies' do + let(:config) do + { rspec: { script: 'rspec', stage: 'test', only: ['master'] }, + prod: { script: 'cap prod', stage: 'deploy', only: ['tags'] } } + end - let(:pipeline) do - build(:ci_pipeline, ref: 'master', config: config) - end + let(:pipeline) do + build(:ci_pipeline, ref: 'master', config: config) + end - it 'populates pipeline according to used policies' do - step.perform! + it 'populates pipeline according to used policies' do + step.perform! - expect(pipeline.stages.size).to eq 1 - expect(pipeline.stages.first.builds.size).to eq 1 - expect(pipeline.stages.first.builds.first.name).to eq 'rspec' + expect(pipeline.stages.size).to eq 1 + expect(pipeline.stages.first.builds.size).to eq 1 + expect(pipeline.stages.first.builds.first.name).to eq 'rspec' + end + + context 'when variables expression is specified' do + let(:config) do + { rspec: { script: 'rspec', only: { variables: ["$CI_PIPELINE_IID == '1'"] } }, + prod: { script: 'cap prod', only: { variables: ["$CI_PIPELINE_IID == '1000'"] } } } + end + + context 'when pipeline iid is the subject' do + it 'populates pipeline according to used policies' do + step.perform! + + expect(pipeline.stages.size).to eq 1 + expect(pipeline.stages.first.builds.size).to eq 1 + expect(pipeline.stages.first.builds.first.name).to eq 'rspec' + end + end + end end end end -- cgit v1.2.1 From c754b6937c8077304386b3a6b37233e52eacdb3e Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 1 Jun 2018 15:37:36 +0900 Subject: Clean up presence validation spec --- spec/models/ci/pipeline_spec.rb | 3 +-- .../models/atomic_internal_id_spec.rb | 28 +++++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 7d28f2eb86b..e03c068b88e 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -36,13 +36,12 @@ describe Ci::Pipeline, :mailer do end describe 'modules' do - it_behaves_like 'AtomicInternalId' do + it_behaves_like 'AtomicInternalId', validate_presence: false do let(:internal_id_attribute) { :iid } let(:instance) { build(:ci_pipeline) } let(:scope) { :project } let(:scope_attrs) { { project: instance.project } } let(:usage) { :ci_pipelines } - let(:allow_nil) { true } end end diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb index a05279364f2..d0cd8da67e1 100644 --- a/spec/support/shared_examples/models/atomic_internal_id_spec.rb +++ b/spec/support/shared_examples/models/atomic_internal_id_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' -shared_examples_for 'AtomicInternalId' do - let(:allow_nil) { false } - +shared_examples_for 'AtomicInternalId' do |validate_presence: true| describe '.has_internal_id' do describe 'Module inclusion' do subject { described_class } @@ -12,18 +10,30 @@ shared_examples_for 'AtomicInternalId' do describe 'Validation' do before do - allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!") {} - end + allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!") - it 'validates presence' do instance.valid? + end - if allow_nil - expect(instance.errors[internal_id_attribute]).to be_empty - else + context 'when presence validattion is required' do + before do + skip unless validate_presence + end + + it 'validates presence' do expect(instance.errors[internal_id_attribute]).to include("can't be blank") end end + + context 'when presence validattion is not required' do + before do + skip if validate_presence + end + + it 'does not validate presence' do + expect(instance.errors[internal_id_attribute]).to be_empty + end + end end describe 'Creating an instance' do -- cgit v1.2.1 From c418d68765eb09c468419ec8f438100cda64a0d4 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 1 Jun 2018 15:41:33 +0900 Subject: Remove unneccesary spec --- spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb | 2 +- spec/models/ci/pipeline_spec.rb | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index 6b18c615430..ffb2c1d5b0c 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -188,7 +188,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do expect(pipeline.stages.size).to eq 1 expect(pipeline.stages.first.builds.size).to eq 1 expect(pipeline.stages.first.builds.first.name).to eq 'rspec' - end + end end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index e03c068b88e..2b9c232743d 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -396,20 +396,6 @@ describe Ci::Pipeline, :mailer do expect(seeds.size).to eq 1 expect(seeds.dig(0, 0, :name)).to eq 'unit' end - - context "when pipeline iid is used for 'only' keyword" do - let(:config) do - { rspec: { script: 'rspec', only: { variables: ['$CI_PIPELINE_IID == 2'] } }, - prod: { script: 'cap prod', only: { variables: ['$CI_PIPELINE_IID == 1'] } } } - end - - it 'returns stage seeds only when variables expression is truthy' do - seeds = pipeline.stage_seeds - - expect(seeds.size).to eq 1 - expect(seeds.dig(0, 0, :name)).to eq 'prod' - end - end end end -- cgit v1.2.1 From c89e57842ebf7f395363bcddaeff76bc7b3f7890 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 1 Jun 2018 15:46:15 +0900 Subject: Use shared examples for populate spec --- spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb | 34 ++++++++++------------ .../models/atomic_internal_id_spec.rb | 4 +-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index ffb2c1d5b0c..7088233f237 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -157,6 +157,16 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do end context 'when variables policy is specified' do + shared_examples_for 'populates pipeline according to used policies' do + it 'populates pipeline according to used policies' do + step.perform! + + expect(pipeline.stages.size).to eq 1 + expect(pipeline.stages.first.builds.size).to eq 1 + expect(pipeline.stages.first.builds.first.name).to eq 'rspec' + end + end + context 'when using only/except build policies' do let(:config) do { rspec: { script: 'rspec', stage: 'test', only: ['master'] }, @@ -167,28 +177,16 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do build(:ci_pipeline, ref: 'master', config: config) end - it 'populates pipeline according to used policies' do - step.perform! - - expect(pipeline.stages.size).to eq 1 - expect(pipeline.stages.first.builds.size).to eq 1 - expect(pipeline.stages.first.builds.first.name).to eq 'rspec' - end + it_behaves_like 'populates pipeline according to used policies' context 'when variables expression is specified' do - let(:config) do - { rspec: { script: 'rspec', only: { variables: ["$CI_PIPELINE_IID == '1'"] } }, - prod: { script: 'cap prod', only: { variables: ["$CI_PIPELINE_IID == '1000'"] } } } - end - context 'when pipeline iid is the subject' do - it 'populates pipeline according to used policies' do - step.perform! - - expect(pipeline.stages.size).to eq 1 - expect(pipeline.stages.first.builds.size).to eq 1 - expect(pipeline.stages.first.builds.first.name).to eq 'rspec' + let(:config) do + { rspec: { script: 'rspec', only: { variables: ["$CI_PIPELINE_IID == '1'"] } }, + prod: { script: 'cap prod', only: { variables: ["$CI_PIPELINE_IID == '1000'"] } } } end + + it_behaves_like 'populates pipeline according to used policies' end end end diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb index d0cd8da67e1..7ab1041d17c 100644 --- a/spec/support/shared_examples/models/atomic_internal_id_spec.rb +++ b/spec/support/shared_examples/models/atomic_internal_id_spec.rb @@ -15,7 +15,7 @@ shared_examples_for 'AtomicInternalId' do |validate_presence: true| instance.valid? end - context 'when presence validattion is required' do + context 'when presence validation is required' do before do skip unless validate_presence end @@ -25,7 +25,7 @@ shared_examples_for 'AtomicInternalId' do |validate_presence: true| end end - context 'when presence validattion is not required' do + context 'when presence validation is not required' do before do skip if validate_presence end -- cgit v1.2.1 From 9b2e19fe37a5d1389e9f83531bb6ba4b06a66de0 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Thu, 24 May 2018 08:55:47 +0200 Subject: Adds variables to POST api/v4/projects/:id/pipeline --- .../25045-add-variables-to-post-pipeline-api.yml | 5 +++++ doc/api/pipelines.md | 4 +++- lib/api/entities.rb | 1 + lib/api/pipelines.rb | 1 + spec/requests/api/pipelines_spec.rb | 17 ++++++++++++++++- 5 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/25045-add-variables-to-post-pipeline-api.yml diff --git a/changelogs/unreleased/25045-add-variables-to-post-pipeline-api.yml b/changelogs/unreleased/25045-add-variables-to-post-pipeline-api.yml new file mode 100644 index 00000000000..1e648b75248 --- /dev/null +++ b/changelogs/unreleased/25045-add-variables-to-post-pipeline-api.yml @@ -0,0 +1,5 @@ +--- +title: Add variables to POST api/v4/projects/:id/pipeline +merge_request: 19124 +author: Jacopo Beschi @jacopo-beschi +type: added diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index 899f5da6647..bc96b3a0bdf 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -102,6 +102,7 @@ POST /projects/:id/pipeline |------------|---------|----------|---------------------| | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | | `ref` | string | yes | Reference to commit | +| `variables_attributes` | array | no | An array containing the variables available in the pipeline matching the structure [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] | ``` curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipeline?ref=master" @@ -132,7 +133,8 @@ Example of response "finished_at": null, "committed_at": null, "duration": null, - "coverage": null + "coverage": null, + "variables": [] } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index c4537036a3a..8773e2c029a 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1067,6 +1067,7 @@ module API expose :created_at, :updated_at, :started_at, :finished_at, :committed_at expose :duration expose :coverage + expose :variables, using: Entities::Variable end class PipelineSchedule < Grape::Entity diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 735591fedd5..85e073ce662 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -41,6 +41,7 @@ module API end params do requires :ref, type: String, desc: 'Reference' + optional :variables_attributes, Array, desc: 'Array of variables available in the pipeline' end post ':id/pipeline' do Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42124') diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 0736329f9fd..f20c2275152 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -294,13 +294,28 @@ describe API::Pipelines do it 'creates and returns a new pipeline' do expect do post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch - end.to change { Ci::Pipeline.count }.by(1) + end.to change { project.pipelines.count }.by(1) expect(response).to have_gitlab_http_status(201) expect(json_response).to be_a Hash expect(json_response['sha']).to eq project.commit.id end + context 'variables given' do + let(:variables_attributes) { [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] } + + it 'creates and returns a new pipeline using the given variables' do + expect do + post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch, variables_attributes: variables_attributes + end.to change { project.pipelines.count }.by(1) + + expect(response).to have_gitlab_http_status(201) + expect(json_response).to be_a Hash + expect(json_response['sha']).to eq project.commit.id + expect(json_response['variables']).to eq variables_attributes + end + end + it 'fails when using an invalid ref' do post api("/projects/#{project.id}/pipeline", user), ref: 'invalid_ref' -- cgit v1.2.1 From 6ae16b6d4d9fb79b715875073bb78efd3f56929b Mon Sep 17 00:00:00 2001 From: Jacopo Date: Thu, 31 May 2018 09:47:53 +0200 Subject: Rename variables_attributes => variables and adds spec for exclude/only option --- doc/api/pipelines.md | 2 +- lib/api/pipelines.rb | 4 ++-- spec/requests/api/pipelines_spec.rb | 21 ++++++++++++++++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index bc96b3a0bdf..0e752056642 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -102,7 +102,7 @@ POST /projects/:id/pipeline |------------|---------|----------|---------------------| | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | | `ref` | string | yes | Reference to commit | -| `variables_attributes` | array | no | An array containing the variables available in the pipeline matching the structure [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] | +| `variables` | array | no | An array containing the variables available in the pipeline matching the structure [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] | ``` curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipeline?ref=master" diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 85e073ce662..378d9585eb8 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -41,7 +41,7 @@ module API end params do requires :ref, type: String, desc: 'Reference' - optional :variables_attributes, Array, desc: 'Array of variables available in the pipeline' + optional :variables, Array, desc: 'Array of variables available in the pipeline' end post ':id/pipeline' do Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42124') @@ -50,7 +50,7 @@ module API new_pipeline = Ci::CreatePipelineService.new(user_project, current_user, - declared_params(include_missing: false)) + declared_params(include_missing: false).merge(variables_attributes: params[:variables])) .execute(:api, ignore_skip_ci: true, save_on_errors: false) if new_pipeline.persisted? diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index f20c2275152..0c6bb56e11d 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -302,17 +302,32 @@ describe API::Pipelines do end context 'variables given' do - let(:variables_attributes) { [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] } + let(:variables) { [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] } it 'creates and returns a new pipeline using the given variables' do expect do - post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch, variables_attributes: variables_attributes + post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch, variables: variables end.to change { project.pipelines.count }.by(1) expect(response).to have_gitlab_http_status(201) expect(json_response).to be_a Hash expect(json_response['sha']).to eq project.commit.id - expect(json_response['variables']).to eq variables_attributes + expect(json_response['variables']).to eq variables + end + end + + context 'when excluding a ref' do + before do + config = YAML.dump(test: { script: 'test', except: [project.default_branch] }) + stub_ci_pipeline_yaml_file(config) + end + + it "doesn't not create a job for the exluded ref" do + expect do + post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch + end.not_to change { project.pipelines.count } + + expect(response).to have_gitlab_http_status(400) end end -- cgit v1.2.1 From 60c4f2840ed632ea2ae154d42992e84357d238c9 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 1 Jun 2018 15:19:46 +0100 Subject: Update to GitLab Workhorse v4.3.0 --- GITLAB_WORKHORSE_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index fae6e3d04b2..80895903a15 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -4.2.1 +4.3.0 -- cgit v1.2.1 From e5dddae1c597a47bb476df476c445f928e279183 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Jun 2018 14:40:52 +0000 Subject: Fixed typo in commit message help popover Closes #47060 --- app/assets/javascripts/ide/components/commit_sidebar/message_field.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue index f14fcdc88ed..0ac0af2feaa 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue @@ -54,7 +54,7 @@ export default { placement: 'top', content: sprintf( __(` - The character highligher helps you keep the subject line to %{titleLength} characters + The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git. `), { titleLength: MAX_TITLE_LENGTH, bodyLength: MAX_BODY_LENGTH }, -- cgit v1.2.1 From 3871ea33ca524ef2d420a4ff311d8535622bd150 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Fri, 1 Jun 2018 15:52:24 +0200 Subject: Review 1 --- doc/api/pipelines.md | 2 +- lib/api/pipelines.rb | 6 +++++- spec/requests/api/pipelines_spec.rb | 29 +++++++++++++++++++++++------ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index 0e752056642..ae6c6ea7b49 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -102,7 +102,7 @@ POST /projects/:id/pipeline |------------|---------|----------|---------------------| | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | | `ref` | string | yes | Reference to commit | -| `variables` | array | no | An array containing the variables available in the pipeline matching the structure [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] | +| `variables` | array | no | An array containing the variables available in the pipeline, matching the structure [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] | ``` curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipeline?ref=master" diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 378d9585eb8..8374a57edfa 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -48,9 +48,13 @@ module API authorize! :create_pipeline, user_project + pipeline_params = declared_params(include_missing: false) + .merge(variables_attributes: params[:variables]) + .except(:variables) + new_pipeline = Ci::CreatePipelineService.new(user_project, current_user, - declared_params(include_missing: false).merge(variables_attributes: params[:variables])) + pipeline_params) .execute(:api, ignore_skip_ci: true, save_on_errors: false) if new_pipeline.persisted? diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 0c6bb56e11d..0a64c46bb92 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -316,18 +316,35 @@ describe API::Pipelines do end end - context 'when excluding a ref' do + describe 'using variables conditions' do + let(:variables) { [{ 'key' => 'STAGING', 'value' => 'true' }] } + before do - config = YAML.dump(test: { script: 'test', except: [project.default_branch] }) + config = YAML.dump(test: { script: 'test', only: { variables: ['$STAGING'] } }) stub_ci_pipeline_yaml_file(config) end - it "doesn't not create a job for the exluded ref" do + it 'creates and returns a new pipeline using the given variables' do expect do - post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch - end.not_to change { project.pipelines.count } + post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch, variables: variables + end.to change { project.pipelines.count }.by(1) - expect(response).to have_gitlab_http_status(400) + expect(response).to have_gitlab_http_status(201) + expect(json_response).to be_a Hash + expect(json_response['sha']).to eq project.commit.id + expect(json_response['variables']).to eq variables + end + + context 'condition unmatch' do + let(:variables) { [{ 'key' => 'STAGING', 'value' => 'false' }] } + + it "doesn't create a job" do + expect do + post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch + end.not_to change { project.pipelines.count } + + expect(response).to have_gitlab_http_status(400) + end end end -- cgit v1.2.1 From afcc0784eac952d15210a798e5b57f0e7eb82eee Mon Sep 17 00:00:00 2001 From: Olivier Gonzalez Date: Fri, 1 Jun 2018 11:10:59 -0400 Subject: Update security products job and artifact names in documentation. Refs gitlab-org/gitlab-ee#6184 --- doc/topics/autodevops/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index efec365042a..1400b2e36fe 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -497,10 +497,10 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac | `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments). | | `INCREMENTAL_ROLLOUT_ENABLED`| From GitLab 10.8, this variable can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment. | | `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. | -| `CODEQUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. | +| `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `code_quality` job. If the variable is present, the job will not be created. | | `SAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast` job. If the variable is present, the job will not be created. | | `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dependency_scanning` job. If the variable is present, the job will not be created. | -| `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast:container` job. If the variable is present, the job will not be created. | +| `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `container_scanning` job. If the variable is present, the job will not be created. | | `REVIEW_DISABLED` | From GitLab 11.0, this variable can be used to disable the `review` and the manual `review:stop` job. If the variable is present, these jobs will not be created. | | `DAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dast` job. If the variable is present, the job will not be created. | | `PERFORMANCE_DISABLED` | From GitLab 11.0, this variable can be used to disable the `performance` job. If the variable is present, the job will not be created. | -- cgit v1.2.1 From 458b5a10ddb4432f1abd5cb0e1b92fc591f4e966 Mon Sep 17 00:00:00 2001 From: Chantal Rollison Date: Thu, 31 May 2018 07:37:00 -0700 Subject: Backport of 5942-extract-ee-specific-files --- app/controllers/concerns/issuable_actions.rb | 30 ++++++++++++++++------------ 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index c925b4aada5..d04eb192129 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -7,6 +7,19 @@ module IssuableActions before_action :authorize_admin_issuable!, only: :bulk_update end + def permitted_keys + [ + :issuable_ids, + :assignee_id, + :milestone_id, + :state_event, + :subscription_event, + label_ids: [], + add_label_ids: [], + remove_label_ids: [] + ] + end + def show respond_to do |format| format.html @@ -140,24 +153,15 @@ module IssuableActions end def bulk_update_params - permitted_keys = [ - :issuable_ids, - :assignee_id, - :milestone_id, - :state_event, - :subscription_event, - label_ids: [], - add_label_ids: [], - remove_label_ids: [] - ] + permitted_keys_array = permitted_keys.dup if resource_name == 'issue' - permitted_keys << { assignee_ids: [] } + permitted_keys_array << { assignee_ids: [] } else - permitted_keys.unshift(:assignee_id) + permitted_keys_array.unshift(:assignee_id) end - params.require(:update).permit(permitted_keys) + params.require(:update).permit(permitted_keys_array) end def resource_name -- cgit v1.2.1 From 43e08b1a38abdb552e4f3d4feee9a898b90d8db2 Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 29 May 2018 16:36:11 -0500 Subject: Backport scroll utilities for the job log component --- app/assets/javascripts/job.js | 82 +++------------------- .../javascripts/lib/utils/logoutput_behaviours.js | 46 ++++++++++++ app/assets/javascripts/lib/utils/scroll_utils.js | 26 +++++++ 3 files changed, 81 insertions(+), 73 deletions(-) create mode 100644 app/assets/javascripts/lib/utils/logoutput_behaviours.js create mode 100644 app/assets/javascripts/lib/utils/scroll_utils.js diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index 8dfe8aae5b5..83c38173a1c 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -6,9 +6,12 @@ import { visitUrl } from './lib/utils/url_utility'; import bp from './breakpoints'; import { numberToHumanSize } from './lib/utils/number_utils'; import { setCiStatusFavicon } from './lib/utils/common_utils'; +import { isScrolledToBottom, scrollDown } from './lib/utils/scroll_utils'; +import LogOutputBehaviours from './lib/utils/logoutput_behaviours'; -export default class Job { +export default class Job extends LogOutputBehaviours { constructor(options) { + super(); this.timeout = null; this.state = null; this.fetchingStatusFavicon = false; @@ -48,23 +51,14 @@ export default class Job { .off('click', '.stage-item') .on('click', '.stage-item', this.updateDropdown); - // add event listeners to the scroll buttons - this.$scrollTopBtn - .off('click') - .on('click', this.scrollToTop.bind(this)); - - this.$scrollBottomBtn - .off('click') - .on('click', this.scrollToBottom.bind(this)); - this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100); this.$window .off('scroll') .on('scroll', () => { - if (!this.isScrolledToBottom()) { + if (!isScrolledToBottom()) { this.toggleScrollAnimation(false); - } else if (this.isScrolledToBottom() && !this.isLogComplete) { + } else if (isScrolledToBottom() && !this.isLogComplete) { this.toggleScrollAnimation(true); } this.scrollThrottled(); @@ -83,60 +77,8 @@ export default class Job { StickyFill.add(this.$topBar); } - // eslint-disable-next-line class-methods-use-this - canScroll() { - return $(document).height() > $(window).height(); - } - - toggleScroll() { - const $document = $(document); - const currentPosition = $document.scrollTop(); - const scrollHeight = $document.height(); - - const windowHeight = $(window).height(); - if (this.canScroll()) { - if (currentPosition > 0 && - (scrollHeight - currentPosition !== windowHeight)) { - // User is in the middle of the log - - this.toggleDisableButton(this.$scrollTopBtn, false); - this.toggleDisableButton(this.$scrollBottomBtn, false); - } else if (currentPosition === 0) { - // User is at Top of Log - - this.toggleDisableButton(this.$scrollTopBtn, true); - this.toggleDisableButton(this.$scrollBottomBtn, false); - } else if (this.isScrolledToBottom()) { - // User is at the bottom of the build log. - - this.toggleDisableButton(this.$scrollTopBtn, false); - this.toggleDisableButton(this.$scrollBottomBtn, true); - } - } else { - this.toggleDisableButton(this.$scrollTopBtn, true); - this.toggleDisableButton(this.$scrollBottomBtn, true); - } - } - // eslint-disable-next-line class-methods-use-this - isScrolledToBottom() { - const $document = $(document); - - const currentPosition = $document.scrollTop(); - const scrollHeight = $document.height(); - - const windowHeight = $(window).height(); - - return scrollHeight - currentPosition === windowHeight; - } - - // eslint-disable-next-line class-methods-use-this - scrollDown() { - const $document = $(document); - $document.scrollTop($document.height()); - } - scrollToBottom() { - this.scrollDown(); + scrollDown(); this.hasBeenScrolled = true; this.toggleScroll(); } @@ -147,12 +89,6 @@ export default class Job { this.toggleScroll(); } - // eslint-disable-next-line class-methods-use-this - toggleDisableButton($button, disable) { - if (disable && $button.prop('disabled')) return; - $button.prop('disabled', disable); - } - toggleScrollAnimation(toggle) { this.$scrollBottomBtn.toggleClass('animate', toggle); } @@ -184,7 +120,7 @@ export default class Job { this.state = log.state; } - this.isScrollInBottom = this.isScrolledToBottom(); + this.isScrollInBottom = isScrolledToBottom(); if (log.append) { this.$buildTraceOutput.append(log.html); @@ -224,7 +160,7 @@ export default class Job { }) .then(() => { if (this.isScrollInBottom) { - this.scrollDown(); + scrollDown(); } }) .then(() => this.toggleScroll()); diff --git a/app/assets/javascripts/lib/utils/logoutput_behaviours.js b/app/assets/javascripts/lib/utils/logoutput_behaviours.js new file mode 100644 index 00000000000..1bf99d935ef --- /dev/null +++ b/app/assets/javascripts/lib/utils/logoutput_behaviours.js @@ -0,0 +1,46 @@ +import $ from 'jquery'; +import { canScroll, isScrolledToBottom, toggleDisableButton } from './scroll_utils'; + +export default class LogOutputBehaviours { + constructor() { + // Scroll buttons + this.$scrollTopBtn = $('.js-scroll-up'); + this.$scrollBottomBtn = $('.js-scroll-down'); + + this.$scrollTopBtn.off('click').on('click', this.scrollToTop.bind(this)); + this.$scrollBottomBtn.off('click').on('click', this.scrollToBottom.bind(this)); + } + + toggleScroll() { + const $document = $(document); + const currentPosition = $document.scrollTop(); + const scrollHeight = $document.height(); + + const windowHeight = $(window).height(); + if (canScroll()) { + if (currentPosition > 0 && scrollHeight - currentPosition !== windowHeight) { + // User is in the middle of the log + + toggleDisableButton(this.$scrollTopBtn, false); + toggleDisableButton(this.$scrollBottomBtn, false); + } else if (currentPosition === 0) { + // User is at Top of Log + + toggleDisableButton(this.$scrollTopBtn, true); + toggleDisableButton(this.$scrollBottomBtn, false); + } else if (isScrolledToBottom()) { + // User is at the bottom of the build log. + + toggleDisableButton(this.$scrollTopBtn, false); + toggleDisableButton(this.$scrollBottomBtn, true); + } + } else { + toggleDisableButton(this.$scrollTopBtn, true); + toggleDisableButton(this.$scrollBottomBtn, true); + } + } + + toggleScrollAnimation(toggle) { + this.$scrollBottomBtn.toggleClass('animate', toggle); + } +} diff --git a/app/assets/javascripts/lib/utils/scroll_utils.js b/app/assets/javascripts/lib/utils/scroll_utils.js new file mode 100644 index 00000000000..b31a8c38550 --- /dev/null +++ b/app/assets/javascripts/lib/utils/scroll_utils.js @@ -0,0 +1,26 @@ +import $ from 'jquery'; + +export const canScroll = () => $(document).height() > $(window).height(); + +export const isScrolledToBottom = () => { + const $document = $(document); + + const currentPosition = $document.scrollTop(); + const scrollHeight = $document.height(); + + const windowHeight = $(window).height(); + + return scrollHeight - currentPosition === windowHeight; +}; + +export const scrollDown = () => { + const $document = $(document); + $document.scrollTop($document.height()); +}; + +export const toggleDisableButton = ($button, disable) => { + if (disable && $button.prop('disabled')) return; + $button.prop('disabled', disable); +}; + +export default {}; -- cgit v1.2.1 From 7644edd8aae6d2959790f35302d3f0ec5767097d Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 30 May 2018 10:18:18 -0500 Subject: Add partial for the build outputs --- app/assets/javascripts/job.js | 4 ---- app/assets/javascripts/lib/utils/scroll_utils.js | 3 +++ app/views/projects/jobs/show.html.haml | 4 +--- app/views/shared/builds/_build_output.html.haml | 3 +++ qa/qa/page/project/job/show.rb | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 app/views/shared/builds/_build_output.html.haml diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index 83c38173a1c..295162a9ad5 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -32,10 +32,6 @@ export default class Job extends LogOutputBehaviours { this.$buildTraceOutput = $('.js-build-output'); this.$topBar = $('.js-top-bar'); - // Scroll controllers - this.$scrollTopBtn = $('.js-scroll-up'); - this.$scrollBottomBtn = $('.js-scroll-down'); - clearTimeout(this.timeout); this.initSidebar(); diff --git a/app/assets/javascripts/lib/utils/scroll_utils.js b/app/assets/javascripts/lib/utils/scroll_utils.js index b31a8c38550..9313b570863 100644 --- a/app/assets/javascripts/lib/utils/scroll_utils.js +++ b/app/assets/javascripts/lib/utils/scroll_utils.js @@ -2,6 +2,9 @@ import $ from 'jquery'; export const canScroll = () => $(document).height() > $(window).height(); +/** + * Checks if the entire page is scrolled down all the way to the bottom + */ export const isScrolledToBottom = () => { const $document = $(document); diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index ec9a04c0eab..1f33bb3a129 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -86,9 +86,7 @@ %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true } = custom_icon('scroll_down') - %pre.build-trace#build-trace - %code.bash.js-build-output - .build-loader-animation.js-build-refresh + = render 'shared/builds/build_output' - else = render "empty_states" diff --git a/app/views/shared/builds/_build_output.html.haml b/app/views/shared/builds/_build_output.html.haml new file mode 100644 index 00000000000..07f1501fadd --- /dev/null +++ b/app/views/shared/builds/_build_output.html.haml @@ -0,0 +1,3 @@ +%pre.build-trace#build-trace + %code.bash.js-build-output + .build-loader-animation.js-build-refresh diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb index 83bb224b5c3..f1a859fd8ee 100644 --- a/qa/qa/page/project/job/show.rb +++ b/qa/qa/page/project/job/show.rb @@ -4,7 +4,7 @@ module QA::Page COMPLETED_STATUSES = %w[passed failed canceled blocked skipped manual].freeze # excludes created, pending, running PASSED_STATUS = 'passed'.freeze - view 'app/views/projects/jobs/show.html.haml' do + view 'app/views/shared/builds/_build_output.html.haml' do element :build_output, '.js-build-output' end -- cgit v1.2.1 From 93ff116023628c9b007475eaecbd3d4efa5831a7 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 1 Jun 2018 18:04:16 +0100 Subject: Update installation from source guide --- doc/install/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index a0ae9017f71..f6bc9b8ed4a 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -301,9 +301,9 @@ sudo usermod -aG redis git ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-8-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 11-0-stable gitlab -**Note:** You can change `10-8-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `11-1-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It -- cgit v1.2.1 From 0e54777ee3e605201fad968a31de8c1c9c40e4a5 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 1 Jun 2018 18:09:36 +0100 Subject: Create update guide --- doc/update/10.8-to-11.0.md | 362 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 doc/update/10.8-to-11.0.md diff --git a/doc/update/10.8-to-11.0.md b/doc/update/10.8-to-11.0.md new file mode 100644 index 00000000000..64b268f7ca2 --- /dev/null +++ b/doc/update/10.8-to-11.0.md @@ -0,0 +1,362 @@ +--- +comments: false +--- + +# From 10.8 to 11.0 + +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + +```bash +sudo service gitlab stop +``` + +### 2. Backup + +NOTE: If you installed GitLab from source, make sure `rsync` is installed. + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Update Ruby + +NOTE: GitLab 9.0 and higher only support Ruby 2.3.x and dropped support for Ruby 2.1.x. Be +sure to upgrade your interpreter if necessary. + +You can check which version you are running with `ruby -v`. + +Download Ruby and compile it: + + ```bash + mkdir /tmp/ruby && cd /tmp/ruby + curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.7.tar.gz + echo '540996fec64984ab6099e34d2f5820b14904f15a ruby-2.3.7.tar.gz' | shasum -c - && tar xzf ruby-2.3.7.tar.gz + cd ruby-2.3.7 + + ./configure --disable-install-rdoc + make + sudo make install + ``` + +Install Bundler: + +```bash +sudo gem install bundler --no-ri --no-rdoc +``` + +### 4. Update Node + +GitLab utilizes [webpack](http://webpack.js.org) to compile frontend assets. +This requires a minimum version of node v6.0.0. + +You can check which version you are running with `node -v`. If you are running +a version older than `v6.0.0` you will need to update to a newer version. You +can find instructions to install from community maintained packages or compile +from source at the nodejs.org website. + + + +GitLab also requires the use of yarn `>= v1.2.0` to manage JavaScript +dependencies. + +```bash +curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list +sudo apt-get update +sudo apt-get install yarn +``` + +More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). + +### 5. Update Go + +NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go +1.5.x through 1.7.x. Be sure to upgrade your installation if necessary. + +You can check which version you are running with `go version`. + +Download and install Go: + +```bash +# Remove former Go installation folder +sudo rm -rf /usr/local/go + +curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz +echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772 go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \ + sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz +sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ +rm go1.8.3.linux-amd64.tar.gz +``` + +### 6. Get latest code + +```bash +cd /home/git/gitlab + +sudo -u git -H git fetch --all --prune +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +sudo -u git -H git checkout -- locale +``` + +For GitLab Community Edition: + +```bash +cd /home/git/gitlab + +sudo -u git -H git checkout 11-0-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +cd /home/git/gitlab + +sudo -u git -H git checkout 11-0-stable-ee +``` + +### 7. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell + +sudo -u git -H git fetch --all --tags --prune +sudo -u git -H git checkout v$( Date: Fri, 1 Jun 2018 19:36:51 +0000 Subject: Update template name via sentence case --- .gitlab/issue_templates/Research Proposal.md | 17 ----------------- .gitlab/issue_templates/Research proposal.md | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) delete mode 100644 .gitlab/issue_templates/Research Proposal.md create mode 100644 .gitlab/issue_templates/Research proposal.md diff --git a/.gitlab/issue_templates/Research Proposal.md b/.gitlab/issue_templates/Research Proposal.md deleted file mode 100644 index 5676656793d..00000000000 --- a/.gitlab/issue_templates/Research Proposal.md +++ /dev/null @@ -1,17 +0,0 @@ -### Background: - -(Include problem, use cases, benefits, and/or goals) - -**What questions are you trying to answer?** - -**Are you looking to verify an existing hypothesis or uncover new issues you should be exploring?** - -**What is the backstory of this project and how does it impact the approach?** - -**What do you already know about the areas you are exploring?** - -**What does success look like at the end of the project?** - -### Links / references: - -/label ~"UX research" diff --git a/.gitlab/issue_templates/Research proposal.md b/.gitlab/issue_templates/Research proposal.md new file mode 100644 index 00000000000..5676656793d --- /dev/null +++ b/.gitlab/issue_templates/Research proposal.md @@ -0,0 +1,17 @@ +### Background: + +(Include problem, use cases, benefits, and/or goals) + +**What questions are you trying to answer?** + +**Are you looking to verify an existing hypothesis or uncover new issues you should be exploring?** + +**What is the backstory of this project and how does it impact the approach?** + +**What do you already know about the areas you are exploring?** + +**What does success look like at the end of the project?** + +### Links / references: + +/label ~"UX research" -- cgit v1.2.1 From d8b87f3ef911e27c88886741c5c40f7a1d345161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20K=C3=A4mmerle?= Date: Fri, 1 Jun 2018 19:39:38 +0000 Subject: Update template name via sentence case (security) --- .../issue_templates/Security Developer Workflow.md | 70 --------------------- .../issue_templates/Security developer workflow.md | 71 ++++++++++++++++++++++ 2 files changed, 71 insertions(+), 70 deletions(-) delete mode 100644 .gitlab/issue_templates/Security Developer Workflow.md create mode 100644 .gitlab/issue_templates/Security developer workflow.md diff --git a/.gitlab/issue_templates/Security Developer Workflow.md b/.gitlab/issue_templates/Security Developer Workflow.md deleted file mode 100644 index 0c878dbf64c..00000000000 --- a/.gitlab/issue_templates/Security Developer Workflow.md +++ /dev/null @@ -1,70 +0,0 @@ - - -### Prior to the security release - -- [ ] Read the [security process for developers] if you are not familiar with it. -- [ ] Link to the original issue adding it to the [links section](#links) -- [ ] Run `scripts/security-harness` in the CE, EE, and/or Omnibus to prevent pushing to any remote besides `dev.gitlab.org` -- [ ] Create an MR targetting `org` `master`, prefixing your branch with `security-` -- [ ] Label your MR with the ~security label, prefix the title with `WIP: [master]` -- [ ] Add a link to the MR to the [links section](#links) -- [ ] Add a link to an EE MR if required -- [ ] Make sure the MR remains in-progress and gets approved after the review cycle, **but never merged**. -- [ ] Assign the MR to a RM once is reviewed and ready to be merged. Check the [RM list] to see who to ping. - -#### Backports - -- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases - - [ ] At this point, it might be easy to squash the commits from the MR into one - - You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [seckpick documentation] - - [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable) - - [ ] Create each MR targetting the security branch `security-X-Y` - - [ ] Add the ~security label and prefix with the version `WIP: [X.Y]` the title of the MR -- [ ] Make sure all MRs have a link in the [links section](#links) and are assigned to a Release Manager. - -[seckpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script - -#### Documentation and final details - -- [ ] Check the topic on #security to see when the next release is going to happen and add a link to the [links section](#links) -- [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details) -- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details) -- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details) -- [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details) - -### Summary -#### Links - -| Description | Link | -| -------- | -------- | -| Original issue | #TODO | -| Security release issue | #TODO | -| `master` MR | !TODO | -| `master` MR (EE) | !TODO | -| `Backport X.Y` MR | !TODO | -| `Backport X.Y` MR | !TODO | -| `Backport X.Y` MR | !TODO | -| `Backport X.Y` MR (EE) | !TODO | -| `Backport X.Y` MR (EE) | !TODO | -| `Backport X.Y` MR (EE) | !TODO | - -#### Details - -| Description | Details | Further details| -| -------- | -------- | -------- | -| Versions affected | X.Y | | -| Upgrade notes | | | -| GitLab Settings updated | Yes/No| | -| Migration required | Yes/No | | -| Thanks | | | - -[security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md -[RM list]: https://about.gitlab.com/release-managers/ - -/label ~security diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md new file mode 100644 index 00000000000..c1f702e9385 --- /dev/null +++ b/.gitlab/issue_templates/Security developer workflow.md @@ -0,0 +1,71 @@ + + +### Prior to the security release + +- [ ] Read the [security process for developers] if you are not familiar with it. +- [ ] Link to the original issue adding it to the [links section](#links) +- [ ] Run `scripts/security-harness` in the CE, EE, and/or Omnibus to prevent pushing to any remote besides `dev.gitlab.org` +- [ ] Create an MR targetting `org` `master`, prefixing your branch with `security-` +- [ ] Label your MR with the ~security label, prefix the title with `WIP: [master]` +- [ ] Add a link to the MR to the [links section](#links) +- [ ] Add a link to an EE MR if required +- [ ] Make sure the MR remains in-progress and gets approved after the review cycle, **but never merged**. +- [ ] Assign the MR to a RM once is reviewed and ready to be merged. Check the [RM list] to see who to ping. + +#### Backports + +- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases + - [ ] At this point, it might be easy to squash the commits from the MR into one + - You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [seckpick documentation] + - [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable) + - [ ] Create each MR targetting the security branch `security-X-Y` + - [ ] Add the ~security label and prefix with the version `WIP: [X.Y]` the title of the MR +- [ ] Make sure all MRs have a link in the [links section](#links) and are assigned to a Release Manager. + +[seckpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script + +#### Documentation and final details + +- [ ] Check the topic on #security to see when the next release is going to happen and add a link to the [links section](#links) +- [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details) +- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details) +- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details) +- [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details) + +### Summary + +#### Links + +| Description | Link | +| -------- | -------- | +| Original issue | #TODO | +| Security release issue | #TODO | +| `master` MR | !TODO | +| `master` MR (EE) | !TODO | +| `Backport X.Y` MR | !TODO | +| `Backport X.Y` MR | !TODO | +| `Backport X.Y` MR | !TODO | +| `Backport X.Y` MR (EE) | !TODO | +| `Backport X.Y` MR (EE) | !TODO | +| `Backport X.Y` MR (EE) | !TODO | + +#### Details + +| Description | Details | Further details| +| -------- | -------- | -------- | +| Versions affected | X.Y | | +| Upgrade notes | | | +| GitLab Settings updated | Yes/No| | +| Migration required | Yes/No | | +| Thanks | | | + +[security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md +[RM list]: https://about.gitlab.com/release-managers/ + +/label ~security -- cgit v1.2.1 From 38744526d8f2c4752a4c9a40080d13949be19994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20K=C3=A4mmerle?= Date: Fri, 1 Jun 2018 19:44:07 +0000 Subject: Update template name via sentence case (database changes) --- .../merge_request_templates/Database Changes.md | 50 ---------------------- .../merge_request_templates/Database changes.md | 50 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 50 deletions(-) delete mode 100644 .gitlab/merge_request_templates/Database Changes.md create mode 100644 .gitlab/merge_request_templates/Database changes.md diff --git a/.gitlab/merge_request_templates/Database Changes.md b/.gitlab/merge_request_templates/Database Changes.md deleted file mode 100644 index 1c4f30d9320..00000000000 --- a/.gitlab/merge_request_templates/Database Changes.md +++ /dev/null @@ -1,50 +0,0 @@ -Add a description of your merge request here. Merge requests without an adequate -description will not be reviewed until one is added. - -## Database Checklist - -When adding migrations: - -- [ ] Updated `db/schema.rb` -- [ ] Added a `down` method so the migration can be reverted -- [ ] Added the output of the migration(s) to the MR body -- [ ] Added tests for the migration in `spec/migrations` if necessary (e.g. when migrating data) - -When adding or modifying queries to improve performance: - -- [ ] Included data that shows the performance improvement, preferably in the form of a benchmark -- [ ] Included the output of `EXPLAIN (ANALYZE, BUFFERS)` of the relevant queries - -When adding foreign keys to existing tables: - -- [ ] Included a migration to remove orphaned rows in the source table before adding the foreign key -- [ ] Removed any instances of `dependent: ...` that may no longer be necessary - -When adding tables: - -- [ ] Ordered columns based on the [Ordering Table Columns](https://docs.gitlab.com/ee/development/ordering_table_columns.html#ordering-table-columns) guidelines -- [ ] Added foreign keys to any columns pointing to data in other tables -- [ ] Added indexes for fields that are used in statements such as WHERE, ORDER BY, GROUP BY, and JOINs - -When removing columns, tables, indexes or other structures: - -- [ ] Removed these in a post-deployment migration -- [ ] Made sure the application no longer uses (or ignores) these structures - -## General Checklist - -- [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary -- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/doc_styleguide.html) -- [ ] API support added -- [ ] Tests added for this feature/bug -- Conform by the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html) - - [ ] Has been reviewed by a Backend maintainer - - [ ] Has been reviewed by a Database specialist -- [ ] Conform by the [merge request performance guides](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html) -- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides) -- [ ] If you have multiple commits, please combine them into a few logically organized commits by [squashing them](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) -- [ ] Internationalization required/considered -- [ ] If paid feature, have we considered GitLab.com plan and how it works for groups and is there a design for promoting it to users who aren't on the correct plan -- [ ] End-to-end tests pass (`package-and-qa` manual pipeline job) - -/label ~database diff --git a/.gitlab/merge_request_templates/Database changes.md b/.gitlab/merge_request_templates/Database changes.md new file mode 100644 index 00000000000..d14d52e1b6b --- /dev/null +++ b/.gitlab/merge_request_templates/Database changes.md @@ -0,0 +1,50 @@ +Add a description of your merge request here. Merge requests without an adequate +description will not be reviewed until one is added. + +## Database checklist + +When adding migrations: + +- [ ] Updated `db/schema.rb` +- [ ] Added a `down` method so the migration can be reverted +- [ ] Added the output of the migration(s) to the MR body +- [ ] Added tests for the migration in `spec/migrations` if necessary (e.g. when migrating data) + +When adding or modifying queries to improve performance: + +- [ ] Included data that shows the performance improvement, preferably in the form of a benchmark +- [ ] Included the output of `EXPLAIN (ANALYZE, BUFFERS)` of the relevant queries + +When adding foreign keys to existing tables: + +- [ ] Included a migration to remove orphaned rows in the source table before adding the foreign key +- [ ] Removed any instances of `dependent: ...` that may no longer be necessary + +When adding tables: + +- [ ] Ordered columns based on the [Ordering Table Columns](https://docs.gitlab.com/ee/development/ordering_table_columns.html#ordering-table-columns) guidelines +- [ ] Added foreign keys to any columns pointing to data in other tables +- [ ] Added indexes for fields that are used in statements such as WHERE, ORDER BY, GROUP BY, and JOINs + +When removing columns, tables, indexes or other structures: + +- [ ] Removed these in a post-deployment migration +- [ ] Made sure the application no longer uses (or ignores) these structures + +## General checklist + +- [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary +- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/doc_styleguide.html) +- [ ] API support added +- [ ] Tests added for this feature/bug +- Conform by the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html) + - [ ] Has been reviewed by a Backend maintainer + - [ ] Has been reviewed by a Database specialist +- [ ] Conform by the [merge request performance guides](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html) +- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides) +- [ ] If you have multiple commits, please combine them into a few logically organized commits by [squashing them](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) +- [ ] Internationalization required/considered +- [ ] If paid feature, have we considered GitLab.com plan and how it works for groups and is there a design for promoting it to users who aren't on the correct plan +- [ ] End-to-end tests pass (`package-and-qa` manual pipeline job) + +/label ~database -- cgit v1.2.1 From 78d78ad1991f0a27b8ff79614d09f85909d20ed1 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 1 Jun 2018 13:44:16 -0700 Subject: Add comment about the need for truncating keys in Ruby 2.4 [ci skip] --- config/settings.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/settings.rb b/config/settings.rb index 4aa903109ea..58f38d103ea 100644 --- a/config/settings.rb +++ b/config/settings.rb @@ -85,7 +85,14 @@ class Settings < Settingslogic File.expand_path(path, Rails.root) end + # Returns a 256-bit key for attr_encrypted def attr_encrypted_db_key_base + # Ruby 2.4+ requires passing in the exact required length for OpenSSL keys + # (https://github.com/ruby/ruby/commit/ce635262f53b760284d56bb1027baebaaec175d1). + # Previous versions quietly truncated the input. + # + # The default mode for the attr_encrypted gem is to use a 256-bit key. + # We truncate the 128-byte string to 32 bytes. Gitlab::Application.secrets.db_key_base[0..31] end -- cgit v1.2.1 From 5e9687a198f205131d8e65c747f6b4ac2ae092f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 1 Jun 2018 23:39:24 +0200 Subject: Add deploy_strategy to ProjectAutoDevops --- app/models/project_auto_devops.rb | 5 +++++ ...01213245_add_deploy_strategy_to_project_auto_devops.rb | 15 +++++++++++++++ db/schema.rb | 3 ++- spec/models/project_auto_devops_spec.rb | 2 ++ 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20180601213245_add_deploy_strategy_to_project_auto_devops.rb diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index ed6c1eddbc1..8464b1d9157 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -1,6 +1,11 @@ class ProjectAutoDevops < ActiveRecord::Base belongs_to :project + enum deploy_strategy: { + manual: 1, + continuous: 2 + } + scope :enabled, -> { where(enabled: true) } scope :disabled, -> { where(enabled: false) } diff --git a/db/migrate/20180601213245_add_deploy_strategy_to_project_auto_devops.rb b/db/migrate/20180601213245_add_deploy_strategy_to_project_auto_devops.rb new file mode 100644 index 00000000000..d5ea25f02d4 --- /dev/null +++ b/db/migrate/20180601213245_add_deploy_strategy_to_project_auto_devops.rb @@ -0,0 +1,15 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddDeployStrategyToProjectAutoDevops < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + change_table :project_auto_devops do |t| + t.integer :deploy_strategy, null: false, default: 0 + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 97247387bc7..fc0a6347c6e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180529093006) do +ActiveRecord::Schema.define(version: 20180601213245) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1490,6 +1490,7 @@ ActiveRecord::Schema.define(version: 20180529093006) do t.datetime_with_timezone "updated_at", null: false t.boolean "enabled" t.string "domain" + t.integer "deploy_strategy", default: 0, null: false end add_index "project_auto_devops", ["project_id"], name: "index_project_auto_devops_on_project_id", unique: true, using: :btree diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb index 7545c0797e9..1bfc6befb4b 100644 --- a/spec/models/project_auto_devops_spec.rb +++ b/spec/models/project_auto_devops_spec.rb @@ -5,6 +5,8 @@ describe ProjectAutoDevops do it { is_expected.to belong_to(:project) } + it { is_expected.to define_enum_for(:deploy_strategy) } + it { is_expected.to respond_to(:created_at) } it { is_expected.to respond_to(:updated_at) } -- cgit v1.2.1 From eb1324dbb7f6cda34d902cd5ead2841bc3c674c9 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 1 Jun 2018 21:58:00 +0000 Subject: Fix bootstrap 4 file inputs --- app/assets/stylesheets/bootstrap_migration.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index d8e57834f9e..e24f8b1d4e8 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -36,6 +36,12 @@ html [type="button"], cursor: pointer; } +input[type="file"] { + // Bootstrap 4 file input height is taller by default + // which makes them look ugly + line-height: 1; +} + b, strong { font-weight: bold; -- cgit v1.2.1 From adbdd8e309fe124f4c0b6b62fda9541850241bba Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 1 Jun 2018 14:58:19 -0700 Subject: Add modal-size option to modal component --- .../javascripts/performance_bar/components/detailed_metric.vue | 1 + app/assets/javascripts/vue_shared/components/gl_modal.vue | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue index db8a0055acd..ea6776852d4 100644 --- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue +++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue @@ -56,6 +56,7 @@ export default { diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 1e7b9534275..996e5c1512d 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -448,6 +448,10 @@ img.emoji { .break-word { word-wrap: break-word; + + &.all-words { + word-break: break-word; + } } /** COMMON CLASSES **/ -- cgit v1.2.1 From e8ecae7e0be12e6a1fe0e999d724ab5db9bb8b69 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sat, 2 Jun 2018 11:55:42 +0900 Subject: Reveert build_relations and simply add a line for creating iid --- lib/gitlab/ci/pipeline/chain/populate.rb | 20 +++++++------------- spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb | 6 +++--- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/gitlab/ci/pipeline/chain/populate.rb b/lib/gitlab/ci/pipeline/chain/populate.rb index 7a2a1c6a80b..f34c11ca3c2 100644 --- a/lib/gitlab/ci/pipeline/chain/populate.rb +++ b/lib/gitlab/ci/pipeline/chain/populate.rb @@ -8,7 +8,13 @@ module Gitlab PopulateError = Class.new(StandardError) def perform! - build_relations + # Allocate next IID. This operation must be outside of transactions of pipeline creations. + pipeline.ensure_project_iid! + + ## + # Populate pipeline with block argument of CreatePipelineService#execute. + # + @command.seeds_block&.call(pipeline) ## # Populate pipeline with all stages, and stages with builds. @@ -31,18 +37,6 @@ module Gitlab def break? pipeline.errors.any? end - - private - - def build_relations - ## - # Populate pipeline with block argument of CreatePipelineService#execute. - # - @command.seeds_block&.call(pipeline) - - # Allocate next IID. This operation must be outside of transactions of pipeline creations. - pipeline.ensure_project_iid! - end end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index 7088233f237..e1766fc0ec9 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -140,10 +140,10 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do expect { step.perform! }.to raise_error(ActiveRecord::RecordNotSaved) end - it 'does not waste pipeline iid' do - step.perform rescue nil + it 'wastes pipeline iid' do + expect { step.perform! }.to raise_error - expect(InternalId.ci_pipelines.where(project_id: project.id).exists?).to be_falsy + expect(InternalId.ci_pipelines.where(project_id: project.id).exists?).to be_truthy end end end -- cgit v1.2.1 From 5403a4a07ed86770f1ab11108ab5a86fa2b90e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 2 Jun 2018 11:46:55 +0200 Subject: Add deploy_strategy to safe model attributes --- spec/lib/gitlab/import_export/safe_model_attributes.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 74e7a45fd6c..5e6311441f8 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -539,6 +539,7 @@ ProjectAutoDevops: - id - enabled - domain +- deploy_strategy - project_id - created_at - updated_at -- cgit v1.2.1 From 61df812ac688cb0848752f9f26f77d65eadf160a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 2 Jun 2018 02:32:30 -0700 Subject: Fix attr_encryption key settings attr_encrypted does different things with `key` depending on what mode you are using: 1. In `:per_attribute_iv_and_salt` mode, it generates a hash with the salt: https://github.com/attr-encrypted/encryptor/blob/c3a62c4a9e74686dd95e0548f9dc2a361fdc95d1/lib/encryptor.rb#L77. There is no need to truncate the key to 32 bytes here. 2. In `:per_attribute_iv` mode, it sets the key directly to the password, so truncation to 32 bytes is necessary. Closes #47166 --- app/models/clusters/platforms/kubernetes.rb | 4 ++-- app/models/clusters/providers/gcp.rb | 2 +- .../unreleased/sh-fix-secrets-not-working.yml | 5 +++++ config/settings.rb | 23 ++++++++++++++-------- 4 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 changelogs/unreleased/sh-fix-secrets-not-working.yml diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 25eac5160f1..36631d57ad1 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -11,12 +11,12 @@ module Clusters attr_encrypted :password, mode: :per_attribute_iv, - key: Settings.attr_encrypted_db_key_base, + key: Settings.attr_encrypted_db_key_base_truncated, algorithm: 'aes-256-cbc' attr_encrypted :token, mode: :per_attribute_iv, - key: Settings.attr_encrypted_db_key_base, + key: Settings.attr_encrypted_db_key_base_truncated, algorithm: 'aes-256-cbc' before_validation :enforce_namespace_to_lower_case diff --git a/app/models/clusters/providers/gcp.rb b/app/models/clusters/providers/gcp.rb index eb2e42fd3fe..4db1bb35c12 100644 --- a/app/models/clusters/providers/gcp.rb +++ b/app/models/clusters/providers/gcp.rb @@ -11,7 +11,7 @@ module Clusters attr_encrypted :access_token, mode: :per_attribute_iv, - key: Settings.attr_encrypted_db_key_base, + key: Settings.attr_encrypted_db_key_base_truncated, algorithm: 'aes-256-cbc' validates :gcp_project_id, diff --git a/changelogs/unreleased/sh-fix-secrets-not-working.yml b/changelogs/unreleased/sh-fix-secrets-not-working.yml new file mode 100644 index 00000000000..044a873ecd9 --- /dev/null +++ b/changelogs/unreleased/sh-fix-secrets-not-working.yml @@ -0,0 +1,5 @@ +--- +title: Fix attr_encryption key settings +merge_request: +author: +type: fixed diff --git a/config/settings.rb b/config/settings.rb index 58f38d103ea..3f3481bb65d 100644 --- a/config/settings.rb +++ b/config/settings.rb @@ -85,17 +85,24 @@ class Settings < Settingslogic File.expand_path(path, Rails.root) end - # Returns a 256-bit key for attr_encrypted - def attr_encrypted_db_key_base - # Ruby 2.4+ requires passing in the exact required length for OpenSSL keys - # (https://github.com/ruby/ruby/commit/ce635262f53b760284d56bb1027baebaaec175d1). - # Previous versions quietly truncated the input. - # - # The default mode for the attr_encrypted gem is to use a 256-bit key. - # We truncate the 128-byte string to 32 bytes. + # Ruby 2.4+ requires passing in the exact required length for OpenSSL keys + # (https://github.com/ruby/ruby/commit/ce635262f53b760284d56bb1027baebaaec175d1). + # Previous versions quietly truncated the input. + # + # Use this when using :per_attribute_iv mode for attr_encrypted. + # We have to truncate the string to 32 bytes for a 256-bit cipher. + def attr_encrypted_db_key_base_truncated Gitlab::Application.secrets.db_key_base[0..31] end + # This should be used for :per_attribute_salt_and_iv mode. There is no + # need to truncate the key because the encryptor will use the salt to + # generate a hash of the password: + # https://github.com/attr-encrypted/encryptor/blob/c3a62c4a9e74686dd95e0548f9dc2a361fdc95d1/lib/encryptor.rb#L77 + def attr_encrypted_db_key_base + Gitlab::Application.secrets.db_key_base + end + private def base_url(config) -- cgit v1.2.1 From a91999e4faddbb5f5fc8e38548f4544ba6a41adf Mon Sep 17 00:00:00 2001 From: Jasper Maes Date: Sat, 2 Jun 2018 01:52:15 +0200 Subject: Rails5 Fix arel from --- changelogs/unreleased/rails5-fix-46281.yml | 5 +++++ lib/gitlab/database/median.rb | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/rails5-fix-46281.yml diff --git a/changelogs/unreleased/rails5-fix-46281.yml b/changelogs/unreleased/rails5-fix-46281.yml new file mode 100644 index 00000000000..ee0b8531988 --- /dev/null +++ b/changelogs/unreleased/rails5-fix-46281.yml @@ -0,0 +1,5 @@ +--- +title: Rails5 fix arel from +merge_request: 19340 +author: Jasper Maes +type: fixed diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb index 74fed447289..3cac007a42c 100644 --- a/lib/gitlab/database/median.rb +++ b/lib/gitlab/database/median.rb @@ -143,8 +143,13 @@ module Gitlab .order(arel_table[column_sym]) ).as('row_id') - count = arel_table.from(arel_table.alias) - .project('COUNT(*)') + arel_from = if Gitlab.rails5? + arel_table.from.from(arel_table.alias) + else + arel_table.from(arel_table.alias) + end + + count = arel_from.project('COUNT(*)') .where(arel_table[partition_column].eq(arel_table.alias[partition_column])) .as('ct') -- cgit v1.2.1 From ca466d5d152e1a4f35ef044f344d8b7b33617db2 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 2 Jun 2018 06:14:58 -0700 Subject: Fix missing key change in 20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb --- ...24104327_migrate_kubernetes_service_to_new_clusters_architectures.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/post_migrate/20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb b/db/post_migrate/20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb index 1586a7eb92f..a957f107405 100644 --- a/db/post_migrate/20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb +++ b/db/post_migrate/20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb @@ -48,7 +48,7 @@ class MigrateKubernetesServiceToNewClustersArchitectures < ActiveRecord::Migrati attr_encrypted :token, mode: :per_attribute_iv, - key: Settings.attr_encrypted_db_key_base, + key: Settings.attr_encrypted_db_key_base_truncated, algorithm: 'aes-256-cbc' end -- cgit v1.2.1 From d65cdd441627cf73ad9e2554ca8d6912e851672d Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 2 Jun 2018 00:28:57 -0700 Subject: Fix intermittent failing spec in spec/support/helpers/cycle_analytics_helpers.rb There was a race condition in the spec where if the commit is created on disk within a second of the frozen `Timecop` time, the test fails. Closes #43981 --- spec/lib/gitlab/cycle_analytics/usage_data_spec.rb | 19 ++++++++--------- spec/support/helpers/cycle_analytics_helpers.rb | 24 +++++++++++----------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb index 56a316318cb..a785b17f682 100644 --- a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb @@ -3,7 +3,12 @@ require 'spec_helper' describe Gitlab::CycleAnalytics::UsageData do describe '#to_json' do before do - Timecop.freeze do + # Since git commits only have second precision, round up to the + # nearest second to ensure we have accurate median and standard + # deviation calculations. + current_time = Time.at(Time.now.to_i) + + Timecop.freeze(current_time) do user = create(:user, :admin) projects = create_list(:project, 2, :repository) @@ -37,13 +42,7 @@ describe Gitlab::CycleAnalytics::UsageData do expected_values.each_pair do |op, value| expect(stage_values).to have_key(op) - - if op == :missing - expect(stage_values[op]).to eq(value) - else - # delta is used because of git timings that Timecop does not stub - expect(stage_values[op].to_i).to be_within(5).of(value.to_i) - end + expect(stage_values[op]).to eq(value) end end end @@ -58,8 +57,8 @@ describe Gitlab::CycleAnalytics::UsageData do missing: 0 }, plan: { - average: 2, - sd: 2, + average: 1, + sd: 0, missing: 0 }, code: { diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index 55359d36597..06a76d53354 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -4,12 +4,12 @@ module CycleAnalyticsHelpers create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name) end - def create_commit(message, project, user, branch_name, count: 1) + def create_commit(message, project, user, branch_name, count: 1, commit_time: nil, skip_push_handler: false) repository = project.repository - oldrev = repository.commit(branch_name).sha + oldrev = repository.commit(branch_name)&.sha || Gitlab::Git::BLANK_SHA if Timecop.frozen? && Gitlab::GitalyClient.feature_enabled?(:operation_user_commit_files) - mock_gitaly_multi_action_dates(repository.raw) + mock_gitaly_multi_action_dates(repository.raw, commit_time) end commit_shas = Array.new(count) do |index| @@ -19,6 +19,8 @@ module CycleAnalyticsHelpers commit_sha end + return if skip_push_handler + GitPushService.new(project, user, oldrev: oldrev, @@ -44,13 +46,11 @@ module CycleAnalyticsHelpers project.repository.add_branch(user, source_branch, 'master') end - sha = project.repository.create_file( - user, - generate(:branch), - 'content', - message: commit_message, - branch_name: source_branch) - project.repository.commit(sha) + # Cycle analytic specs often test with frozen times, which causes metrics to be + # pinned to the current time. For example, in the plan stage, we assume that an issue + # milestone has been created before any code has been written. We add a second + # to ensure that the plan time is positive. + create_commit(commit_message, project, user, source_branch, commit_time: Time.now + 1.second, skip_push_handler: true) opts = { title: 'Awesome merge_request', @@ -116,9 +116,9 @@ module CycleAnalyticsHelpers protected: false) end - def mock_gitaly_multi_action_dates(raw_repository) + def mock_gitaly_multi_action_dates(raw_repository, commit_time) allow(raw_repository).to receive(:multi_action).and_wrap_original do |m, *args| - new_date = Time.now + new_date = commit_time || Time.now branch_update = m.call(*args) if branch_update.newrev -- cgit v1.2.1 From 2121e3b15f0e65e322e8f14690159e2e07905849 Mon Sep 17 00:00:00 2001 From: Marc Shaw Date: Wed, 18 Apr 2018 23:22:53 +1200 Subject: 44790: Disable the action mailer base logging when emails are disabled --- config/initializers/disable_email_interceptor.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config/initializers/disable_email_interceptor.rb b/config/initializers/disable_email_interceptor.rb index c76a6b8b19f..e8770c8d460 100644 --- a/config/initializers/disable_email_interceptor.rb +++ b/config/initializers/disable_email_interceptor.rb @@ -1,2 +1,5 @@ # Interceptor in lib/disable_email_interceptor.rb -ActionMailer::Base.register_interceptor(DisableEmailInterceptor) unless Gitlab.config.gitlab.email_enabled +unless Gitlab.config.gitlab.email_enabled + ActionMailer::Base.register_interceptor(DisableEmailInterceptor) + ActionMailer::Base.logger = nil +end -- cgit v1.2.1 From caadfcdee31237bbfbcfb304346fc4a5994295dd Mon Sep 17 00:00:00 2001 From: Mario de la Ossa Date: Mon, 21 May 2018 20:24:37 -0600 Subject: Backport changes in app/services/test_hooks/project_service.rb --- app/services/test_hooks/project_service.rb | 4 +++- spec/services/test_hooks/project_service_spec.rb | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/services/test_hooks/project_service.rb b/app/services/test_hooks/project_service.rb index 01d5d774cd5..65183e84cce 100644 --- a/app/services/test_hooks/project_service.rb +++ b/app/services/test_hooks/project_service.rb @@ -1,11 +1,13 @@ module TestHooks class ProjectService < TestHooks::BaseService - private + attr_writer :project def project @project ||= hook.project end + private + def push_events_data throw(:validation_error, 'Ensure the project has at least one commit.') if project.empty_repo? diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb index 962b9f40c4f..19e1c5ff3b2 100644 --- a/spec/services/test_hooks/project_service_spec.rb +++ b/spec/services/test_hooks/project_service_spec.rb @@ -6,13 +6,19 @@ describe TestHooks::ProjectService do describe '#execute' do let(:project) { create(:project, :repository) } let(:hook) { create(:project_hook, project: project) } + let(:trigger) { 'not_implemented_events' } let(:service) { described_class.new(hook, current_user, trigger) } let(:sample_data) { { data: 'sample' } } let(:success_result) { { status: :success, http_status: 200, message: 'ok' } } - context 'hook with not implemented test' do - let(:trigger) { 'not_implemented_events' } + it 'allows to set a custom project' do + project = double + service.project = project + + expect(service.project).to eq(project) + end + context 'hook with not implemented test' do it 'returns error message' do expect(hook).not_to receive(:execute) expect(service.execute).to include({ status: :error, message: 'Testing not available for this hook' }) -- cgit v1.2.1 From 0c994215af61f8b15f90a5d996f5ba35e94be860 Mon Sep 17 00:00:00 2001 From: Mario de la Ossa Date: Sat, 2 Jun 2018 17:16:36 -0600 Subject: Backport changes in app/services/applications/create_service/rb --- app/services/applications/create_service.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/services/applications/create_service.rb b/app/services/applications/create_service.rb index 35d45f25a71..e67af929954 100644 --- a/app/services/applications/create_service.rb +++ b/app/services/applications/create_service.rb @@ -2,8 +2,7 @@ module Applications class CreateService def initialize(current_user, params) @current_user = current_user - @params = params - @ip_address = @params.delete(:ip_address) + @params = params.except(:ip_address) end def execute(request = nil) -- cgit v1.2.1 From 95e41bc54bb314755486587141e977034f332b60 Mon Sep 17 00:00:00 2001 From: Marc Shaw Date: Sat, 12 May 2018 12:45:17 +1200 Subject: 44790: Add change log --- changelogs/unreleased/44790-disabled-emails-logging.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/44790-disabled-emails-logging.yml diff --git a/changelogs/unreleased/44790-disabled-emails-logging.yml b/changelogs/unreleased/44790-disabled-emails-logging.yml new file mode 100644 index 00000000000..90125dc0300 --- /dev/null +++ b/changelogs/unreleased/44790-disabled-emails-logging.yml @@ -0,0 +1,5 @@ +--- +title: Stop logging email information when emails are disabled +merge_request: 18521 +author: Marc Shaw +type: fixed -- cgit v1.2.1 From f139b62c01816fd25228c3740a3a5b379339e74c Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Mon, 28 May 2018 08:00:36 +0900 Subject: Fix CSS for buttons not to be hidden on issues/MR title --- app/assets/stylesheets/pages/detail_page.scss | 3 ++- app/assets/stylesheets/pages/issuable.scss | 2 ++ app/helpers/issuables_helper.rb | 2 +- changelogs/unreleased/46861-issuable-title-with-longer-username.yml | 5 +++++ 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/46861-issuable-title-with-longer-username.yml diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 7b36bcb3c7d..2e007c52592 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -23,7 +23,8 @@ position: relative; line-height: 35px; display: flex; - flex-grow: 1; + flex: 1 1; + min-width: 0; @include media-breakpoint-up(sm) { padding-left: 0; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 4aea9740735..10a0e076cb4 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -689,6 +689,8 @@ font-size: 14px; line-height: 24px; align-self: center; + overflow: hidden; + text-overflow: ellipsis; } .js-issuable-selector-wrap { diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 8572c2b7276..52d6356e567 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -157,7 +157,7 @@ module IssuablesHelper output = "" output << "Opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe output << content_tag(:strong) do - author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline-block", tooltip: true) + author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline", tooltip: true) author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-block d-sm-none") end diff --git a/changelogs/unreleased/46861-issuable-title-with-longer-username.yml b/changelogs/unreleased/46861-issuable-title-with-longer-username.yml new file mode 100644 index 00000000000..9df6879deb6 --- /dev/null +++ b/changelogs/unreleased/46861-issuable-title-with-longer-username.yml @@ -0,0 +1,5 @@ +--- +title: Fix CSS for buttons not to be hidden on issues/MR title +merge_request: 19176 +author: Takuya Noguchi +type: fixed -- cgit v1.2.1 From bd4bfcc6411e4819c0c67717095bb2e54e7bb6df Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 3 Jun 2018 03:31:41 -0700 Subject: Fix N+1 with source projects in merge requests API Now that we are checking `MergeRequest#for_fork?`, we also need the source project preloaded for a merge request. --- changelogs/unreleased/sh-fix-source-project-nplus-one.yml | 5 +++++ lib/api/merge_requests.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/sh-fix-source-project-nplus-one.yml diff --git a/changelogs/unreleased/sh-fix-source-project-nplus-one.yml b/changelogs/unreleased/sh-fix-source-project-nplus-one.yml new file mode 100644 index 00000000000..9d78ad6408c --- /dev/null +++ b/changelogs/unreleased/sh-fix-source-project-nplus-one.yml @@ -0,0 +1,5 @@ +--- +title: Fix N+1 with source_projects in merge requests API +merge_request: +author: +type: performance diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index b1e510d72de..278d53427f0 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -38,7 +38,7 @@ module API merge_requests = MergeRequestsFinder.new(current_user, args).execute .reorder(args[:order_by] => args[:sort]) merge_requests = paginate(merge_requests) - .preload(:target_project) + .preload(:source_project, :target_project) return merge_requests if args[:view] == 'simple' -- cgit v1.2.1 From e87031e4b127fe11044e2d761362f90fecda1537 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Sat, 26 May 2018 23:21:20 +0900 Subject: Update email_spec to 2.2.0 --- Gemfile | 2 +- Gemfile.lock | 7 ++++--- changelogs/unreleased/46845-update-email_spec-to-2-2-0.yml | 5 +++++ 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/46845-update-email_spec-to-2-2-0.yml diff --git a/Gemfile b/Gemfile index 68c7b3dcb08..cd9cbcde359 100644 --- a/Gemfile +++ b/Gemfile @@ -374,7 +374,7 @@ end group :test do gem 'shoulda-matchers', '~> 3.1.2', require: false - gem 'email_spec', '~> 1.6.0' + gem 'email_spec', '~> 2.2.0' gem 'json-schema', '~> 2.8.0' gem 'webmock', '~> 2.3.2' gem 'rails-controller-testing' if rails5? # Rails5 only gem. diff --git a/Gemfile.lock b/Gemfile.lock index 2efd89bf40d..c2f11ca4c84 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -178,9 +178,10 @@ GEM dropzonejs-rails (0.7.2) rails (> 3.1) email_reply_trimmer (0.1.6) - email_spec (1.6.0) + email_spec (2.2.0) + htmlentities (~> 4.3.3) launchy (~> 2.1) - mail (~> 2.2) + mail (~> 2.7) encryptor (3.0.0) equalizer (0.0.11) erubis (2.7.0) @@ -1012,7 +1013,7 @@ DEPENDENCIES doorkeeper-openid_connect (~> 1.3) dropzonejs-rails (~> 0.7.1) email_reply_trimmer (~> 0.1) - email_spec (~> 1.6.0) + email_spec (~> 2.2.0) factory_bot_rails (~> 4.8.2) faraday (~> 0.12) fast_blank diff --git a/changelogs/unreleased/46845-update-email_spec-to-2-2-0.yml b/changelogs/unreleased/46845-update-email_spec-to-2-2-0.yml new file mode 100644 index 00000000000..bf501340769 --- /dev/null +++ b/changelogs/unreleased/46845-update-email_spec-to-2-2-0.yml @@ -0,0 +1,5 @@ +--- +title: Update email_spec to 2.2.0 +merge_request: 19164 +author: Takuya Noguchi +type: other -- cgit v1.2.1 From 3579ed016acbc3a512217d896a112747fcfd33bc Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Sun, 3 Jun 2018 22:17:36 +0900 Subject: Revert "Add a new have_html_escaped_body_text that match an HTML-escaped text" This reverts commit 517598ba10793efa02cb90379f78ab97c9c5b25d. --- spec/mailers/notify_spec.rb | 18 +++++++++--------- spec/support/matchers/email_matchers.rb | 5 ----- 2 files changed, 9 insertions(+), 14 deletions(-) delete mode 100644 spec/support/matchers/email_matchers.rb diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 69eafbe4bbe..ecd3d309327 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -660,7 +660,7 @@ describe Notify do is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.invite_email - is_expected.to have_html_escaped_body_text invited_user.name + is_expected.to have_body_text invited_user.name end end @@ -932,7 +932,7 @@ describe Notify do end it 'contains the message from the note' do - is_expected.to have_html_escaped_body_text note.note + is_expected.to have_body_text note.note end it 'contains an introduction' do @@ -991,7 +991,7 @@ describe Notify do expect(to_emails).to eq([recipient.notification_email]) is_expected.to have_subject "Request to join the #{group.name} group" - is_expected.to have_html_escaped_body_text group.name + is_expected.to have_body_text group.name is_expected.to have_body_text group_group_members_url(group) is_expected.to have_body_text group_member.human_access end @@ -1010,7 +1010,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject "Access to the #{group.name} group was denied" - is_expected.to have_html_escaped_body_text group.name + is_expected.to have_body_text group.name is_expected.to have_body_text group.web_url end end @@ -1026,7 +1026,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject "Access to the #{group.name} group was granted" - is_expected.to have_html_escaped_body_text group.name + is_expected.to have_body_text group.name is_expected.to have_body_text group.web_url is_expected.to have_body_text group_member.human_access end @@ -1056,7 +1056,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject "Invitation to join the #{group.name} group" - is_expected.to have_html_escaped_body_text group.name + is_expected.to have_body_text group.name is_expected.to have_body_text group.web_url is_expected.to have_body_text group_member.human_access is_expected.to have_body_text group_member.invite_token @@ -1080,10 +1080,10 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject 'Invitation accepted' - is_expected.to have_html_escaped_body_text group.name + is_expected.to have_body_text group.name is_expected.to have_body_text group.web_url is_expected.to have_body_text group_member.invite_email - is_expected.to have_html_escaped_body_text invited_user.name + is_expected.to have_body_text invited_user.name end end @@ -1103,7 +1103,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject 'Invitation declined' - is_expected.to have_html_escaped_body_text group.name + is_expected.to have_body_text group.name is_expected.to have_body_text group.web_url is_expected.to have_body_text group_member.invite_email end diff --git a/spec/support/matchers/email_matchers.rb b/spec/support/matchers/email_matchers.rb deleted file mode 100644 index d9d59ec12ec..00000000000 --- a/spec/support/matchers/email_matchers.rb +++ /dev/null @@ -1,5 +0,0 @@ -RSpec::Matchers.define :have_html_escaped_body_text do |expected| - match do |actual| - expect(actual).to have_body_text(ERB::Util.html_escape(expected)) - end -end -- cgit v1.2.1 From d46c12290d74e9faac888bf5de5cc1d84a00b675 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Sun, 3 Jun 2018 22:22:50 +0900 Subject: Replace have_html_espaced_body_text after 517598ba --- spec/mailers/notify_spec.rb | 38 +++++++++++----------- .../shared_examples/notify_shared_examples.rb | 4 +-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index ecd3d309327..775ca4ba0eb 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -68,7 +68,7 @@ describe Notify do end it 'contains the description' do - is_expected.to have_html_escaped_body_text issue.description + is_expected.to have_body_text issue.description end it 'does not add a reason header' do @@ -89,7 +89,7 @@ describe Notify do end it 'contains a link to note author' do - is_expected.to have_html_escaped_body_text(issue.author_name) + is_expected.to have_body_text(issue.author_name) is_expected.to have_body_text 'created an issue:' end end @@ -115,8 +115,8 @@ describe Notify do it 'has the correct subject and body' do aggregate_failures do is_expected.to have_referable_subject(issue, reply: true) - is_expected.to have_html_escaped_body_text(previous_assignee.name) - is_expected.to have_html_escaped_body_text(assignee.name) + is_expected.to have_body_text(previous_assignee.name) + is_expected.to have_body_text(assignee.name) is_expected.to have_body_text(project_issue_path(project, issue)) end end @@ -190,7 +190,7 @@ describe Notify do aggregate_failures do is_expected.to have_referable_subject(issue, reply: true) is_expected.to have_body_text(status) - is_expected.to have_html_escaped_body_text(current_user.name) + is_expected.to have_body_text(current_user.name) is_expected.to have_body_text(project_issue_path project, issue) end end @@ -243,7 +243,7 @@ describe Notify do end it 'contains the description' do - is_expected.to have_html_escaped_body_text merge_request.description + is_expected.to have_body_text merge_request.description end context 'when sent with a reason' do @@ -260,7 +260,7 @@ describe Notify do end it 'contains a link to note author' do - is_expected.to have_html_escaped_body_text merge_request.author_name + is_expected.to have_body_text merge_request.author_name is_expected.to have_body_text 'created a merge request:' end end @@ -286,9 +286,9 @@ describe Notify do it 'has the correct subject and body' do aggregate_failures do is_expected.to have_referable_subject(merge_request, reply: true) - is_expected.to have_html_escaped_body_text(previous_assignee.name) + is_expected.to have_body_text(previous_assignee.name) is_expected.to have_body_text(project_merge_request_path(project, merge_request)) - is_expected.to have_html_escaped_body_text(assignee.name) + is_expected.to have_body_text(assignee.name) end end @@ -358,7 +358,7 @@ describe Notify do aggregate_failures do is_expected.to have_referable_subject(merge_request, reply: true) is_expected.to have_body_text(status) - is_expected.to have_html_escaped_body_text(current_user.name) + is_expected.to have_body_text(current_user.name) is_expected.to have_body_text(project_merge_request_path(project, merge_request)) end end @@ -526,7 +526,7 @@ describe Notify do it 'has the correct subject and body' do is_expected.to have_referable_subject(project_snippet, reply: true) - is_expected.to have_html_escaped_body_text project_snippet_note.note + is_expected.to have_body_text project_snippet_note.note end end @@ -539,7 +539,7 @@ describe Notify do it 'has the correct subject and body' do is_expected.to have_subject("#{project.name} | Project was moved") - is_expected.to have_html_escaped_body_text project.full_name + is_expected.to have_body_text project.full_name is_expected.to have_body_text(project.ssh_url_to_repo) end end @@ -566,7 +566,7 @@ describe Notify do expect(to_emails).to eq([recipient.notification_email]) is_expected.to have_subject "Request to join the #{project.full_name} project" - is_expected.to have_html_escaped_body_text project.full_name + is_expected.to have_body_text project.full_name is_expected.to have_body_text project_project_members_url(project) is_expected.to have_body_text project_member.human_access end @@ -586,7 +586,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject "Access to the #{project.full_name} project was denied" - is_expected.to have_html_escaped_body_text project.full_name + is_expected.to have_body_text project.full_name is_expected.to have_body_text project.web_url end end @@ -603,7 +603,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject "Access to the #{project.full_name} project was granted" - is_expected.to have_html_escaped_body_text project.full_name + is_expected.to have_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.human_access end @@ -633,7 +633,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject "Invitation to join the #{project.full_name} project" - is_expected.to have_html_escaped_body_text project.full_name + is_expected.to have_body_text project.full_name is_expected.to have_body_text project.full_name is_expected.to have_body_text project_member.human_access is_expected.to have_body_text project_member.invite_token @@ -657,7 +657,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject 'Invitation accepted' - is_expected.to have_html_escaped_body_text project.full_name + is_expected.to have_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.invite_email is_expected.to have_body_text invited_user.name @@ -680,7 +680,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject 'Invitation declined' - is_expected.to have_html_escaped_body_text project.full_name + is_expected.to have_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.invite_email end @@ -1396,7 +1396,7 @@ describe Notify do it 'has the correct subject and body' do is_expected.to have_referable_subject(personal_snippet, reply: true) - is_expected.to have_html_escaped_body_text personal_snippet_note.note + is_expected.to have_body_text personal_snippet_note.note end end end diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index 43fdaddf545..d176d3fa425 100644 --- a/spec/support/shared_examples/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb @@ -212,7 +212,7 @@ shared_examples 'a note email' do end it 'contains the message from the note' do - is_expected.to have_html_escaped_body_text note.note + is_expected.to have_body_text note.note end it 'does not contain note author' do @@ -225,7 +225,7 @@ shared_examples 'a note email' do end it 'contains a link to note author' do - is_expected.to have_html_escaped_body_text note.author_name + is_expected.to have_body_text note.author_name end end end -- cgit v1.2.1 From 0b648a492060654126de86d813c1b877535de832 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Mon, 4 Jun 2018 00:15:58 +0900 Subject: Update selenium-webdriver to 3.12.0 --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- .../unreleased/47183-update-selenium-webdriver-to-3-12-0.yml | 5 +++++ 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/47183-update-selenium-webdriver-to-3-12-0.yml diff --git a/Gemfile b/Gemfile index 68c7b3dcb08..e3e7ef2aa27 100644 --- a/Gemfile +++ b/Gemfile @@ -342,7 +342,7 @@ group :development, :test do gem 'capybara', '~> 2.15' gem 'capybara-screenshot', '~> 1.0.0' - gem 'selenium-webdriver', '~> 3.5' + gem 'selenium-webdriver', '~> 3.12' gem 'spring', '~> 2.0.0' gem 'spring-commands-rspec', '~> 1.0.4' diff --git a/Gemfile.lock b/Gemfile.lock index 2efd89bf40d..767472a8a7a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -115,7 +115,7 @@ GEM mime-types (>= 1.16) cause (0.1) charlock_holmes (0.7.6) - childprocess (0.7.0) + childprocess (0.9.0) ffi (~> 1.0, >= 1.0.11) chronic (0.10.2) chronic_duration (0.10.6) @@ -828,9 +828,9 @@ GEM activesupport (>= 3.1) select2-rails (3.5.9.3) thor (~> 0.14) - selenium-webdriver (3.5.0) + selenium-webdriver (3.12.0) childprocess (~> 0.5) - rubyzip (~> 1.0) + rubyzip (~> 1.2) sentry-raven (2.7.2) faraday (>= 0.7.6, < 1.0) settingslogic (2.0.9) @@ -1154,7 +1154,7 @@ DEPENDENCIES scss_lint (~> 0.56.0) seed-fu (~> 2.3.7) select2-rails (~> 3.5.9) - selenium-webdriver (~> 3.5) + selenium-webdriver (~> 3.12) sentry-raven (~> 2.7) settingslogic (~> 2.0.9) sham_rack (~> 1.3.6) diff --git a/changelogs/unreleased/47183-update-selenium-webdriver-to-3-12-0.yml b/changelogs/unreleased/47183-update-selenium-webdriver-to-3-12-0.yml new file mode 100644 index 00000000000..b0d51d810f2 --- /dev/null +++ b/changelogs/unreleased/47183-update-selenium-webdriver-to-3-12-0.yml @@ -0,0 +1,5 @@ +--- +title: Update selenium-webdriver to 3.12.0 +merge_request: 19351 +author: Takuya Noguchi +type: other -- cgit v1.2.1 From 3f55e0b29fe39c5fd31c703911c96808bd17335d Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Sun, 3 Jun 2018 23:45:26 +0900 Subject: Use the default strings of timeago.js for timeago --- .../javascripts/lib/utils/datetime_utility.js | 36 +++++++++++----------- ...47182-use-the-default-strings-of-timeago-js.yml | 5 +++ .../admin/admin_uses_repository_checks_spec.rb | 2 +- .../merge_request/user_posts_notes_spec.rb | 2 +- 4 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 changelogs/unreleased/47182-use-the-default-strings-of-timeago-js.yml diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 0ff23bbb061..c3644fb7ff6 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -79,37 +79,37 @@ export function getTimeago() { if (!timeagoInstance) { const localeRemaining = function getLocaleRemaining(number, index) { return [ - [s__('Timeago|less than a minute ago'), s__('Timeago|right now')], - [s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')], - [s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')], + [s__('Timeago|just now'), s__('Timeago|right now')], + [s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')], + [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')], [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')], - [s__('Timeago|about an hour ago'), s__('Timeago|1 hour remaining')], - [s__('Timeago|about %s hours ago'), s__('Timeago|%s hours remaining')], - [s__('Timeago|a day ago'), s__('Timeago|1 day remaining')], + [s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')], + [s__('Timeago|%s hours ago'), s__('Timeago|%s hours remaining')], + [s__('Timeago|1 day ago'), s__('Timeago|1 day remaining')], [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')], - [s__('Timeago|a week ago'), s__('Timeago|1 week remaining')], + [s__('Timeago|1 week ago'), s__('Timeago|1 week remaining')], [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')], - [s__('Timeago|a month ago'), s__('Timeago|1 month remaining')], + [s__('Timeago|1 month ago'), s__('Timeago|1 month remaining')], [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')], - [s__('Timeago|a year ago'), s__('Timeago|1 year remaining')], + [s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')], [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')], ][index]; }; const locale = function getLocale(number, index) { return [ - [s__('Timeago|less than a minute ago'), s__('Timeago|right now')], - [s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')], - [s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')], + [s__('Timeago|just now'), s__('Timeago|right now')], + [s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')], + [s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')], [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')], - [s__('Timeago|about an hour ago'), s__('Timeago|in 1 hour')], - [s__('Timeago|about %s hours ago'), s__('Timeago|in %s hours')], - [s__('Timeago|a day ago'), s__('Timeago|in 1 day')], + [s__('Timeago|1 hour ago'), s__('Timeago|in 1 hour')], + [s__('Timeago|%s hours ago'), s__('Timeago|in %s hours')], + [s__('Timeago|1 day ago'), s__('Timeago|in 1 day')], [s__('Timeago|%s days ago'), s__('Timeago|in %s days')], - [s__('Timeago|a week ago'), s__('Timeago|in 1 week')], + [s__('Timeago|1 week ago'), s__('Timeago|in 1 week')], [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')], - [s__('Timeago|a month ago'), s__('Timeago|in 1 month')], + [s__('Timeago|1 month ago'), s__('Timeago|in 1 month')], [s__('Timeago|%s months ago'), s__('Timeago|in %s months')], - [s__('Timeago|a year ago'), s__('Timeago|in 1 year')], + [s__('Timeago|1 year ago'), s__('Timeago|in 1 year')], [s__('Timeago|%s years ago'), s__('Timeago|in %s years')], ][index]; }; diff --git a/changelogs/unreleased/47182-use-the-default-strings-of-timeago-js.yml b/changelogs/unreleased/47182-use-the-default-strings-of-timeago-js.yml new file mode 100644 index 00000000000..010b1db5aac --- /dev/null +++ b/changelogs/unreleased/47182-use-the-default-strings-of-timeago-js.yml @@ -0,0 +1,5 @@ +--- +title: Use the default strings of timeago.js for timeago +merge_request: 19350 +author: Takuya Noguchi +type: other diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 90cf5a53787..7371a494d36 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -28,7 +28,7 @@ feature 'Admin uses repository checks' do visit_admin_project_page(project) page.within('.alert') do - expect(page.text).to match(/Last repository check \(.* ago\) failed/) + expect(page.text).to match(/Last repository check \(just now\) failed/) end end diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb index b54addce993..3bd9f5e2298 100644 --- a/spec/features/merge_request/user_posts_notes_spec.rb +++ b/spec/features/merge_request/user_posts_notes_spec.rb @@ -139,7 +139,7 @@ describe 'Merge request > User posts notes', :js do page.within("#note_#{note.id}") do is_expected.to have_css('.note_edited_ago') expect(find('.note_edited_ago').text) - .to match(/less than a minute ago/) + .to match(/just now/) end end end -- cgit v1.2.1 From b0ec77663254f4a0c8abccd7ee9fdde23a55fb27 Mon Sep 17 00:00:00 2001 From: Ash McKenzie Date: Mon, 4 Jun 2018 12:23:18 +1000 Subject: Bump octokit to 4.9 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index e3e7ef2aa27..ae1301c335a 100644 --- a/Gemfile +++ b/Gemfile @@ -384,7 +384,7 @@ group :test do gem 'test-prof', '~> 0.2.5' end -gem 'octokit', '~> 4.8' +gem 'octokit', '~> 4.9' gem 'mail_room', '~> 0.9.1' diff --git a/Gemfile.lock b/Gemfile.lock index 767472a8a7a..e6e8f3d11bc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -517,7 +517,7 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - octokit (4.8.0) + octokit (4.9.0) sawyer (~> 0.8.0, >= 0.5.3) omniauth (1.8.1) hashie (>= 3.4.6, < 3.6.0) @@ -1084,7 +1084,7 @@ DEPENDENCIES net-ssh (~> 4.2.0) nokogiri (~> 1.8.2) oauth2 (~> 1.4) - octokit (~> 4.8) + octokit (~> 4.9) omniauth (~> 1.8) omniauth-auth0 (~> 2.0.0) omniauth-authentiq (~> 0.3.3) -- cgit v1.2.1 From eb05d475b7e82b943f5a72b8adf41b9bce519382 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 4 Jun 2018 12:12:02 +0900 Subject: Fix wording in spec. Add PIPELINE_IID in examples of debugged variables in documants. --- doc/ci/variables/README.md | 3 +++ spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index dfea10314b9..aa4395b01a9 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -353,6 +353,8 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach ++ CI_PROJECT_URL=https://example.com/gitlab-examples/ci-debug-trace ++ export CI_PIPELINE_ID=52666 ++ CI_PIPELINE_ID=52666 +++ export CI_PIPELINE_IID=123 +++ CI_PIPELINE_IID=123 ++ export CI_RUNNER_ID=1337 ++ CI_RUNNER_ID=1337 ++ export CI_RUNNER_DESCRIPTION=shared-runners-manager-1.example.com @@ -440,6 +442,7 @@ export CI_JOB_MANUAL="true" export CI_JOB_TRIGGERED="true" export CI_JOB_TOKEN="abcde-1234ABCD5678ef" export CI_PIPELINE_ID="1000" +export CI_PIPELINE_IID="10" export CI_PROJECT_ID="34" export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce" export CI_PROJECT_NAME="gitlab-ce" diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index e1766fc0ec9..c5a4d9b4778 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -143,7 +143,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do it 'wastes pipeline iid' do expect { step.perform! }.to raise_error - expect(InternalId.ci_pipelines.where(project_id: project.id).exists?).to be_truthy + expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0 end end end @@ -157,7 +157,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do end context 'when variables policy is specified' do - shared_examples_for 'populates pipeline according to used policies' do + shared_examples_for 'a correct pipeline' do it 'populates pipeline according to used policies' do step.perform! @@ -177,7 +177,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do build(:ci_pipeline, ref: 'master', config: config) end - it_behaves_like 'populates pipeline according to used policies' + it_behaves_like 'a correct pipeline' context 'when variables expression is specified' do context 'when pipeline iid is the subject' do @@ -186,7 +186,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do prod: { script: 'cap prod', only: { variables: ["$CI_PIPELINE_IID == '1000'"] } } } end - it_behaves_like 'populates pipeline according to used policies' + it_behaves_like 'a correct pipeline' end end end -- cgit v1.2.1 From dadf1209013a33f6eb9e4ca45d173337e2660df8 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 4 Jun 2018 03:49:01 +0000 Subject: Update _destroy.haml Use better descriptive text --- app/views/projects/pages/_destroy.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index 4ada19a1368..9b77c4e3494 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -5,7 +5,7 @@ .errors-holder .card-body %p - Removing the pages will prevent from exposing them to outside world. + Removing pages will prevent them from being exposed to the outside world. .form-actions = link_to 'Remove pages', project_pages_path(@project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" - else -- cgit v1.2.1 From 4022466e25cdfb1c320b425bb9bf810ffff1e417 Mon Sep 17 00:00:00 2001 From: Natho Date: Mon, 4 Jun 2018 14:08:35 +0930 Subject: Update IPs to valid example IPs. As per: https://tools.ietf.org/html/rfc5737 --- doc/administration/pages/index.md | 22 +++++++++++----------- doc/administration/pages/source.md | 18 +++++++++--------- doc/install/kubernetes/gitlab_omnibus.md | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index c0221533f13..9b1297ca4ba 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -83,12 +83,12 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the host that GitLab runs. For example, an entry would look like this: ``` -*.example.io. 1800 IN A 1.1.1.1 +*.example.io. 1800 IN A 192.0.2.1 *.example.io. 1800 IN AAAA 2001::1 ``` where `example.io` is the domain under which GitLab Pages will be served -and `1.1.1.1` is the IPv4 address of your GitLab instance and `2001::1` is the +and `192.0.2.1` is the IPv4 address of your GitLab instance and `2001::1` is the IPv6 address. If you don't have IPv6, you can omit the AAAA record. > **Note:** @@ -193,13 +193,13 @@ world. Custom domains are supported, but no TLS. ```shell pages_external_url "http://example.io" - nginx['listen_addresses'] = ['1.1.1.1'] + nginx['listen_addresses'] = ['192.0.2.1'] pages_nginx['enable'] = false - gitlab_pages['external_http'] = ['1.1.1.2:80', '[2001::2]:80'] + gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80'] ``` - where `1.1.1.1` is the primary IP address that GitLab is listening to and - `1.1.1.2` and `2001::2` are the secondary IPs the GitLab Pages daemon + where `192.0.2.1` is the primary IP address that GitLab is listening to and + `192.0.2.2` and `2001::2` are the secondary IPs the GitLab Pages daemon listens on. If you don't have IPv6, you can omit the IPv6 address. 1. [Reconfigure GitLab][reconfigure] @@ -228,16 +228,16 @@ world. Custom domains and TLS are supported. ```shell pages_external_url "https://example.io" - nginx['listen_addresses'] = ['1.1.1.1'] + nginx['listen_addresses'] = ['192.0.2.1'] pages_nginx['enable'] = false gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt" gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key" - gitlab_pages['external_http'] = ['1.1.1.2:80', '[2001::2]:80'] - gitlab_pages['external_https'] = ['1.1.1.2:443', '[2001::2]:443'] + gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80'] + gitlab_pages['external_https'] = ['192.0.2.2:443', '[2001::2]:443'] ``` - where `1.1.1.1` is the primary IP address that GitLab is listening to and - `1.1.1.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon + where `192.0.2.1` is the primary IP address that GitLab is listening to and + `192.0.2.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon listens on. If you don't have IPv6, you can omit the IPv6 address. 1. [Reconfigure GitLab][reconfigure] diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md index a45c3306457..4e40a7cb18d 100644 --- a/doc/administration/pages/source.md +++ b/doc/administration/pages/source.md @@ -67,11 +67,11 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the host that GitLab runs. For example, an entry would look like this: ``` -*.example.io. 1800 IN A 1.1.1.1 +*.example.io. 1800 IN A 192.0.2.1 ``` where `example.io` is the domain under which GitLab Pages will be served -and `1.1.1.1` is the IP address of your GitLab instance. +and `192.0.2.1` is the IP address of your GitLab instance. > **Note:** You should not use the GitLab domain to serve user pages. For more information @@ -253,7 +253,7 @@ world. Custom domains are supported, but no TLS. port: 80 https: false - external_http: 1.1.1.2:80 + external_http: 192.0.2.2:80 ``` 1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in @@ -263,7 +263,7 @@ world. Custom domains are supported, but no TLS. ``` gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80" + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80" ``` 1. Copy the `gitlab-pages-ssl` Nginx configuration file: @@ -274,7 +274,7 @@ world. Custom domains are supported, but no TLS. ``` 1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace - `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + `0.0.0.0` with `192.0.2.1`, where `192.0.2.1` the primary IP where GitLab listens to. 1. Restart NGINX 1. [Restart GitLab][restart] @@ -320,8 +320,8 @@ world. Custom domains and TLS are supported. port: 443 https: true - external_http: 1.1.1.2:80 - external_https: 1.1.1.2:443 + external_http: 192.0.2.2:80 + external_https: 192.0.2.2:443 ``` 1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in @@ -333,7 +333,7 @@ world. Custom domains and TLS are supported. ``` gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80 -listen-https 1.1.1.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80 -listen-https 192.0.2.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key ``` 1. Copy the `gitlab-pages-ssl` Nginx configuration file: @@ -344,7 +344,7 @@ world. Custom domains and TLS are supported. ``` 1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace - `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + `0.0.0.0` with `192.0.2.1`, where `192.0.2.1` the primary IP where GitLab listens to. 1. Restart NGINX 1. [Restart GitLab][restart] diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md index 98af87455ec..e1d1969651e 100644 --- a/doc/install/kubernetes/gitlab_omnibus.md +++ b/doc/install/kubernetes/gitlab_omnibus.md @@ -144,7 +144,7 @@ helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus or passing them on the command line: ```bash -helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus +helm install --name gitlab --set baseDomain=gitlab.io,baseIP=192.0.2.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus ``` ## Updating GitLab using the Helm Chart -- cgit v1.2.1 From 89b4304f12cd37d8715c274cdee080e95f2d3bad Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 29 May 2018 17:06:14 +0900 Subject: Add background migrations to arhive legacy traces --- .../20180529152628_archive_legacy_traces.rb | 44 ++++++++++++ db/schema.rb | 2 +- .../background_migration/archive_legacy_traces.rb | 79 ++++++++++++++++++++++ .../archive_legacy_traces_spec.rb | 60 ++++++++++++++++ spec/migrations/archive_legacy_traces_spec.rb | 45 ++++++++++++ 5 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 db/post_migrate/20180529152628_archive_legacy_traces.rb create mode 100644 lib/gitlab/background_migration/archive_legacy_traces.rb create mode 100644 spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb create mode 100644 spec/migrations/archive_legacy_traces_spec.rb diff --git a/db/post_migrate/20180529152628_archive_legacy_traces.rb b/db/post_migrate/20180529152628_archive_legacy_traces.rb new file mode 100644 index 00000000000..78ec1ab1a94 --- /dev/null +++ b/db/post_migrate/20180529152628_archive_legacy_traces.rb @@ -0,0 +1,44 @@ +class ArchiveLegacyTraces < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + BATCH_SIZE = 10_000 + BACKGROUND_MIGRATION_CLASS = 'ArchiveLegacyTraces' + + disable_ddl_transaction! + + class Build < ActiveRecord::Base + include EachBatch + self.table_name = 'ci_builds' + self.inheritance_column = :_type_disabled # Disable STI + + scope :finished, -> { where(status: [:success, :failed, :canceled]) } + + scope :without_new_traces, ->() do + where('NOT EXISTS (?)', + ::ArchiveLegacyTraces::JobArtifact.select(1).trace.where('ci_builds.id = ci_job_artifacts.job_id')) + end + end + + class JobArtifact < ActiveRecord::Base + self.table_name = 'ci_job_artifacts' + + enum file_type: { + archive: 1, + metadata: 2, + trace: 3 + } + end + + def up + queue_background_migration_jobs_by_range_at_intervals( + ::ArchiveLegacyTraces::Build.finished.without_new_traces, + BACKGROUND_MIGRATION_CLASS, + 5.minutes, + batch_size: BATCH_SIZE) + end + + def down + # noop + end +end diff --git a/db/schema.rb b/db/schema.rb index 97247387bc7..a8f8e14a3fc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180529093006) do +ActiveRecord::Schema.define(version: 20180529152628) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/lib/gitlab/background_migration/archive_legacy_traces.rb b/lib/gitlab/background_migration/archive_legacy_traces.rb new file mode 100644 index 00000000000..9741a7c181e --- /dev/null +++ b/lib/gitlab/background_migration/archive_legacy_traces.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true +# rubocop:disable Metrics/AbcSize +# rubocop:disable Style/Documentation + +module Gitlab + module BackgroundMigration + class ArchiveLegacyTraces + class Build < ActiveRecord::Base + include ::HasStatus + + self.table_name = 'ci_builds' + self.inheritance_column = :_type_disabled # Disable STI + + belongs_to :project, foreign_key: :project_id, class_name: 'ArchiveLegacyTraces::Project' + has_one :job_artifacts_trace, -> () { where(file_type: ArchiveLegacyTraces::JobArtifact.file_types[:trace]) }, class_name: 'ArchiveLegacyTraces::JobArtifact', foreign_key: :job_id + has_many :trace_chunks, foreign_key: :build_id, class_name: 'ArchiveLegacyTraces::BuildTraceChunk' + + scope :finished, -> { where(status: [:success, :failed, :canceled]) } + + scope :without_new_traces, ->() do + finished.where('NOT EXISTS (?)', + BackgroundMigration::ArchiveLegacyTraces::JobArtifact.select(1).trace.where('ci_builds.id = ci_job_artifacts.job_id')) + end + + def trace + ::Gitlab::Ci::Trace.new(self) + end + + def trace=(data) + raise NotImplementedError + end + + def old_trace + read_attribute(:trace) + end + + def erase_old_trace! + update_column(:trace, nil) + end + end + + class JobArtifact < ActiveRecord::Base + self.table_name = 'ci_job_artifacts' + + belongs_to :build + belongs_to :project + + mount_uploader :file, JobArtifactUploader + + enum file_type: { + archive: 1, + metadata: 2, + trace: 3 + } + end + + class BuildTraceChunk < ActiveRecord::Base + self.table_name = 'ci_build_trace_chunks' + + belongs_to :build + end + + class Project < ActiveRecord::Base + self.table_name = 'projects' + + has_many :builds, foreign_key: :project_id, class_name: 'ArchiveLegacyTraces::Build' + end + + def perform(start_id, stop_id) + BackgroundMigration::ArchiveLegacyTraces::Build + .finished + .without_new_traces + .where(id: (start_id..stop_id)).find_each do |build| + build.trace.archive! + end + end + end + end +end diff --git a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb new file mode 100644 index 00000000000..ecc6eea9284 --- /dev/null +++ b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 20180529152628 do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:builds) { table(:ci_builds) } + let(:job_artifacts) { table(:ci_job_artifacts) } + + before do + namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1') + projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123) + build = builds.create!(id: 1, project_id: 123, status: 'success') + + @legacy_trace_dir = File.join(Settings.gitlab_ci.builds_path, + build.created_at.utc.strftime("%Y_%m"), + build.project_id.to_s) + + FileUtils.mkdir_p(@legacy_trace_dir) + + @legacy_trace_path = File.join(@legacy_trace_dir, "#{build.id}.log") + end + + context 'when trace file exsits at the right place' do + before do + File.open(@legacy_trace_path, 'wb') { |stream| stream.write('aiueo') } + end + + it 'correctly archive legacy traces' do + expect(job_artifacts.count).to eq(0) + expect(File.exist?(@legacy_trace_path)).to be_truthy + + described_class.new.perform(1, 1) + + expect(job_artifacts.count).to eq(1) + expect(File.exist?(@legacy_trace_path)).to be_falsy + expect(File.read(new_trace_path)).to eq('aiueo') + end + end + + context 'when trace file does not exsits at the right place' do + it 'correctly archive legacy traces' do + expect(job_artifacts.count).to eq(0) + expect(File.exist?(@legacy_trace_path)).to be_falsy + + described_class.new.perform(1, 1) + + expect(job_artifacts.count).to eq(0) + end + end + + def new_trace_path + job_artifact = job_artifacts.first + + disk_hash = Digest::SHA2.hexdigest(job_artifact.project_id.to_s) + creation_date = job_artifact.created_at.utc.strftime('%Y_%m_%d') + + File.join(Gitlab.config.artifacts.path, disk_hash[0..1], disk_hash[2..3], disk_hash, + creation_date, job_artifact.job_id.to_s, job_artifact.id.to_s, 'job.log') + end +end diff --git a/spec/migrations/archive_legacy_traces_spec.rb b/spec/migrations/archive_legacy_traces_spec.rb new file mode 100644 index 00000000000..fc61c4bec17 --- /dev/null +++ b/spec/migrations/archive_legacy_traces_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180529152628_archive_legacy_traces') + +describe ArchiveLegacyTraces, :migration do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:builds) { table(:ci_builds) } + let(:job_artifacts) { table(:ci_job_artifacts) } + + before do + namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1') + projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123) + build = builds.create!(id: 1) + + @legacy_trace_path = File.join( + Settings.gitlab_ci.builds_path, + build.created_at.utc.strftime("%Y_%m"), + build.project_id.to_s, + "#{job.id}.log" + ) + + File.open(@legacy_trace_path, 'wb') { |stream| stream.write('aiueo') } + end + + it 'correctly archive legacy traces' do + expect(job_artifacts.count).to eq(0) + expect(File.exist?(@legacy_trace_path)).to be_truthy + + migrate! + + expect(job_artifacts.count).to eq(1) + expect(File.exist?(@legacy_trace_path)).to be_falsy + expect(File.exist?(new_trace_path)).to be_truthy + end + + def new_trace_path + job_artifact = job_artifacts.first + + disk_hash = Digest::SHA2.hexdigest(job_artifact.project_id.to_s) + creation_date = job_artifact.created_at.utc.strftime('%Y_%m_%d') + + File.join(disk_hash[0..1], disk_hash[2..3], disk_hash, + creation_date, job_artifact.job_id.to_s, job_artifact.id.to_s) + end +end -- cgit v1.2.1 From b626fcc045dda7ac25b1b7d01229589d8fd00521 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sun, 3 Jun 2018 12:08:54 +0900 Subject: Add changelog --- .../unreleased/add-background-migrations-for-not-archived-traces.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/add-background-migrations-for-not-archived-traces.yml diff --git a/changelogs/unreleased/add-background-migrations-for-not-archived-traces.yml b/changelogs/unreleased/add-background-migrations-for-not-archived-traces.yml new file mode 100644 index 00000000000..b1b23c477df --- /dev/null +++ b/changelogs/unreleased/add-background-migrations-for-not-archived-traces.yml @@ -0,0 +1,5 @@ +--- +title: Add background migrations for archiving legacy job traces +merge_request: 19194 +author: +type: performance -- cgit v1.2.1 From 0d00d02e842a0c4b22d213e00143a08d97597000 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sun, 3 Jun 2018 14:21:50 +0900 Subject: Directly refer application code from migration code --- app/models/ci/build.rb | 5 ++ .../20180529152628_archive_legacy_traces.rb | 19 ++---- .../background_migration/archive_legacy_traces.rb | 77 ++++------------------ lib/tasks/gitlab/traces.rake | 4 +- .../archive_legacy_traces_spec.rb | 2 +- 5 files changed, 22 insertions(+), 85 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 75fd55a8f7b..6cbf9108244 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -55,6 +55,11 @@ module Ci where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)', '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive) end + + scope :without_archived_trace, ->() do + where('NOT EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace) + end + scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) } scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) } diff --git a/db/post_migrate/20180529152628_archive_legacy_traces.rb b/db/post_migrate/20180529152628_archive_legacy_traces.rb index 78ec1ab1a94..12f219f606c 100644 --- a/db/post_migrate/20180529152628_archive_legacy_traces.rb +++ b/db/post_migrate/20180529152628_archive_legacy_traces.rb @@ -2,7 +2,7 @@ class ArchiveLegacyTraces < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers DOWNTIME = false - BATCH_SIZE = 10_000 + BATCH_SIZE = 5000 BACKGROUND_MIGRATION_CLASS = 'ArchiveLegacyTraces' disable_ddl_transaction! @@ -14,25 +14,14 @@ class ArchiveLegacyTraces < ActiveRecord::Migration scope :finished, -> { where(status: [:success, :failed, :canceled]) } - scope :without_new_traces, ->() do - where('NOT EXISTS (?)', - ::ArchiveLegacyTraces::JobArtifact.select(1).trace.where('ci_builds.id = ci_job_artifacts.job_id')) + scope :without_archived_trace, -> do + where('NOT EXISTS (SELECT 1 FROM ci_job_artifacts WHERE ci_builds.id = ci_job_artifacts.job_id AND ci_job_artifacts.file_type = 3)') end end - class JobArtifact < ActiveRecord::Base - self.table_name = 'ci_job_artifacts' - - enum file_type: { - archive: 1, - metadata: 2, - trace: 3 - } - end - def up queue_background_migration_jobs_by_range_at_intervals( - ::ArchiveLegacyTraces::Build.finished.without_new_traces, + ::ArchiveLegacyTraces::Build.finished.without_archived_trace, BACKGROUND_MIGRATION_CLASS, 5.minutes, batch_size: BATCH_SIZE) diff --git a/lib/gitlab/background_migration/archive_legacy_traces.rb b/lib/gitlab/background_migration/archive_legacy_traces.rb index 9741a7c181e..0999ea95e56 100644 --- a/lib/gitlab/background_migration/archive_legacy_traces.rb +++ b/lib/gitlab/background_migration/archive_legacy_traces.rb @@ -5,73 +5,18 @@ module Gitlab module BackgroundMigration class ArchiveLegacyTraces - class Build < ActiveRecord::Base - include ::HasStatus - - self.table_name = 'ci_builds' - self.inheritance_column = :_type_disabled # Disable STI - - belongs_to :project, foreign_key: :project_id, class_name: 'ArchiveLegacyTraces::Project' - has_one :job_artifacts_trace, -> () { where(file_type: ArchiveLegacyTraces::JobArtifact.file_types[:trace]) }, class_name: 'ArchiveLegacyTraces::JobArtifact', foreign_key: :job_id - has_many :trace_chunks, foreign_key: :build_id, class_name: 'ArchiveLegacyTraces::BuildTraceChunk' - - scope :finished, -> { where(status: [:success, :failed, :canceled]) } - - scope :without_new_traces, ->() do - finished.where('NOT EXISTS (?)', - BackgroundMigration::ArchiveLegacyTraces::JobArtifact.select(1).trace.where('ci_builds.id = ci_job_artifacts.job_id')) - end - - def trace - ::Gitlab::Ci::Trace.new(self) - end - - def trace=(data) - raise NotImplementedError - end - - def old_trace - read_attribute(:trace) - end - - def erase_old_trace! - update_column(:trace, nil) - end - end - - class JobArtifact < ActiveRecord::Base - self.table_name = 'ci_job_artifacts' - - belongs_to :build - belongs_to :project - - mount_uploader :file, JobArtifactUploader - - enum file_type: { - archive: 1, - metadata: 2, - trace: 3 - } - end - - class BuildTraceChunk < ActiveRecord::Base - self.table_name = 'ci_build_trace_chunks' - - belongs_to :build - end - - class Project < ActiveRecord::Base - self.table_name = 'projects' - - has_many :builds, foreign_key: :project_id, class_name: 'ArchiveLegacyTraces::Build' - end - def perform(start_id, stop_id) - BackgroundMigration::ArchiveLegacyTraces::Build - .finished - .without_new_traces - .where(id: (start_id..stop_id)).find_each do |build| - build.trace.archive! + # This background migrations directly refer ::Ci::Build model which is defined in application code. + # In general, migration code should be isolated as much as possible in order to be idempotent. + # However, `archive!` logic is too complicated to be replicated. So we chose a way to refer ::Ci::Build directly + # and we don't change the `archive!` logic until 11.1 + ::Ci::Build.finished.without_archived_trace + .where(id: start_id..stop_id).find_each do |build| + begin + build.trace.archive! + rescue => e + Rails.logger.error "Failed to archive live trace. id: #{build.id} message: #{e.message}" + end end end end diff --git a/lib/tasks/gitlab/traces.rake b/lib/tasks/gitlab/traces.rake index fd2a4f2d11a..ddcca69711f 100644 --- a/lib/tasks/gitlab/traces.rake +++ b/lib/tasks/gitlab/traces.rake @@ -8,9 +8,7 @@ namespace :gitlab do logger = Logger.new(STDOUT) logger.info('Archiving legacy traces') - Ci::Build.finished - .where('NOT EXISTS (?)', - Ci::JobArtifact.select(1).trace.where('ci_builds.id = ci_job_artifacts.job_id')) + Ci::Build.finished.without_archived_trace .order(id: :asc) .find_in_batches(batch_size: 1000) do |jobs| job_ids = jobs.map { |job| [job.id] } diff --git a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb index ecc6eea9284..1a62b30ce81 100644 --- a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb +++ b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 2 @legacy_trace_dir = File.join(Settings.gitlab_ci.builds_path, build.created_at.utc.strftime("%Y_%m"), build.project_id.to_s) - + FileUtils.mkdir_p(@legacy_trace_dir) @legacy_trace_path = File.join(@legacy_trace_dir, "#{build.id}.log") -- cgit v1.2.1 From bcd664f53a4009bc752fbc47e1c4d6f76c0b8cc2 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sun, 3 Jun 2018 15:04:47 +0900 Subject: Fix specs. Rename migration file name which was conflicted with background migration's. --- .../20180529152628_archive_legacy_traces.rb | 33 ---------------- ...0529152628_schedule_to_archive_legacy_traces.rb | 33 ++++++++++++++++ .../archive_legacy_traces_spec.rb | 35 ++++------------- spec/migrations/archive_legacy_traces_spec.rb | 45 ---------------------- .../schedule_to_archive_legacy_traces_spec.rb | 45 ++++++++++++++++++++++ spec/support/trace/trace_helpers.rb | 23 +++++++++++ 6 files changed, 109 insertions(+), 105 deletions(-) delete mode 100644 db/post_migrate/20180529152628_archive_legacy_traces.rb create mode 100644 db/post_migrate/20180529152628_schedule_to_archive_legacy_traces.rb delete mode 100644 spec/migrations/archive_legacy_traces_spec.rb create mode 100644 spec/migrations/schedule_to_archive_legacy_traces_spec.rb create mode 100644 spec/support/trace/trace_helpers.rb diff --git a/db/post_migrate/20180529152628_archive_legacy_traces.rb b/db/post_migrate/20180529152628_archive_legacy_traces.rb deleted file mode 100644 index 12f219f606c..00000000000 --- a/db/post_migrate/20180529152628_archive_legacy_traces.rb +++ /dev/null @@ -1,33 +0,0 @@ -class ArchiveLegacyTraces < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - BATCH_SIZE = 5000 - BACKGROUND_MIGRATION_CLASS = 'ArchiveLegacyTraces' - - disable_ddl_transaction! - - class Build < ActiveRecord::Base - include EachBatch - self.table_name = 'ci_builds' - self.inheritance_column = :_type_disabled # Disable STI - - scope :finished, -> { where(status: [:success, :failed, :canceled]) } - - scope :without_archived_trace, -> do - where('NOT EXISTS (SELECT 1 FROM ci_job_artifacts WHERE ci_builds.id = ci_job_artifacts.job_id AND ci_job_artifacts.file_type = 3)') - end - end - - def up - queue_background_migration_jobs_by_range_at_intervals( - ::ArchiveLegacyTraces::Build.finished.without_archived_trace, - BACKGROUND_MIGRATION_CLASS, - 5.minutes, - batch_size: BATCH_SIZE) - end - - def down - # noop - end -end diff --git a/db/post_migrate/20180529152628_schedule_to_archive_legacy_traces.rb b/db/post_migrate/20180529152628_schedule_to_archive_legacy_traces.rb new file mode 100644 index 00000000000..ea782e1596b --- /dev/null +++ b/db/post_migrate/20180529152628_schedule_to_archive_legacy_traces.rb @@ -0,0 +1,33 @@ +class ScheduleToArchiveLegacyTraces < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + BATCH_SIZE = 5000 + BACKGROUND_MIGRATION_CLASS = 'ArchiveLegacyTraces' + + disable_ddl_transaction! + + class Build < ActiveRecord::Base + include EachBatch + self.table_name = 'ci_builds' + self.inheritance_column = :_type_disabled # Disable STI + + scope :finished, -> { where(status: [:success, :failed, :canceled]) } + + scope :without_archived_trace, -> do + where('NOT EXISTS (SELECT 1 FROM ci_job_artifacts WHERE ci_builds.id = ci_job_artifacts.job_id AND ci_job_artifacts.file_type = 3)') + end + end + + def up + queue_background_migration_jobs_by_range_at_intervals( + ::ScheduleToArchiveLegacyTraces::Build.finished.without_archived_trace, + BACKGROUND_MIGRATION_CLASS, + 5.minutes, + batch_size: BATCH_SIZE) + end + + def down + # noop + end +end diff --git a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb index 1a62b30ce81..0765f4149f9 100644 --- a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb +++ b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 20180529152628 do + include TraceHelpers + let(:namespaces) { table(:namespaces) } let(:projects) { table(:projects) } let(:builds) { table(:ci_builds) } @@ -9,52 +11,31 @@ describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 2 before do namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1') projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123) - build = builds.create!(id: 1, project_id: 123, status: 'success') - - @legacy_trace_dir = File.join(Settings.gitlab_ci.builds_path, - build.created_at.utc.strftime("%Y_%m"), - build.project_id.to_s) - - FileUtils.mkdir_p(@legacy_trace_dir) - - @legacy_trace_path = File.join(@legacy_trace_dir, "#{build.id}.log") + @build = builds.create!(id: 1, project_id: 123, status: 'success', type: 'Ci::Build') end context 'when trace file exsits at the right place' do before do - File.open(@legacy_trace_path, 'wb') { |stream| stream.write('aiueo') } + create_legacy_trace(@build, 'aiueo') end it 'correctly archive legacy traces' do expect(job_artifacts.count).to eq(0) - expect(File.exist?(@legacy_trace_path)).to be_truthy + expect(File.exist?(legacy_trace_path(@build))).to be_truthy described_class.new.perform(1, 1) expect(job_artifacts.count).to eq(1) - expect(File.exist?(@legacy_trace_path)).to be_falsy - expect(File.read(new_trace_path)).to eq('aiueo') + expect(File.exist?(legacy_trace_path(@build))).to be_falsy + expect(File.read(archived_trace_path(job_artifacts.first))).to eq('aiueo') end end context 'when trace file does not exsits at the right place' do - it 'correctly archive legacy traces' do - expect(job_artifacts.count).to eq(0) - expect(File.exist?(@legacy_trace_path)).to be_falsy - + it 'does not raise errors and create job artifact row' do described_class.new.perform(1, 1) expect(job_artifacts.count).to eq(0) end end - - def new_trace_path - job_artifact = job_artifacts.first - - disk_hash = Digest::SHA2.hexdigest(job_artifact.project_id.to_s) - creation_date = job_artifact.created_at.utc.strftime('%Y_%m_%d') - - File.join(Gitlab.config.artifacts.path, disk_hash[0..1], disk_hash[2..3], disk_hash, - creation_date, job_artifact.job_id.to_s, job_artifact.id.to_s, 'job.log') - end end diff --git a/spec/migrations/archive_legacy_traces_spec.rb b/spec/migrations/archive_legacy_traces_spec.rb deleted file mode 100644 index fc61c4bec17..00000000000 --- a/spec/migrations/archive_legacy_traces_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20180529152628_archive_legacy_traces') - -describe ArchiveLegacyTraces, :migration do - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:builds) { table(:ci_builds) } - let(:job_artifacts) { table(:ci_job_artifacts) } - - before do - namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1') - projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123) - build = builds.create!(id: 1) - - @legacy_trace_path = File.join( - Settings.gitlab_ci.builds_path, - build.created_at.utc.strftime("%Y_%m"), - build.project_id.to_s, - "#{job.id}.log" - ) - - File.open(@legacy_trace_path, 'wb') { |stream| stream.write('aiueo') } - end - - it 'correctly archive legacy traces' do - expect(job_artifacts.count).to eq(0) - expect(File.exist?(@legacy_trace_path)).to be_truthy - - migrate! - - expect(job_artifacts.count).to eq(1) - expect(File.exist?(@legacy_trace_path)).to be_falsy - expect(File.exist?(new_trace_path)).to be_truthy - end - - def new_trace_path - job_artifact = job_artifacts.first - - disk_hash = Digest::SHA2.hexdigest(job_artifact.project_id.to_s) - creation_date = job_artifact.created_at.utc.strftime('%Y_%m_%d') - - File.join(disk_hash[0..1], disk_hash[2..3], disk_hash, - creation_date, job_artifact.job_id.to_s, job_artifact.id.to_s) - end -end diff --git a/spec/migrations/schedule_to_archive_legacy_traces_spec.rb b/spec/migrations/schedule_to_archive_legacy_traces_spec.rb new file mode 100644 index 00000000000..d3eac3c45ea --- /dev/null +++ b/spec/migrations/schedule_to_archive_legacy_traces_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180529152628_schedule_to_archive_legacy_traces') + +describe ScheduleToArchiveLegacyTraces, :migration do + include TraceHelpers + + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:builds) { table(:ci_builds) } + let(:job_artifacts) { table(:ci_job_artifacts) } + + before do + namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1') + projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123) + @build_success = builds.create!(id: 1, project_id: 123, status: 'success', type: 'Ci::Build') + @build_failed = builds.create!(id: 2, project_id: 123, status: 'failed', type: 'Ci::Build') + @builds_canceled = builds.create!(id: 3, project_id: 123, status: 'canceled', type: 'Ci::Build') + @build_running = builds.create!(id: 4, project_id: 123, status: 'running', type: 'Ci::Build') + + create_legacy_trace(@build_success, 'This job is done') + create_legacy_trace(@build_failed, 'This job is done') + create_legacy_trace(@builds_canceled, 'This job is done') + create_legacy_trace(@build_running, 'This job is not done yet') + end + + it 'correctly archive legacy traces' do + expect(job_artifacts.count).to eq(0) + expect(File.exist?(legacy_trace_path(@build_success))).to be_truthy + expect(File.exist?(legacy_trace_path(@build_failed))).to be_truthy + expect(File.exist?(legacy_trace_path(@builds_canceled))).to be_truthy + expect(File.exist?(legacy_trace_path(@build_running))).to be_truthy + + migrate! + + expect(job_artifacts.count).to eq(3) + expect(File.exist?(legacy_trace_path(@build_success))).to be_falsy + expect(File.exist?(legacy_trace_path(@build_failed))).to be_falsy + expect(File.exist?(legacy_trace_path(@builds_canceled))).to be_falsy + expect(File.exist?(legacy_trace_path(@build_running))).to be_truthy + expect(File.exist?(archived_trace_path(job_artifacts.where(job_id: @build_success.id).first))).to be_truthy + expect(File.exist?(archived_trace_path(job_artifacts.where(job_id: @build_failed.id).first))).to be_truthy + expect(File.exist?(archived_trace_path(job_artifacts.where(job_id: @builds_canceled.id).first))).to be_truthy + expect(job_artifacts.where(job_id: @build_running.id)).not_to be_exist + end +end diff --git a/spec/support/trace/trace_helpers.rb b/spec/support/trace/trace_helpers.rb new file mode 100644 index 00000000000..f6d11b61038 --- /dev/null +++ b/spec/support/trace/trace_helpers.rb @@ -0,0 +1,23 @@ +module TraceHelpers + def create_legacy_trace(build, content) + File.open(legacy_trace_path(build), 'wb') { |stream| stream.write(content) } + end + + def legacy_trace_path(build) + legacy_trace_dir = File.join(Settings.gitlab_ci.builds_path, + build.created_at.utc.strftime("%Y_%m"), + build.project_id.to_s) + + FileUtils.mkdir_p(legacy_trace_dir) + + File.join(legacy_trace_dir, "#{build.id}.log") + end + + def archived_trace_path(job_artifact) + disk_hash = Digest::SHA2.hexdigest(job_artifact.project_id.to_s) + creation_date = job_artifact.created_at.utc.strftime('%Y_%m_%d') + + File.join(Gitlab.config.artifacts.path, disk_hash[0..1], disk_hash[2..3], disk_hash, + creation_date, job_artifact.job_id.to_s, job_artifact.id.to_s, 'job.log') + end +end -- cgit v1.2.1 From 623accf9cb3babb81d789a013e6ad87ad0bf26e5 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sun, 3 Jun 2018 15:09:49 +0900 Subject: Add type_build to ScheduleToArchiveLegacyTraces::Build --- db/post_migrate/20180529152628_schedule_to_archive_legacy_traces.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/db/post_migrate/20180529152628_schedule_to_archive_legacy_traces.rb b/db/post_migrate/20180529152628_schedule_to_archive_legacy_traces.rb index ea782e1596b..965cd3a8714 100644 --- a/db/post_migrate/20180529152628_schedule_to_archive_legacy_traces.rb +++ b/db/post_migrate/20180529152628_schedule_to_archive_legacy_traces.rb @@ -12,6 +12,8 @@ class ScheduleToArchiveLegacyTraces < ActiveRecord::Migration self.table_name = 'ci_builds' self.inheritance_column = :_type_disabled # Disable STI + scope :type_build, -> { where(type: 'Ci::Build') } + scope :finished, -> { where(status: [:success, :failed, :canceled]) } scope :without_archived_trace, -> do @@ -21,7 +23,7 @@ class ScheduleToArchiveLegacyTraces < ActiveRecord::Migration def up queue_background_migration_jobs_by_range_at_intervals( - ::ScheduleToArchiveLegacyTraces::Build.finished.without_archived_trace, + ::ScheduleToArchiveLegacyTraces::Build.type_build.finished.without_archived_trace, BACKGROUND_MIGRATION_CLASS, 5.minutes, batch_size: BATCH_SIZE) -- cgit v1.2.1 From 2184c753fd81925c74a3a30abe9be187aa8c4133 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sun, 3 Jun 2018 15:13:36 +0900 Subject: Revise comments in ArchiveLegacyTraces --- lib/gitlab/background_migration/archive_legacy_traces.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/background_migration/archive_legacy_traces.rb b/lib/gitlab/background_migration/archive_legacy_traces.rb index 0999ea95e56..5a4e5b2c471 100644 --- a/lib/gitlab/background_migration/archive_legacy_traces.rb +++ b/lib/gitlab/background_migration/archive_legacy_traces.rb @@ -6,10 +6,10 @@ module Gitlab module BackgroundMigration class ArchiveLegacyTraces def perform(start_id, stop_id) - # This background migrations directly refer ::Ci::Build model which is defined in application code. + # This background migration directly refers to ::Ci::Build model which is defined in application code. # In general, migration code should be isolated as much as possible in order to be idempotent. - # However, `archive!` logic is too complicated to be replicated. So we chose a way to refer ::Ci::Build directly - # and we don't change the `archive!` logic until 11.1 + # However, `archive!` method is too complicated to be replicated by coping its subsequent code. + # So we chose a way to use ::Ci::Build directly and we don't change the `archive!` method until 11.1 ::Ci::Build.finished.without_archived_trace .where(id: start_id..stop_id).find_each do |build| begin -- cgit v1.2.1 From 8f1f73d4e3ca82e3d449e478606f133d19ead7b1 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 4 Jun 2018 14:28:21 +0900 Subject: Fix typo in spec. Add a test for the case of when trace is stored in database --- .../archive_legacy_traces_spec.rb | 26 ++++++++++++++++++---- spec/support/trace/trace_helpers.rb | 4 ++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb index 0765f4149f9..877c061d11b 100644 --- a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb +++ b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb @@ -16,7 +16,7 @@ describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 2 context 'when trace file exsits at the right place' do before do - create_legacy_trace(@build, 'aiueo') + create_legacy_trace(@build, 'trace in file') end it 'correctly archive legacy traces' do @@ -27,15 +27,33 @@ describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 2 expect(job_artifacts.count).to eq(1) expect(File.exist?(legacy_trace_path(@build))).to be_falsy - expect(File.read(archived_trace_path(job_artifacts.first))).to eq('aiueo') + expect(File.read(archived_trace_path(job_artifacts.first))).to eq('trace in file') end end context 'when trace file does not exsits at the right place' do - it 'does not raise errors and create job artifact row' do - described_class.new.perform(1, 1) + it 'does not raise errors nor create job artifact' do + expect { described_class.new.perform(1, 1) }.not_to raise_error expect(job_artifacts.count).to eq(0) end end + + context 'when trace data exsits in database' do + before do + create_legacy_trace_in_db(@build, 'trace in db') + end + + it 'correctly archive legacy traces' do + expect(job_artifacts.count).to eq(0) + expect(@build.read_attribute(:trace)).not_to be_empty + + described_class.new.perform(1, 1) + + @build.reload + expect(job_artifacts.count).to eq(1) + expect(@build.read_attribute(:trace)).to be_nil + expect(File.read(archived_trace_path(job_artifacts.first))).to eq('trace in db') + end + end end diff --git a/spec/support/trace/trace_helpers.rb b/spec/support/trace/trace_helpers.rb index f6d11b61038..c7802bbcb94 100644 --- a/spec/support/trace/trace_helpers.rb +++ b/spec/support/trace/trace_helpers.rb @@ -3,6 +3,10 @@ module TraceHelpers File.open(legacy_trace_path(build), 'wb') { |stream| stream.write(content) } end + def create_legacy_trace_in_db(build, content) + build.update_column(:trace, content) + end + def legacy_trace_path(build) legacy_trace_dir = File.join(Settings.gitlab_ci.builds_path, build.created_at.utc.strftime("%Y_%m"), -- cgit v1.2.1 From 4bfd208b9f1738a67cac149ccac7ec2153c43448 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 4 Jun 2018 08:31:03 +0200 Subject: Bump gitlab-shell to 7.1.3 This includes the change that prints the @username of a user instead of the full name. https://gitlab.com/gitlab-org/gitlab-shell/merge_requests/204 --- GITLAB_SHELL_VERSION | 2 +- changelogs/unreleased/bvl-bump-gitlab-shell-7-1-3.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/bvl-bump-gitlab-shell-7-1-3.yml diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index a8a18875682..1996c504476 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -7.1.2 +7.1.3 diff --git a/changelogs/unreleased/bvl-bump-gitlab-shell-7-1-3.yml b/changelogs/unreleased/bvl-bump-gitlab-shell-7-1-3.yml new file mode 100644 index 00000000000..76bb25bc7d7 --- /dev/null +++ b/changelogs/unreleased/bvl-bump-gitlab-shell-7-1-3.yml @@ -0,0 +1,5 @@ +--- +title: Include username in output when testing SSH to GitLab +merge_request: 19358 +author: +type: other -- cgit v1.2.1 From 8008442829ea797b822d7c782334f6b2d319ca86 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 4 Jun 2018 08:30:31 +0000 Subject: Use gitlab-shell 7.1.4 --- GITLAB_SHELL_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index a8a18875682..b7f8ee41e69 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -7.1.2 +7.1.4 -- cgit v1.2.1 From 7b354784fd53014ac1227ba5c8076bea737d0fc0 Mon Sep 17 00:00:00 2001 From: *Kim Date: Mon, 4 Jun 2018 09:06:40 +0000 Subject: Add .activities class to group activity .nav-block --- app/views/groups/_activities.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml index fd6e7111f38..577c63503a8 100644 --- a/app/views/groups/_activities.html.haml +++ b/app/views/groups/_activities.html.haml @@ -1,4 +1,4 @@ -.nav-block +.nav-block.activities .controls = link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do %i.fa.fa-rss -- cgit v1.2.1 From 114c26ccf0f10788271c6108774e72809a7f93e1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 4 Jun 2018 11:29:39 +0200 Subject: Raise error if pipeline / stage hits unknown status --- app/models/ci/pipeline.rb | 6 +++++- app/models/ci/stage.rb | 4 ++++ app/models/concerns/has_status.rb | 2 ++ spec/models/ci/pipeline_spec.rb | 37 +++++++++++++++++++++++++++++++++++++ spec/models/ci/stage_spec.rb | 13 +++++++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 94a548181d9..1274d2a5f16 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -517,7 +517,8 @@ module Ci def update_status retry_optimistic_lock(self) do - case latest_builds_status + case latest_builds_status.to_s + when 'created' then nil when 'pending' then enqueue when 'running' then run when 'success' then succeed @@ -525,6 +526,9 @@ module Ci when 'canceled' then cancel when 'skipped' then skip when 'manual' then block + else + raise HasStatus::UnknownStatusError, + "Unknown status `#{latest_builds_status}`" end end end diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index abf73c00539..ea07f37e6c1 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -68,6 +68,7 @@ module Ci def update_status retry_optimistic_lock(self) do case statuses.latest.status + when 'created' then nil when 'pending' then enqueue when 'running' then run when 'success' then succeed @@ -75,6 +76,9 @@ module Ci when 'canceled' then cancel when 'manual' then block when 'skipped', nil then skip + else + raise HasStatus::UnknownStatusError, + "Unknown status `#{statuses.latest.status}`" end end end diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 7c3ed96bc28..72c236a0fc7 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -11,6 +11,8 @@ module HasStatus STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3, failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze + UnknownStatusError = Class.new(StandardError) + class_methods do def status_sql scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index a29fa0c4cae..decfef6c8cc 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1251,6 +1251,43 @@ describe Ci::Pipeline, :mailer do end end + describe '#update_status' do + context 'when pipeline is empty' do + it 'updates does not change pipeline status' do + expect(pipeline.statuses.latest.status).to be_nil + + expect { pipeline.update_status } + .to change { pipeline.reload.status }.to 'skipped' + end + end + + context 'when updating status to pending' do + before do + allow(pipeline) + .to receive_message_chain(:statuses, :latest, :status) + .and_return(:running) + end + + it 'updates pipeline status to running' do + expect { pipeline.update_status } + .to change { pipeline.reload.status }.to 'running' + end + end + + context 'when statuses status was not recognized' do + before do + allow(pipeline) + .to receive(:latest_builds_status) + .and_return(:unknown) + end + + it 'raises an exception' do + expect { pipeline.update_status } + .to raise_error(HasStatus::UnknownStatusError) + end + end + end + describe '#detailed_status' do subject { pipeline.detailed_status(user) } diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index b40496252b4..22a4556c10c 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -110,6 +110,19 @@ describe Ci::Stage, :models do expect(stage.reload).to be_failed end end + + context 'when statuses status was not recognized' do + before do + allow(stage) + .to receive_message_chain(:statuses, :latest, :status) + .and_return(:unknown) + end + + it 'raises an exception' do + expect { stage.update_status } + .to raise_error(HasStatus::UnknownStatusError) + end + end end describe '#detailed_status' do -- cgit v1.2.1 From 20b04cc07eab0b0d6108a1bb56f4575b92720b95 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Mon, 4 Jun 2018 07:54:25 +0000 Subject: Update GitHub import instructions (github.md) --- doc/user/project/import/github.md | 172 ++++++++++++++------------------------ 1 file changed, 61 insertions(+), 111 deletions(-) diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md index 8c639bd5343..6757ba7061d 100644 --- a/doc/user/project/import/github.md +++ b/doc/user/project/import/github.md @@ -1,154 +1,104 @@ # Import your project from GitHub to GitLab -Import your projects from GitHub to GitLab with minimal effort. - ## Overview ->**Note:** -If you are an administrator you can enable the [GitHub integration][gh-import] -in your GitLab instance sitewide. This configuration is optional, users will -still be able to import their GitHub repositories with a -[personal access token][gh-token]. - ->**Note:** -Administrators of a GitLab instance (Community or Enterprise Edition) can also -use the [GitHub rake task][gh-rake] to import projects from GitHub without the -constrains of a Sidekiq worker. - -- At its current state, GitHub importer can import: - - the repository description (GitLab 7.7+) - - the Git repository data (GitLab 7.7+) - - the issues (GitLab 7.7+) - - the pull requests (GitLab 8.4+) - - the wiki pages (GitLab 8.4+) - - the milestones (GitLab 8.7+) - - the labels (GitLab 8.7+) - - the release note descriptions (GitLab 8.12+) - - the pull request review comments (GitLab 10.2+) - - the regular issue and pull request comments -- References to pull requests and issues are preserved (GitLab 8.7+) -- Repository public access is retained. If a repository is private in GitHub - it will be created as private in GitLab as well. +Using the importer, you can import your GitHub repositories to GitLab.com or to your self-hosted GitLab instance. -## How it works +>**Note:** While these instructions will always work for users on gitlab.com, if you are an administrator of a self-hosted GitLab instance, you will need to enable the [GitHub integration](https://docs.gitlab.com/ee/integration/github.html) in order for users to follow the preferred import method described on this page. If this is not enabled, users can alternatively import their GitHub repositories using a [personal access token](https://docs.gitlab.com/ee/user/project/import/github.html#authorize-access-to-your-repositories-using-a-personal-access-token) from GitHub, but this method will not be able to associate all user activity (such as issues and pull requests) with matching GitLab users. -When issues/pull requests are being imported, the GitHub importer tries to find -the GitHub author/assignee in GitLab's database using the GitHub ID. For this -to work, the GitHub author/assignee should have signed in beforehand in GitLab -and **associated their GitHub account**. If the user is not -found in GitLab's database, the project creator (most of the times the current -user that started the import process) is set as the author, but a reference on -the issue about the original GitHub author is kept. +>As an administrator of a self-hosted GitLab instance, you can also use the [GitHub rake task](https://docs.gitlab.com/ee/administration/raketasks/github_import.html) to import projects from GitHub without the constraints of a Sidekiq worker. -The importer will create any new namespaces (groups) if they don't exist or in -the case the namespace is taken, the repository will be imported under the user's -namespace that started the import process. +* The following aspects of a project are imported: + * repository description (GitLab.com & 7.7+) + * Git repository data (GitLab.com & 7.7+) + * issues (GitLab.com & 7.7+) + * pull requests (GitLab.com & 8.4+) + * wiki pages (GitLab.com & 8.4+) + * milestones (GitLab.com & 8.7+) + * labels (GitLab.com & 8.7+) + * release note descriptions (GitLab.com & 8.12+) + * pull request review comments (GitLab.com & 10.2+) + * regular issue and pull request comments +* References to pull requests and issues are preserved (GitLab.com & 8.7+) +* Each imported repository defaults to ‘private’ but can be made public, as needed. -The importer will also import branches on forks of projects related to open pull -requests. These branches will be imported with a naming scheme similar to -GH-SHA-Username/Pull-Request-number/fork-name/branch. This may lead to a discrepancy -in branches compared to the GitHub Repository. +## How it works -For a more technical description and an overview of the architecture you can -refer to [Working with the GitHub importer][gh-import-dev-docs]. +When issues and pull requests are being imported, the importer attempts to find their GitHub authors and assignees in the database of the GitLab instance. (Note that pull requests are called "merge requests" in GitLab.) -## Importing your GitHub repositories +For this association to succeed, prior to the import, each GitHub author and assignee in the repository must have either previously logged in to a GitLab account using the GitHub icon **or** have a GitHub account with a [public email address](https://help.github.com/articles/setting-your-commit-email-address-on-github/) that matches their GitLab account’s email address. -The importer page is visible when you create a new project. +If a user referenced in the project is not found in GitLab's database, the project creator (typically the user that initiated the import process) is set as the author/assignee, but a note on the issue mentioning the original GitHub author is added. -![New project page on GitLab](img/import_projects_from_new_project_page.png) +The importer creates any new namespaces (groups) if they do not exist, or, if the namespace is taken, the repository is imported under the namespace of the user who initiated the import process. The namespace/repository name can also be edited, with the proper permissions. -Click on the **GitHub** link and the import authorization process will start. -There are two ways to authorize access to your GitHub repositories: +The importer will also import branches on forks of projects related to open pull requests. These branches will be imported with a naming scheme similar to GH-`SHA-Username/Pull-Request-number/fork-name/branch`. This may lead to a discrepancy in branches compared to those of the GitHub repository. -1. [Using the GitHub integration][gh-integration] (if it's enabled by your - GitLab administrator). This is the preferred way as it's possible to - preserve the GitHub authors/assignees. Read more in the [How it works](#how-it-works) - section. -1. [Using a personal access token][gh-token] provided by GitHub. +For additional technical details, you can refer to the [GitHub Importer][gh-import-dev-docs] developer documentation. -![Select authentication method](img/import_projects_from_github_select_auth_method.png) +## Import your GitHub repository into GitLab -### Authorize access to your repositories using the GitHub integration +### Use the GitHub integration -If the [GitHub integration][gh-import] is enabled by your GitLab administrator, -you can use it instead of the personal access token. +Before you begin, ensure that any GitHub users who you want to map to GitLab users have either: +1. A GitLab account that has logged in using the GitHub icon. +\- or - +2. A GitLab account with an email address that matches the [public email address](https://help.github.com/articles/setting-your-commit-email-address-on-github/) of the GitHub user. -1. First you may want to connect your GitHub account to GitLab in order for - the username mapping to be correct. -1. Once you connect GitHub, click the **List your GitHub repositories** button - and you will be redirected to GitHub for permission to access your projects. -1. After accepting, you'll be automatically redirected to the importer. +User-matching attempts occur in that order, and if a user is not identified either way, the activity is associated with the user account that is performing the import. -You can now go on and [select which repositories to import](#select-which-repositories-to-import). +Note: If you are using a self-hosted GitLab instance, this process requires that you have configured the [GitHub integration][gh-import]. -### Authorize access to your repositories using a personal access token +1. From the top navigation bar, click **+** and select **New Project**. +2. Select the **Import project** tab and then select **GitHub**. +3. Select the first button to **List your GitHub repositories**. You are redirected to a page on github.com to authorize the GitLab application. +4. Click **Authorize gitlabhq**. You are redirected back to GitLab’s Import page and all of your GitHub repositories are listed. +5. Continue on to [select which repositories to import](#select-which-repositories-to-import). ->**Note:** -For a proper author/assignee mapping for issues and pull requests, the -[GitHub integration][gh-integration] should be used instead of the -[personal access token][gh-token]. If the GitHub integration is enabled by your -GitLab administrator, it should be the preferred method to import your repositories. -Read more in the [How it works](#how-it-works) section. +### Use a GitHub token -If you are not using the GitHub integration, you can still perform a one-off -authorization with GitHub to grant GitLab access your repositories: +>**Note:** For a proper author/assignee mapping for issues and pull requests, the GitHub integration method (above) should be used instead of the personal access token. If you are using gitlab.com or a self-hosted GitLab instance with the GitHub integration enabled, that should be the preferred method to import your repositories. Read more in the [How it works](#how-it-works) section. -1. Go to . -1. Enter a token description. -1. Check the `repo` scope. -1. Click **Generate token**. -1. Copy the token hash. -1. Go back to GitLab and provide the token to the GitHub importer. -1. Hit the **List Your GitHub Repositories** button and wait while GitLab reads - your repositories' information. Once done, you'll be taken to the importer - page to select the repositories to import. +If you are not using the GitHub integration, you can still perform an authorization with GitHub to grant GitLab access your repositories: -### Select which repositories to import +1. Go to [https://github.com/settings/tokens/new](https://github.com/settings/tokens/new). +2. Enter a token description. +3. Select the repo scope. +4. Click **Generate token**. +5. Copy the token hash. +6. Go back to GitLab and provide the token to the GitHub importer. +7. Hit the **List Your GitHub Repositories** button and wait while GitLab reads your repositories' information. Once done, you'll be taken to the importer page to select the repositories to import. -After you've authorized access to your GitHub repositories, you will be -redirected to the GitHub importer page. +### Select which repositories to import -From there, you can see the import statuses of your GitHub repositories. +After you have authorized access to your GitHub repositories, you are redirected to the GitHub importer page and your GitHub repositories are listed. -- Those that are being imported will show a _started_ status, -- those already successfully imported will be green with a _done_ status, -- whereas those that are not yet imported will have an **Import** button on the - right side of the table. +1. By default, the proposed repository namespaces match the names as they exist in GitHub, but based on your permissions, you can choose to edit these names before you proceed to import any of them. +2. Select the **Import** button next to any number of repositories, or select **Import all repositories**. +3. The **Status** column shows the import status of each repository. You can choose to leave the page open and it will update in realtime or you can return to it later. +4. Once a repository has been imported, click its GitLab path to open its GitLab URL. -If you want, you can import all your GitHub projects in one go by hitting -**Import all projects** in the upper left corner. +## Mirroring and pipeline status sharing -![GitHub importer page](img/import_projects_from_github_importer.png) +Depending your GitLab tier, [project mirroring](../../../workflow/repository_mirroring.md) can be set up to keep your imported project in sync with its GitHub copy. ---- +Additionally you can configure GitLab to send pipeline status updates back GitHub with the [GitHub Project Integration](../integrations/github.md). -You can also choose a different name for the project and a different namespace, -if you have the privileges to do so. +If you import your project using [CI/CD for external repo](../../../ci/ci_cd_for_external_repos/) then both of the above are automatically configured. -## Making the import process go faster +## Improving the speed of imports on self-hosted instances -For large projects it may take a while to import all data. To reduce the time -necessary you can increase the number of Sidekiq workers that process the -following queues: +For large projects it may take a while to import all data. To reduce the time necessary you can increase the number of Sidekiq workers that process the following queues: * `github_importer` * `github_importer_advance_stage` -For an optimal experience we recommend having at least 4 Sidekiq processes (each -running a number of threads equal to the number of CPU cores) that _only_ -process these queues. We also recommend that these processes run on separate -servers. For 4 servers with 8 cores this means you can import up to 32 objects -(e.g. issues) in parallel. +For an optimal experience we recommend having at least 4 Sidekiq processes (each running a number of threads equal to the number of CPU cores) that *only* process these queues. We also recommend that these processes run on separate servers. For 4 servers with 8 cores this means you can import up to 32 objects (e.g. issues) in parallel. -Reducing the time spent in cloning a repository can be done by increasing -network throughput, CPU capacity, and disk performance (e.g. by using high -performance SSDs) of the disks that store the Git repositories (for your GitLab -instance). Increasing the number of Sidekiq workers will _not_ reduce the time -spent cloning repositories. +Reducing the time spent in cloning a repository can be done by increasing network throughput, CPU capacity, and disk performance (e.g. by using high performance SSDs) of the disks that store the Git repositories (for your GitLab instance). Increasing the number of Sidekiq workers will *not* reduce the time spent cloning repositories. [gh-import]: ../../../integration/github.md "GitHub integration" [gh-rake]: ../../../administration/raketasks/github_import.md "GitHub rake task" [gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration [gh-token]: #authorize-access-to-your-repositories-using-a-personal-access-token -[gh-import-dev-docs]: ../../../development/github_importer.md "Working with the GitHub importer" +[gh-import-dev-docs]: ../../../development/github_importer.md "Working with the GitHub importer" \ No newline at end of file -- cgit v1.2.1 From 8197c756b13be9189eebb649fdb61e7aebb7978d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 4 Jun 2018 11:12:21 +0200 Subject: Copyedit GitHub importer docs --- doc/integration/github.md | 2 +- doc/user/project/import/github.md | 165 +++++++++++++++++++++++--------------- 2 files changed, 103 insertions(+), 64 deletions(-) diff --git a/doc/integration/github.md b/doc/integration/github.md index 23bb8ef9303..680712f9e01 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -110,7 +110,7 @@ On the sign in page there should now be a GitHub icon below the regular sign in Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in. -### GitHub Enterprise with Self-Signed Certificate +## GitHub Enterprise with self-signed Certificate If you are attempting to import projects from GitHub Enterprise with a self-signed certificate and the imports are failing, you will need to disable SSL verification. diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md index 6757ba7061d..cad85881c4d 100644 --- a/doc/user/project/import/github.md +++ b/doc/user/project/import/github.md @@ -1,104 +1,143 @@ # Import your project from GitHub to GitLab -## Overview - -Using the importer, you can import your GitHub repositories to GitLab.com or to your self-hosted GitLab instance. - ->**Note:** While these instructions will always work for users on gitlab.com, if you are an administrator of a self-hosted GitLab instance, you will need to enable the [GitHub integration](https://docs.gitlab.com/ee/integration/github.html) in order for users to follow the preferred import method described on this page. If this is not enabled, users can alternatively import their GitHub repositories using a [personal access token](https://docs.gitlab.com/ee/user/project/import/github.html#authorize-access-to-your-repositories-using-a-personal-access-token) from GitHub, but this method will not be able to associate all user activity (such as issues and pull requests) with matching GitLab users. - ->As an administrator of a self-hosted GitLab instance, you can also use the [GitHub rake task](https://docs.gitlab.com/ee/administration/raketasks/github_import.html) to import projects from GitHub without the constraints of a Sidekiq worker. - -* The following aspects of a project are imported: - * repository description (GitLab.com & 7.7+) - * Git repository data (GitLab.com & 7.7+) - * issues (GitLab.com & 7.7+) - * pull requests (GitLab.com & 8.4+) - * wiki pages (GitLab.com & 8.4+) - * milestones (GitLab.com & 8.7+) - * labels (GitLab.com & 8.7+) - * release note descriptions (GitLab.com & 8.12+) - * pull request review comments (GitLab.com & 10.2+) - * regular issue and pull request comments -* References to pull requests and issues are preserved (GitLab.com & 8.7+) -* Each imported repository defaults to ‘private’ but can be made public, as needed. - -## How it works - -When issues and pull requests are being imported, the importer attempts to find their GitHub authors and assignees in the database of the GitLab instance. (Note that pull requests are called "merge requests" in GitLab.) - -For this association to succeed, prior to the import, each GitHub author and assignee in the repository must have either previously logged in to a GitLab account using the GitHub icon **or** have a GitHub account with a [public email address](https://help.github.com/articles/setting-your-commit-email-address-on-github/) that matches their GitLab account’s email address. +Using the importer, you can import your GitHub repositories to GitLab.com or to +your self-hosted GitLab instance. -If a user referenced in the project is not found in GitLab's database, the project creator (typically the user that initiated the import process) is set as the author/assignee, but a note on the issue mentioning the original GitHub author is added. - -The importer creates any new namespaces (groups) if they do not exist, or, if the namespace is taken, the repository is imported under the namespace of the user who initiated the import process. The namespace/repository name can also be edited, with the proper permissions. - -The importer will also import branches on forks of projects related to open pull requests. These branches will be imported with a naming scheme similar to GH-`SHA-Username/Pull-Request-number/fork-name/branch`. This may lead to a discrepancy in branches compared to those of the GitHub repository. - -For additional technical details, you can refer to the [GitHub Importer][gh-import-dev-docs] developer documentation. - -## Import your GitHub repository into GitLab +## Overview -### Use the GitHub integration +NOTE: **Note:** +While these instructions will always work for users on GitLab.com, if you are an +administrator of a self-hosted GitLab instance, you will need to enable the +[GitHub integration][gh-import] in order for users to follow the preferred +import method described on this page. If this is not enabled, users can alternatively import their +GitHub repositories using a [personal access token](#using-a-github-token) from GitHub, +but this method will not be able to associate all user activity (such as issues and pull requests) +with matching GitLab users. As an administrator of a self-hosted GitLab instance, you can also use +the [GitHub rake task](../../../administration/raketasks/github_import.md) to import projects from +GitHub without the constraints of a Sidekiq worker. + +The following aspects of a project are imported: + * Repository description (GitLab.com & 7.7+) + * Git repository data (GitLab.com & 7.7+) + * Issues (GitLab.com & 7.7+) + * Pull requests (GitLab.com & 8.4+) + * Wiki pages (GitLab.com & 8.4+) + * Milestones (GitLab.com & 8.7+) + * Labels (GitLab.com & 8.7+) + * Release note descriptions (GitLab.com & 8.12+) + * Pull request review comments (GitLab.com & 10.2+) + * Regular issue and pull request comments + +References to pull requests and issues are preserved (GitLab.com & 8.7+), and +each imported repository defaults to `private` but [can be made public](../settings/index.md#sharing-and-permissions), as needed. + +## How it works + +When issues and pull requests are being imported, the importer attempts to find their GitHub authors and +assignees in the database of the GitLab instance (note that pull requests are called "merge requests" in GitLab). + +For this association to succeed, prior to the import, each GitHub author and assignee in the repository must +have either previously logged in to a GitLab account using the GitHub icon **or** have a GitHub account with +a [public email address](https://help.github.com/articles/setting-your-commit-email-address-on-github/) that +matches their GitLab account's email address. + +If a user referenced in the project is not found in GitLab's database, the project creator (typically the user +that initiated the import process) is set as the author/assignee, but a note on the issue mentioning the original +GitHub author is added. + +The importer creates any new namespaces (groups) if they do not exist, or, if the namespace is taken, the +repository is imported under the namespace of the user who initiated the import process. The namespace/repository +name can also be edited, with the proper permissions. + +The importer will also import branches on forks of projects related to open pull requests. These branches will be +imported with a naming scheme similar to `GH-SHA-username/pull-request-number/fork-name/branch`. This may lead to +a discrepancy in branches compared to those of the GitHub repository. + +For additional technical details, you can refer to the +[GitHub Importer](../../../development/github_importer.md "Working with the GitHub importer") +developer documentation. + +## Import your GitHub repository into GitLab + +### Using the GitHub integration Before you begin, ensure that any GitHub users who you want to map to GitLab users have either: -1. A GitLab account that has logged in using the GitHub icon. + +1. A GitLab account that has logged in using the GitHub icon \- or - -2. A GitLab account with an email address that matches the [public email address](https://help.github.com/articles/setting-your-commit-email-address-on-github/) of the GitHub user. +2. A GitLab account with an email address that matches the [public email address](https://help.github.com/articles/setting-your-commit-email-address-on-github/) of the GitHub user -User-matching attempts occur in that order, and if a user is not identified either way, the activity is associated with the user account that is performing the import. +User-matching attempts occur in that order, and if a user is not identified either way, the activity is associated with +the user account that is performing the import. -Note: If you are using a self-hosted GitLab instance, this process requires that you have configured the [GitHub integration][gh-import]. +NOTE: **Note:** +If you are using a self-hosted GitLab instance, this process requires that you have configured the +[GitHub integration][gh-import]. -1. From the top navigation bar, click **+** and select **New Project**. +1. From the top navigation bar, click **+** and select **New project**. 2. Select the **Import project** tab and then select **GitHub**. 3. Select the first button to **List your GitHub repositories**. You are redirected to a page on github.com to authorize the GitLab application. -4. Click **Authorize gitlabhq**. You are redirected back to GitLab’s Import page and all of your GitHub repositories are listed. -5. Continue on to [select which repositories to import](#select-which-repositories-to-import). +4. Click **Authorize gitlabhq**. You are redirected back to GitLab's Import page and all of your GitHub repositories are listed. +5. Continue on to [selecting which repositories to import](#selecting-which-repositories-to-import). -### Use a GitHub token +### Using a GitHub token ->**Note:** For a proper author/assignee mapping for issues and pull requests, the GitHub integration method (above) should be used instead of the personal access token. If you are using gitlab.com or a self-hosted GitLab instance with the GitHub integration enabled, that should be the preferred method to import your repositories. Read more in the [How it works](#how-it-works) section. +NOTE: **Note:** +For a proper author/assignee mapping for issues and pull requests, the [GitHub integration method (above)](#using-the-github-integration) +should be used instead of the personal access token. If you are using GitLab.com or a self-hosted GitLab instance with the GitHub +integration enabled, that should be the preferred method to import your repositories. Read more in the [How it works](#how-it-works) section. If you are not using the GitHub integration, you can still perform an authorization with GitHub to grant GitLab access your repositories: -1. Go to [https://github.com/settings/tokens/new](https://github.com/settings/tokens/new). +1. Go to https://github.com/settings/tokens/new 2. Enter a token description. 3. Select the repo scope. 4. Click **Generate token**. 5. Copy the token hash. 6. Go back to GitLab and provide the token to the GitHub importer. -7. Hit the **List Your GitHub Repositories** button and wait while GitLab reads your repositories' information. Once done, you'll be taken to the importer page to select the repositories to import. +7. Hit the **List Your GitHub Repositories** button and wait while GitLab reads your repositories' information. + Once done, you'll be taken to the importer page to select the repositories to import. -### Select which repositories to import +### Selecting which repositories to import -After you have authorized access to your GitHub repositories, you are redirected to the GitHub importer page and your GitHub repositories are listed. +After you have authorized access to your GitHub repositories, you are redirected to the GitHub importer page and +your GitHub repositories are listed. -1. By default, the proposed repository namespaces match the names as they exist in GitHub, but based on your permissions, you can choose to edit these names before you proceed to import any of them. +1. By default, the proposed repository namespaces match the names as they exist in GitHub, but based on your permissions, + you can choose to edit these names before you proceed to import any of them. 2. Select the **Import** button next to any number of repositories, or select **Import all repositories**. -3. The **Status** column shows the import status of each repository. You can choose to leave the page open and it will update in realtime or you can return to it later. +3. The **Status** column shows the import status of each repository. You can choose to leave the page open and it will + update in realtime or you can return to it later. 4. Once a repository has been imported, click its GitLab path to open its GitLab URL. ## Mirroring and pipeline status sharing -Depending your GitLab tier, [project mirroring](../../../workflow/repository_mirroring.md) can be set up to keep your imported project in sync with its GitHub copy. +Depending your GitLab tier, [project mirroring](../../../workflow/repository_mirroring.md) can be set up to keep +your imported project in sync with its GitHub copy. + +Additionally, you can configure GitLab to send pipeline status updates back GitHub with the +[GitHub Project Integration](https://docs.gitlab.com/ee/user/project/integrations/github.html). **[PREMIUM]** -Additionally you can configure GitLab to send pipeline status updates back GitHub with the [GitHub Project Integration](../integrations/github.md). +If you import your project using [CI/CD for external repo](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/), then both +of the above are automatically configured. **[PREMIUM]** -If you import your project using [CI/CD for external repo](../../../ci/ci_cd_for_external_repos/) then both of the above are automatically configured. +## Improving the speed of imports on self-hosted instances -## Improving the speed of imports on self-hosted instances +NOTE: **Note:** +Admin access to the GitLab server is required. -For large projects it may take a while to import all data. To reduce the time necessary you can increase the number of Sidekiq workers that process the following queues: +For large projects it may take a while to import all data. To reduce the time necessary, you can increase the number of +Sidekiq workers that process the following queues: * `github_importer` * `github_importer_advance_stage` -For an optimal experience we recommend having at least 4 Sidekiq processes (each running a number of threads equal to the number of CPU cores) that *only* process these queues. We also recommend that these processes run on separate servers. For 4 servers with 8 cores this means you can import up to 32 objects (e.g. issues) in parallel. +For an optimal experience, it's recommended having at least 4 Sidekiq processes (each running a number of threads equal +to the number of CPU cores) that *only* process these queues. It's also recommended that these processes run on separate +servers. For 4 servers with 8 cores this means you can import up to 32 objects (e.g., issues) in parallel. -Reducing the time spent in cloning a repository can be done by increasing network throughput, CPU capacity, and disk performance (e.g. by using high performance SSDs) of the disks that store the Git repositories (for your GitLab instance). Increasing the number of Sidekiq workers will *not* reduce the time spent cloning repositories. +Reducing the time spent in cloning a repository can be done by increasing network throughput, CPU capacity, and disk +performance (e.g., by using high performance SSDs) of the disks that store the Git repositories (for your GitLab instance). +Increasing the number of Sidekiq workers will *not* reduce the time spent cloning repositories. [gh-import]: ../../../integration/github.md "GitHub integration" -[gh-rake]: ../../../administration/raketasks/github_import.md "GitHub rake task" -[gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration -[gh-token]: #authorize-access-to-your-repositories-using-a-personal-access-token -[gh-import-dev-docs]: ../../../development/github_importer.md "Working with the GitHub importer" \ No newline at end of file -- cgit v1.2.1 From 2d7aef1397c08f6fade604cdecfe542b7088ac26 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 4 Jun 2018 11:15:48 +0200 Subject: Port repository mirroring from EE --- doc/workflow/repository_mirroring.md | 250 ++++++++++++++++++++- .../repository_mirroring_detect_host_keys.png | Bin 0 -> 61463 bytes .../repository_mirroring_diverged_branch.png | Bin 0 -> 22668 bytes .../repository_mirroring_hard_failed_main.png | Bin 0 -> 47943 bytes .../repository_mirroring_hard_failed_settings.png | Bin 0 -> 53279 bytes .../repository_mirroring_new_project.png | Bin 0 -> 20364 bytes ...epository_mirroring_pull_advanced_host_keys.png | Bin 0 -> 115796 bytes .../repository_mirroring_pull_settings.png | Bin 0 -> 100470 bytes .../repository_mirroring_pull_settings_for_ssh.png | Bin 0 -> 69467 bytes ...repository_mirroring_ssh_host_keys_verified.png | Bin 0 -> 23724 bytes ...ory_mirroring_ssh_public_key_authentication.png | Bin 0 -> 82456 bytes 11 files changed, 245 insertions(+), 5 deletions(-) create mode 100644 doc/workflow/repository_mirroring/repository_mirroring_detect_host_keys.png create mode 100644 doc/workflow/repository_mirroring/repository_mirroring_diverged_branch.png create mode 100644 doc/workflow/repository_mirroring/repository_mirroring_hard_failed_main.png create mode 100644 doc/workflow/repository_mirroring/repository_mirroring_hard_failed_settings.png create mode 100644 doc/workflow/repository_mirroring/repository_mirroring_new_project.png create mode 100644 doc/workflow/repository_mirroring/repository_mirroring_pull_advanced_host_keys.png create mode 100644 doc/workflow/repository_mirroring/repository_mirroring_pull_settings.png create mode 100644 doc/workflow/repository_mirroring/repository_mirroring_pull_settings_for_ssh.png create mode 100644 doc/workflow/repository_mirroring/repository_mirroring_ssh_host_keys_verified.png create mode 100644 doc/workflow/repository_mirroring/repository_mirroring_ssh_public_key_authentication.png diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md index dbe63144e38..aaddbe4fbf5 100644 --- a/doc/workflow/repository_mirroring.md +++ b/doc/workflow/repository_mirroring.md @@ -1,6 +1,6 @@ # Repository mirroring -Repository Mirroring is a way to mirror repositories from external sources. +Repository mirroring is a way to mirror repositories from external sources. It can be used to mirror all branches, tags, and commits that you have in your repository. @@ -34,13 +34,200 @@ A few things/limitations to consider: - The Git LFS objects will not be synced. You'll need to push/pull them manually. -## Use-case +## Use cases +- You migrated to GitLab but still need to keep your project in another source. + In that case, you can simply set it up to mirror to GitLab (pull) and all the + essential history of commits, tags and branches will be available in your + GitLab instance. - You have old projects in another source that you don't use actively anymore, but don't want to remove for archiving purposes. In that case, you can create a push mirror so that your active GitLab repository can push its changes to the old location. +## Pulling from a remote repository **[STARTER]** + +>[Introduced][ee-51] in GitLab Enterprise Edition 8.2. + +You can set up a repository to automatically have its branches, tags, and commits +updated from an upstream repository. This is useful when a repository you're +interested in is located on a different server, and you want to be able to +browse its content and its activity using the familiar GitLab interface. + +When creating a new project, you can enable repository mirroring when you choose +to import the repository from "Any repo by URL". Enter the full URL of the Git +repository to pull from and click on the **Mirror repository** checkbox. + +![New project](repository_mirroring/repository_mirroring_new_project.png) + +For an existing project, you can set up mirror pulling by visiting your project's +**Settings ➔ Repository** and searching for the "Pull from a remote repository" +section. Check the "Mirror repository" box and hit **Save changes** at the bottom. +You have a few options to choose from one being the user who will be the author +of all events in the activity feed that are the result of an update. This user +needs to have at least [master access][perms] to the project. Another option is +whether you want to trigger builds for mirror updates. + +![Pull settings](repository_mirroring/repository_mirroring_pull_settings.png) + +Since the repository on GitLab functions as a mirror of the upstream repository, +you are advised not to push commits directly to the repository on GitLab. +Instead, any commits should be pushed to the upstream repository, and will end +up in the GitLab repository automatically within a certain period of time +or when a [forced update](#forcing-an-update) is initiated. + +If you do manually update a branch in the GitLab repository, the branch will +become diverged from upstream, and GitLab will no longer automatically update +this branch to prevent any changes from being lost. + +![Diverged branch](repository_mirroring/repository_mirroring_diverged_branch.png) + +### Trigger update using API **[STARTER]** + +>[Introduced][ee-3453] in GitLab Enterprise Edition 10.3. + +Pull mirroring uses polling to detect new branches and commits added upstream, +often many minutes afterwards. If you notify GitLab by [API][pull-api], updates +will be pulled immediately. + +Read the [Pull Mirror Trigger API docs][pull-api]. + +### Pull only protected branches **[STARTER]** + +>[Introduced][ee-3326] in GitLab Enterprise Edition 10.3. + +You can choose to only pull the protected branches from your remote repository to GitLab. + +To use this option go to your project's repository settings page under pull mirror. + +### Overwrite diverged branches **[STARTER]** + +>[Introduced][ee-4559] in GitLab Enterprise Edition 10.6. + +You can choose to always update your local branch with the remote version even +if your local version has diverged from the remote. + +To use this option go to your project's repository settings page under pull mirror. + +### Hard failure **[STARTER]** + +>[Introduced][ee-3117] in GitLab Enterprise Edition 10.2. + +Once a mirror gets retried 14 times in a row, it will get marked as hard failed, +this will become visible in either the project main dashboard or in the +pull mirror settings page. + +![Hard failed mirror main notice](repository_mirroring/repository_mirroring_hard_failed_main.png) + +![Hard failed mirror settings notice](repository_mirroring/repository_mirroring_hard_failed_settings.png) + +When a project is hard failed, it will no longer get picked up for mirroring. +A user can resume the project mirroring again by either [forcing an update](#forcing-an-update) +or by changing the import URL in repository settings. + +### SSH authentication **[STARTER]** + +> [Introduced][ee-2551] in GitLab Starter 9.5 + +If you're mirroring over SSH (i.e., an `ssh://` URL), you can authenticate using +password-based authentication, just as over HTTPS, but you can also use public +key authentication. This is often more secure than password authentication, +especially when the source repository supports [Deploy Keys][deploy-key]. + +To get started, navigate to **Settings ➔ Repository ➔ Pull from a remote repository**, +enable mirroring (if not already enabled) and enter an `ssh://` URL. + +> **NOTE**: SCP-style URLs, e.g., `git@example.com:group/project.git`, are not +supported at this time. + +Entering the URL adds two features to the page - `Fingerprints` and +`SSH public key authentication`: + +![Pull settings for SSH](repository_mirroring/repository_mirroring_pull_settings_for_ssh.png) + +SSH authentication is mutual. You have to prove to the server that you're +allowed to access the repository, but the server also has to prove to *you* that +it's who it claims to be. You provide your credentials as a password or public +key. The server that the source repository resides on provides its credentials +as a "host key", the fingerprint of which needs to be verified manually. + +Press the `Detect host keys` button. GitLab will fetch the host keys from the +server, and display the fingerprints to you: + +![Detect SSH host keys](repository_mirroring/repository_mirroring_detect_host_keys.png) + +You now need to verify that the fingerprints are those you expect. GitLab.com +and other code hosting sites publish their fingerprints in the open for you +to check: + +* [AWS CodeCommit](http://docs.aws.amazon.com/codecommit/latest/userguide/regions.html#regions-fingerprints) +* [Bitbucket](https://confluence.atlassian.com/bitbucket/use-the-ssh-protocol-with-bitbucket-cloud-221449711.html#UsetheSSHprotocolwithBitbucketCloud-KnownhostorBitbucket%27spublickeyfingerprints) +* [GitHub](https://help.github.com/articles/github-s-ssh-key-fingerprints/) +* [GitLab.com](https://about.gitlab.com/gitlab-com/settings/#ssh-host-keys-fingerprints) +* [Launchpad](https://help.launchpad.net/SSHFingerprints) +* [Savannah](http://savannah.gnu.org/maintenance/SshAccess/) +* [SourceForge](https://sourceforge.net/p/forge/documentation/SSH%20Key%20Fingerprints/) + +Other providers will vary. If you're running on-premises GitLab, or otherwise +have access to the source server, you can securely gather the key fingerprints: + +``` +$ cat /etc/ssh/ssh_host*pub | ssh-keygen -E md5 -l -f - +256 MD5:f4:28:9f:23:99:15:21:1b:bf:ed:1f:8e:a0:76:b2:9d root@example.com (ECDSA) +256 MD5:e6:eb:45:8a:3c:59:35:5f:e9:5b:80:12:be:7e:22:73 root@example.com (ED25519) +2048 MD5:3f:72:be:3d:62:03:5c:62:83:e8:6e:14:34:3a:85:1d root@example.com (RSA) +``` + +(You may need to exclude `-E md5` for some older versions of SSH). + +If you're an SSH expert and already have a `known_hosts` file you'd like to use +unaltered, then you can skip these steps. Just press the "Show advanced" button +and paste in the file contents: + +![Advanced SSH host key management](repository_mirroring/repository_mirroring_pull_advanced_host_keys.png) + +Once you've **carefully verified** that all the fingerprints match your trusted +source, you can press `Save changes`. This will record the host keys, along with +the person who verified them (you!) and the date: + +![SSH host keys submitted](repository_mirroring/repository_mirroring_ssh_host_keys_verified.png) + +When pulling changes from the source repository, GitLab will now check that at +least one of the stored host keys matches before connecting. This can prevent +malicious code from being injected into your mirror, or your password being +stolen! + +To use SSH public key authentication, you'll also need to choose that option +from the authentication methods dropdown. GitLab will generate a 4096-bit RSA +key and display the public component of that key to you: + +![SSH public key authentication](repository_mirroring/repository_mirroring_ssh_public_key_authentication.png) + +You then need to add the public SSH key to the source repository configuration. +If the source is hosted on GitLab, you should add it as a [Deploy Key][deploy-key]. +Other sources may require you to add the key to your user's `authorized_keys` +file - just paste the entire `ssh-rsa AAA.... user@host` block into the file on +its own line and save it. + +Once the public key is set up on the source repository, press `Save changes` and your +mirror will begin working. + +If you need to change the key at any time, you can press the `Regenerate key` +button to do so. You'll have to update the source repository with the new key +to keep the mirror running. + +### How it works + +Once you activate the pull mirroring feature, the mirror will be inserted into +a queue. A scheduler will start every minute and schedule a fixed amount of +mirrors for update, based on the configured maximum capacity. + +If the mirror successfully updates it will be enqueued once again with a small +backoff period. + +If the mirror fails (eg: branch diverged from upstream), the project's backoff +period will be penalized each time it fails up to a maximum amount of time. + ## Pushing to a remote repository **[STARTER]** >[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in @@ -83,7 +270,7 @@ To use this option go to your project's repository settings page under push mirr To set up a mirror from GitLab to GitHub, you need to follow these steps: 1. Create a [GitHub personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/) with the "public_repo" box checked: - + ![edit personal access token GitHub](repository_mirroring/repository_mirroring_github_edit_personal_access_token.png) 1. Fill in the "Git repository URL" with the personal access token replacing the password `https://GitHubUsername:GitHubPersonalAccessToken@github.com/group/project.git`: @@ -94,7 +281,7 @@ To set up a mirror from GitLab to GitHub, you need to follow these steps: 1. And either wait or trigger the "Update Now" button: ![update now](repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository_update_now.png) - + ## Forcing an update While mirrors are scheduled to update automatically, you can always force an update @@ -105,7 +292,60 @@ by using the **Update now** button which is exposed in various places: - in the tags page - in the **Mirror repository** settings page +## Bidirectional mirroring + +CAUTION: **Warning:** +There is no bidirectional support without conflicts. If you +configure a repository to pull and push to a second remote, there is no +guarantee that it will update correctly on both remotes. If you configure +a repository for bidirectional mirroring, you should consider when conflicts +occur who and how they will be resolved. + +Rewriting any mirrored commit on either remote will cause conflicts and +mirroring to fail. This can be prevented by [only pulling protected branches]( +#pull-only-protected-branches) and [only pushing protected branches]( +#push-only-protected-branches). You should protect the branches you wish to +mirror on both remotes to prevent conflicts caused by rewriting history. + +Bidirectional mirroring also creates a race condition where commits to the same +branch in close proximity will cause conflicts. The race condition can be +mitigated by reducing the mirroring delay by using a Push event webhook to +trigger an immediate pull to GitLab. Push mirroring from GitLab is rate limited +to once per minute when only push mirroring protected branches. + +It may be possible to implement a locking mechanism using the server-side +`pre-receive` hook to prevent the race condition. Read about [configuring +custom Git hooks][hooks] on the GitLab server. + +### Mirroring with Perforce via GitFusion + +CAUTION: **Warning:** +Bidirectional mirroring should not be used as a permanent +configuration. There is no bidirectional mirroring without conflicts. +Refer to [Migrating from Perforce Helix][perforce] for alternative migration +approaches. + +GitFusion provides a Git interface to Perforce which can be used by GitLab to +bidirectionally mirror projects with GitLab. This may be useful in some +situations when migrating from Perforce to GitLab where overlapping Perforce +workspaces cannot be migrated simultaneously to GitLab. + +If using mirroring with Perforce you should only mirror protected branches. +Perforce will reject any pushes that rewrite history. It is recommended that +only the fewest number of branches are mirrored due to the performance +limitations of GitFusion. + +[ee-51]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51 +[ee-2551]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551 +[ee-3117]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3117 +[ee-3326]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3326 [ee-3350]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350 +[ee-3453]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3453 +[ee-4559]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4559 [ce-18715]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715 [perms]: ../user/permissions.md - +[hooks]: ../administration/custom_hooks.md +[deploy-key]: ../ssh/README.md#deploy-keys +[webhook]: ../user/project/integrations/webhooks.md#push-events +[pull-api]: ../api/projects.md#start-the-pull-mirroring-process-for-a-project +[perforce]: ../user/project/import/perforce.md diff --git a/doc/workflow/repository_mirroring/repository_mirroring_detect_host_keys.png b/doc/workflow/repository_mirroring/repository_mirroring_detect_host_keys.png new file mode 100644 index 00000000000..333648942f8 Binary files /dev/null and b/doc/workflow/repository_mirroring/repository_mirroring_detect_host_keys.png differ diff --git a/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch.png b/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch.png new file mode 100644 index 00000000000..45c9bce0889 Binary files /dev/null and b/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch.png differ diff --git a/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_main.png b/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_main.png new file mode 100644 index 00000000000..99d429a1802 Binary files /dev/null and b/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_main.png differ diff --git a/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_settings.png b/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_settings.png new file mode 100644 index 00000000000..0ab07afa3cc Binary files /dev/null and b/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_settings.png differ diff --git a/doc/workflow/repository_mirroring/repository_mirroring_new_project.png b/doc/workflow/repository_mirroring/repository_mirroring_new_project.png new file mode 100644 index 00000000000..43bf304838f Binary files /dev/null and b/doc/workflow/repository_mirroring/repository_mirroring_new_project.png differ diff --git a/doc/workflow/repository_mirroring/repository_mirroring_pull_advanced_host_keys.png b/doc/workflow/repository_mirroring/repository_mirroring_pull_advanced_host_keys.png new file mode 100644 index 00000000000..5da5a7436bb Binary files /dev/null and b/doc/workflow/repository_mirroring/repository_mirroring_pull_advanced_host_keys.png differ diff --git a/doc/workflow/repository_mirroring/repository_mirroring_pull_settings.png b/doc/workflow/repository_mirroring/repository_mirroring_pull_settings.png new file mode 100644 index 00000000000..4b9085302a1 Binary files /dev/null and b/doc/workflow/repository_mirroring/repository_mirroring_pull_settings.png differ diff --git a/doc/workflow/repository_mirroring/repository_mirroring_pull_settings_for_ssh.png b/doc/workflow/repository_mirroring/repository_mirroring_pull_settings_for_ssh.png new file mode 100644 index 00000000000..8c2efdafa43 Binary files /dev/null and b/doc/workflow/repository_mirroring/repository_mirroring_pull_settings_for_ssh.png differ diff --git a/doc/workflow/repository_mirroring/repository_mirroring_ssh_host_keys_verified.png b/doc/workflow/repository_mirroring/repository_mirroring_ssh_host_keys_verified.png new file mode 100644 index 00000000000..93f3a532a0e Binary files /dev/null and b/doc/workflow/repository_mirroring/repository_mirroring_ssh_host_keys_verified.png differ diff --git a/doc/workflow/repository_mirroring/repository_mirroring_ssh_public_key_authentication.png b/doc/workflow/repository_mirroring/repository_mirroring_ssh_public_key_authentication.png new file mode 100644 index 00000000000..6997ad511d9 Binary files /dev/null and b/doc/workflow/repository_mirroring/repository_mirroring_ssh_public_key_authentication.png differ -- cgit v1.2.1 From b8370c9f55843351b49073dafe84a2e9858c8c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 9 May 2018 17:27:38 +0200 Subject: Support presigned multipart uploads --- app/controllers/projects/lfs_storage_controller.rb | 2 +- app/uploaders/object_storage.rb | 21 +-- .../unreleased/presigned-multipart-uploads.yml | 5 + .../artifacts_direct_upload_support.rb | 7 - config/initializers/direct_upload_support.rb | 13 ++ doc/administration/job_artifacts.md | 3 +- lib/api/runner.rb | 2 +- lib/object_storage/direct_upload.rb | 166 ++++++++++++++++++ .../artifacts_direct_upload_support_spec.rb | 71 -------- spec/initializers/direct_upload_support_spec.rb | 89 ++++++++++ spec/lib/object_storage/direct_upload_spec.rb | 188 +++++++++++++++++++++ spec/requests/api/runner_spec.rb | 1 + spec/requests/lfs_http_spec.rb | 1 + spec/support/helpers/stub_object_storage.rb | 12 ++ spec/uploaders/object_storage_spec.rb | 134 ++++++++++++--- 15 files changed, 592 insertions(+), 123 deletions(-) create mode 100644 changelogs/unreleased/presigned-multipart-uploads.yml delete mode 100644 config/initializers/artifacts_direct_upload_support.rb create mode 100644 config/initializers/direct_upload_support.rb create mode 100644 lib/object_storage/direct_upload.rb delete mode 100644 spec/initializers/artifacts_direct_upload_support_spec.rb create mode 100644 spec/initializers/direct_upload_support_spec.rb create mode 100644 spec/lib/object_storage/direct_upload_spec.rb diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb index 43d8867a536..45c98d60822 100644 --- a/app/controllers/projects/lfs_storage_controller.rb +++ b/app/controllers/projects/lfs_storage_controller.rb @@ -18,7 +18,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController def upload_authorize set_workhorse_internal_api_content_type - authorized = LfsObjectUploader.workhorse_authorize + authorized = LfsObjectUploader.workhorse_authorize(has_length: true) authorized.merge!(LfsOid: oid, LfsSize: size) render json: authorized diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb index 5bdca26a584..3bb2e1ea63a 100644 --- a/app/uploaders/object_storage.rb +++ b/app/uploaders/object_storage.rb @@ -10,8 +10,6 @@ module ObjectStorage UnknownStoreError = Class.new(StandardError) ObjectStorageUnavailable = Class.new(StandardError) - DIRECT_UPLOAD_TIMEOUT = 4.hours - DIRECT_UPLOAD_EXPIRE_OFFSET = 15.minutes TMP_UPLOAD_PATH = 'tmp/uploads'.freeze module Store @@ -157,9 +155,9 @@ module ObjectStorage model_class.uploader_options.dig(mount_point, :mount_on) || mount_point end - def workhorse_authorize + def workhorse_authorize(has_length:, maximum_size: nil) { - RemoteObject: workhorse_remote_upload_options, + RemoteObject: workhorse_remote_upload_options(has_length: has_length, maximum_size: maximum_size), TempPath: workhorse_local_upload_path }.compact end @@ -168,23 +166,16 @@ module ObjectStorage File.join(self.root, TMP_UPLOAD_PATH) end - def workhorse_remote_upload_options + def workhorse_remote_upload_options(has_length:, maximum_size: nil) return unless self.object_store_enabled? return unless self.direct_upload_enabled? id = [CarrierWave.generate_cache_id, SecureRandom.hex].join('-') upload_path = File.join(TMP_UPLOAD_PATH, id) - connection = ::Fog::Storage.new(self.object_store_credentials) - expire_at = Time.now + DIRECT_UPLOAD_TIMEOUT + DIRECT_UPLOAD_EXPIRE_OFFSET - options = { 'Content-Type' => 'application/octet-stream' } + direct_upload = ObjectStorage::DirectUpload.new(self.object_store_credentials, remote_store_path, upload_path, + has_length: has_length, maximum_size: maximum_size) - { - ID: id, - Timeout: DIRECT_UPLOAD_TIMEOUT, - GetURL: connection.get_object_url(remote_store_path, upload_path, expire_at), - DeleteURL: connection.delete_object_url(remote_store_path, upload_path, expire_at), - StoreURL: connection.put_object_url(remote_store_path, upload_path, expire_at, options) - } + direct_upload.to_hash.merge(ID: id) end end diff --git a/changelogs/unreleased/presigned-multipart-uploads.yml b/changelogs/unreleased/presigned-multipart-uploads.yml new file mode 100644 index 00000000000..52fae6534fd --- /dev/null +++ b/changelogs/unreleased/presigned-multipart-uploads.yml @@ -0,0 +1,5 @@ +--- +title: Support direct_upload with S3 Multipart uploads +merge_request: +author: +type: added diff --git a/config/initializers/artifacts_direct_upload_support.rb b/config/initializers/artifacts_direct_upload_support.rb deleted file mode 100644 index d2bc35ea613..00000000000 --- a/config/initializers/artifacts_direct_upload_support.rb +++ /dev/null @@ -1,7 +0,0 @@ -artifacts_object_store = Gitlab.config.artifacts.object_store - -if artifacts_object_store.enabled && - artifacts_object_store.direct_upload && - artifacts_object_store.connection&.provider.to_s != 'Google' - raise "Only 'Google' is supported as a object storage provider when 'direct_upload' of artifacts is used" -end diff --git a/config/initializers/direct_upload_support.rb b/config/initializers/direct_upload_support.rb new file mode 100644 index 00000000000..8ba1229415f --- /dev/null +++ b/config/initializers/direct_upload_support.rb @@ -0,0 +1,13 @@ +SUPPORTED_DIRECT_UPLOAD_PROVIDERS = %w(Google AWS).freeze + +def verify_provider_support!(object_store) + return unless object_store.enabled + return unless object_store.direct_upload + return if SUPPORTED_DIRECT_UPLOAD_PROVIDERS.include?(object_store.connection&.provider.to_s) + + raise "Only #{SUPPORTED_DIRECT_UPLOAD_PROVIDERS.join(',')} are supported as a object storage provider when 'direct_upload' is used" +end + +verify_provider_support!(Gitlab.config.artifacts.object_store) +verify_provider_support!(Gitlab.config.uploads.object_store) +verify_provider_support!(Gitlab.config.lfs.object_store) diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index 77fe4d561a1..e59ab5a72e1 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -94,6 +94,7 @@ _The artifacts are stored by default in > Available in [GitLab Premium](https://about.gitlab.com/products/) and [GitLab.com Silver](https://about.gitlab.com/gitlab-com/). > Since version 10.6, available in [GitLab CE](https://about.gitlab.com/products/) +> Since version 11.0, we support direct_upload to S3. If you don't want to use the local disk where GitLab is installed to store the artifacts, you can use an object storage like AWS S3 instead. @@ -108,7 +109,7 @@ For source installations the following settings are nested under `artifacts:` an |---------|-------------|---------| | `enabled` | Enable/disable object storage | `false` | | `remote_directory` | The bucket name where Artifacts will be stored| | -| `direct_upload` | Set to true to enable direct upload of Artifacts without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. Currently only `Google` provider is supported | `false` | +| `direct_upload` | Set to true to enable direct upload of Artifacts without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. | `false` | | `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 | `true` | | `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | | `connection` | Various connection options described below | | diff --git a/lib/api/runner.rb b/lib/api/runner.rb index e9886c76870..db502697a19 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -205,7 +205,7 @@ module API status 200 content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE - JobArtifactUploader.workhorse_authorize + JobArtifactUploader.workhorse_authorize(has_length: false, maximum_size: max_artifacts_size) end desc 'Upload artifacts for job' do diff --git a/lib/object_storage/direct_upload.rb b/lib/object_storage/direct_upload.rb new file mode 100644 index 00000000000..a4d6b79eb45 --- /dev/null +++ b/lib/object_storage/direct_upload.rb @@ -0,0 +1,166 @@ +module ObjectStorage + # + # The DirectUpload c;ass generates a set of presigned URLs + # that can be used to upload data to object storage from untrusted component: Workhorse, Runner? + # + # For Google it assumes that the platform supports variable Content-Length. + # + # For AWS it initiates Multipart Upload and presignes a set of part uploads. + # Class calculates the best part size to be able to upload up to asked maximum size. + # The number of generated parts will never go above 100, + # but we will always try to reduce amount of generated parts. + # The part size is rounded-up to 5MB. + # + class DirectUpload + include Gitlab::Utils::StrongMemoize + + TIMEOUT = 4.hours + EXPIRE_OFFSET = 15.minutes + + MAXIMUM_MULTIPART_PARTS = 100 + MINIMUM_MULTIPART_SIZE = 5.megabytes + + attr_reader :credentials, :bucket_name, :object_name + attr_reader :has_length, :maximum_size + + def initialize(credentials, bucket_name, object_name, has_length:, maximum_size: nil) + unless has_length + raise ArgumentError, 'maximum_size has to be specified if length is unknown' unless maximum_size + end + + @credentials = credentials + @bucket_name = bucket_name + @object_name = object_name + @has_length = has_length + @maximum_size = maximum_size + end + + def to_hash + { + Timeout: TIMEOUT, + GetURL: get_url, + StoreURL: store_url, + DeleteURL: delete_url, + MultipartUpload: multipart_upload_hash + }.compact + end + + def multipart_upload_hash + return unless requires_multipart_upload? + + { + PartSize: rounded_multipart_part_size, + PartURLs: multipart_part_urls, + CompleteURL: multipart_complete_url, + AbortURL: multipart_abort_url + } + end + + def provider + credentials[:provider].to_s + end + + # Implements https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html + def get_url + connection.get_object_url(bucket_name, object_name, expire_at) + end + + # Implements https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectDELETE.html + def delete_url + connection.delete_object_url(bucket_name, object_name, expire_at) + end + + # Implements https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html + def store_url + connection.put_object_url(bucket_name, object_name, expire_at, upload_options) + end + + def multipart_part_urls + Array.new(number_of_multipart_parts) do |part_index| + multipart_part_upload_url(part_index + 1) + end + end + + # Implements https://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadUploadPart.html + def multipart_part_upload_url(part_number) + connection.signed_url({ + method: 'PUT', + bucket_name: bucket_name, + object_name: object_name, + query: { uploadId: upload_id, partNumber: part_number }, + headers: upload_options + }, expire_at) + end + + # Implements https://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadComplete.html + def multipart_complete_url + connection.signed_url({ + method: 'POST', + bucket_name: bucket_name, + object_name: object_name, + query: { uploadId: upload_id }, + headers: { 'Content-Type' => 'application/xml' } + }, expire_at) + end + + # Implements https://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadAbort.html + def multipart_abort_url + connection.signed_url({ + method: 'DELETE', + bucket_name: bucket_name, + object_name: object_name, + query: { uploadId: upload_id } + }, expire_at) + end + + private + + def rounded_multipart_part_size + # round multipart_part_size up to minimum_mulitpart_size + (multipart_part_size + MINIMUM_MULTIPART_SIZE - 1) / MINIMUM_MULTIPART_SIZE * MINIMUM_MULTIPART_SIZE + end + + def multipart_part_size + maximum_size / number_of_multipart_parts + end + + def number_of_multipart_parts + [ + # round maximum_size up to minimum_mulitpart_size + (maximum_size + MINIMUM_MULTIPART_SIZE - 1) / MINIMUM_MULTIPART_SIZE, + MAXIMUM_MULTIPART_PARTS + ].min + end + + def aws? + provider == 'AWS' + end + + def requires_multipart_upload? + aws? && !has_length + end + + def upload_id + return unless requires_multipart_upload? + + strong_memoize(:upload_id) do + new_upload = connection.initiate_multipart_upload(bucket_name, object_name) + new_upload.body["UploadId"] + end + end + + def expire_at + strong_memoize(:expire_at) do + Time.now + TIMEOUT + EXPIRE_OFFSET + end + end + + def upload_options + { 'Content-Type' => 'application/octet-stream' } + end + + def connection + @connection ||= ::Fog::Storage.new(credentials) + end + end +end diff --git a/spec/initializers/artifacts_direct_upload_support_spec.rb b/spec/initializers/artifacts_direct_upload_support_spec.rb deleted file mode 100644 index bfb71da3388..00000000000 --- a/spec/initializers/artifacts_direct_upload_support_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'spec_helper' - -describe 'Artifacts direct upload support' do - subject do - load Rails.root.join('config/initializers/artifacts_direct_upload_support.rb') - end - - let(:connection) do - { provider: provider } - end - - before do - stub_artifacts_setting( - object_store: { - enabled: enabled, - direct_upload: direct_upload, - connection: connection - }) - end - - context 'when object storage is enabled' do - let(:enabled) { true } - - context 'when direct upload is enabled' do - let(:direct_upload) { true } - - context 'when provider is Google' do - let(:provider) { 'Google' } - - it 'succeeds' do - expect { subject }.not_to raise_error - end - end - - context 'when connection is empty' do - let(:connection) { nil } - - it 'raises an error' do - expect { subject }.to raise_error /object storage provider when 'direct_upload' of artifacts is used/ - end - end - - context 'when other provider is used' do - let(:provider) { 'AWS' } - - it 'raises an error' do - expect { subject }.to raise_error /object storage provider when 'direct_upload' of artifacts is used/ - end - end - end - - context 'when direct upload is disabled' do - let(:direct_upload) { false } - let(:provider) { 'AWS' } - - it 'succeeds' do - expect { subject }.not_to raise_error - end - end - end - - context 'when object storage is disabled' do - let(:enabled) { false } - let(:direct_upload) { false } - let(:provider) { 'AWS' } - - it 'succeeds' do - expect { subject }.not_to raise_error - end - end -end diff --git a/spec/initializers/direct_upload_support_spec.rb b/spec/initializers/direct_upload_support_spec.rb new file mode 100644 index 00000000000..f124e726bac --- /dev/null +++ b/spec/initializers/direct_upload_support_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe 'Direct upload support' do + subject do + load Rails.root.join('config/initializers/direct_upload_support.rb') + end + + where(:config_name) do + ['lfs', 'artifacts', 'uploads'] + end + + with_them do + let(:connection) do + { provider: provider } + end + + let(:object_store) do + { + enabled: enabled, + direct_upload: direct_upload, + connection: connection + } + end + + before do + allow(Gitlab.config).to receive_messages(to_settings( + config_name => { object_store: object_store })) + end + + context 'when object storage is enabled' do + let(:enabled) { true } + + context 'when direct upload is enabled' do + let(:direct_upload) { true } + + context 'when provider is AWS' do + let(:provider) { 'AWS' } + + it 'succeeds' do + expect { subject }.not_to raise_error + end + end + + context 'when provider is Google' do + let(:provider) { 'Google' } + + it 'succeeds' do + expect { subject }.not_to raise_error + end + end + + context 'when connection is empty' do + let(:connection) { nil } + + it 'raises an error' do + expect { subject }.to raise_error /are supported as a object storage provider when 'direct_upload' is used/ + end + end + + context 'when other provider is used' do + let(:provider) { 'Rackspace' } + + it 'raises an error' do + expect { subject }.to raise_error /are supported as a object storage provider when 'direct_upload' is used/ + end + end + end + + context 'when direct upload is disabled' do + let(:direct_upload) { false } + let(:provider) { 'AWS' } + + it 'succeeds' do + expect { subject }.not_to raise_error + end + end + end + + context 'when object storage is disabled' do + let(:enabled) { false } + let(:direct_upload) { false } + let(:provider) { 'Rackspace' } + + it 'succeeds' do + expect { subject }.not_to raise_error + end + end + end +end diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb new file mode 100644 index 00000000000..0f86d10b881 --- /dev/null +++ b/spec/lib/object_storage/direct_upload_spec.rb @@ -0,0 +1,188 @@ +require 'spec_helper' + +describe ObjectStorage::DirectUpload do + let(:credentials) do + { + provider: 'AWS', + aws_access_key_id: 'AWS_ACCESS_KEY_ID', + aws_secret_access_key: 'AWS_SECRET_ACCESS_KEY' + } + end + + let(:storage_url) { 'https://uploads.s3.amazonaws.com/' } + + let(:bucket_name) { 'uploads' } + let(:object_name) { 'tmp/uploads/my-file' } + let(:maximum_size) { 1.gigabyte } + + let(:direct_upload) { described_class.new(credentials, bucket_name, object_name, has_length: has_length, maximum_size: maximum_size) } + + describe '#has_length' do + context 'is known' do + let(:has_length) { true } + let(:maximum_size) { nil } + + it "maximum size is not required" do + expect { direct_upload }.not_to raise_error + end + end + + context 'is unknown' do + let(:has_length) { false } + + context 'and maximum size is specified' do + let(:maximum_size) { 1.gigabyte } + + it "does not raise an error" do + expect { direct_upload }.not_to raise_error + end + end + + context 'and maximum size is not specified' do + let(:maximum_size) { nil } + + it "raises an error" do + expect { direct_upload }.to raise_error /maximum_size has to be specified if length is unknown/ + end + end + end + end + + describe '#to_hash' do + subject { direct_upload.to_hash } + + shared_examples 'a valid upload' do + it "returns valid structure" do + expect(subject).to have_key(:Timeout) + expect(subject[:GetURL]).to start_with(storage_url) + expect(subject[:StoreURL]).to start_with(storage_url) + expect(subject[:DeleteURL]).to start_with(storage_url) + end + end + + shared_examples 'a valid upload with multipart data' do + before do + stub_object_storage_multipart_init(storage_url, "myUpload") + end + + it_behaves_like 'a valid upload' + + it "returns valid structure" do + expect(subject).to have_key(:MultipartUpload) + expect(subject[:MultipartUpload]).to have_key(:PartSize) + expect(subject[:MultipartUpload][:PartURLs]).to all(start_with(storage_url)) + expect(subject[:MultipartUpload][:PartURLs]).to all(include('uploadId=myUpload')) + expect(subject[:MultipartUpload][:CompleteURL]).to start_with(storage_url) + expect(subject[:MultipartUpload][:CompleteURL]).to include('uploadId=myUpload') + expect(subject[:MultipartUpload][:AbortURL]).to start_with(storage_url) + expect(subject[:MultipartUpload][:AbortURL]).to include('uploadId=myUpload') + end + end + + shared_examples 'a valid upload without multipart data' do + it_behaves_like 'a valid upload' + + it "returns valid structure" do + expect(subject).not_to have_key(:MultipartUpload) + end + end + + context 'when AWS is used' do + context 'when length is known' do + let(:has_length) { true } + + it_behaves_like 'a valid upload without multipart data' + end + + context 'when length is unknown' do + let(:has_length) { false } + + it_behaves_like 'a valid upload with multipart data' do + context 'when maximum upload size is 10MB' do + let(:maximum_size) { 10.megabyte } + + it 'returns only 2 parts' do + expect(subject[:MultipartUpload][:PartURLs].length).to eq(2) + end + + it 'part size is mimimum, 5MB' do + expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte) + end + end + + context 'when maximum upload size is 12MB' do + let(:maximum_size) { 12.megabyte } + + it 'returns only 3 parts' do + expect(subject[:MultipartUpload][:PartURLs].length).to eq(3) + end + + it 'part size is rounded-up to 5MB' do + expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte) + end + end + + context 'when maximum upload size is 49GB' do + let(:maximum_size) { 49.gigabyte } + + it 'returns maximum, 100 parts' do + expect(subject[:MultipartUpload][:PartURLs].length).to eq(100) + end + + it 'part size is rounded-up to 5MB' do + expect(subject[:MultipartUpload][:PartSize]).to eq(505.megabyte) + end + end + end + end + end + + context 'when Google is used' do + let(:credentials) do + { + provider: 'Google', + google_storage_access_key_id: 'GOOGLE_ACCESS_KEY_ID', + google_storage_secret_access_key: 'GOOGLE_SECRET_ACCESS_KEY' + } + end + + let(:storage_url) { 'https://storage.googleapis.com/uploads/' } + + context 'when length is known' do + let(:has_length) { true } + + it_behaves_like 'a valid upload without multipart data' + end + + context 'when length is unknown' do + let(:has_length) { false } + + it_behaves_like 'a valid upload without multipart data' + end + end + end + + describe '#get_url' do + # this method can only be tested with integration tests + end + + describe '#delete_url' do + # this method can only be tested with integration tests + end + + describe '#store_url' do + # this method can only be tested with integration tests + end + + describe '#multipart_part_upload_url' do + # this method can only be tested with integration tests + end + + describe '#multipart_complete_url' do + # this method can only be tested with integration tests + end + + describe '#multipart_abort_url' do + # this method can only be tested with integration tests + end +end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 319ac389083..c981a10ac38 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -1101,6 +1101,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do expect(json_response['RemoteObject']).to have_key('GetURL') expect(json_response['RemoteObject']).to have_key('StoreURL') expect(json_response['RemoteObject']).to have_key('DeleteURL') + expect(json_response['RemoteObject']).to have_key('MultipartUpload') end end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 79672fe1cc5..4d30b99262e 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -1021,6 +1021,7 @@ describe 'Git LFS API and storage' do expect(json_response['RemoteObject']).to have_key('GetURL') expect(json_response['RemoteObject']).to have_key('StoreURL') expect(json_response['RemoteObject']).to have_key('DeleteURL') + expect(json_response['RemoteObject']).not_to have_key('MultipartUpload') expect(json_response['LfsOid']).to eq(sample_oid) expect(json_response['LfsSize']).to eq(sample_size) end diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb index 19d744b959a..80204bdab8b 100644 --- a/spec/support/helpers/stub_object_storage.rb +++ b/spec/support/helpers/stub_object_storage.rb @@ -45,4 +45,16 @@ module StubObjectStorage remote_directory: 'uploads', **params) end + + def stub_object_storage_multipart_init(endpoint, upload_id = "upload_id") + stub_request(:post, %r{\A#{endpoint}tmp/uploads/[a-z0-9-]*\?uploads\z}). + to_return status: 200, body: <<-EOS.strip_heredoc + + + example-bucket + example-object + #{upload_id} + + EOS + end end diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb index 2dd0925a8e6..01166865e88 100644 --- a/spec/uploaders/object_storage_spec.rb +++ b/spec/uploaders/object_storage_spec.rb @@ -355,7 +355,10 @@ describe ObjectStorage do end describe '.workhorse_authorize' do - subject { uploader_class.workhorse_authorize } + let(:has_length) { true } + let(:maximum_size) { nil } + + subject { uploader_class.workhorse_authorize(has_length: has_length, maximum_size: maximum_size) } before do # ensure that we use regular Fog libraries @@ -371,10 +374,6 @@ describe ObjectStorage do expect(subject[:TempPath]).to start_with(uploader_class.root) expect(subject[:TempPath]).to include(described_class::TMP_UPLOAD_PATH) end - - it "does not return remote store" do - is_expected.not_to have_key('RemoteObject') - end end shared_examples 'uses remote storage' do @@ -383,7 +382,7 @@ describe ObjectStorage do expect(subject[:RemoteObject]).to have_key(:ID) expect(subject[:RemoteObject]).to include(Timeout: a_kind_of(Integer)) - expect(subject[:RemoteObject][:Timeout]).to be(ObjectStorage::DIRECT_UPLOAD_TIMEOUT) + expect(subject[:RemoteObject][:Timeout]).to be(ObjectStorage::DirectUpload::TIMEOUT) expect(subject[:RemoteObject]).to have_key(:GetURL) expect(subject[:RemoteObject]).to have_key(:DeleteURL) expect(subject[:RemoteObject]).to have_key(:StoreURL) @@ -391,9 +390,31 @@ describe ObjectStorage do expect(subject[:RemoteObject][:DeleteURL]).to include(described_class::TMP_UPLOAD_PATH) expect(subject[:RemoteObject][:StoreURL]).to include(described_class::TMP_UPLOAD_PATH) end + end - it "does not return local store" do - is_expected.not_to have_key('TempPath') + shared_examples 'uses remote storage with multipart uploads' do + it_behaves_like 'uses remote storage' do + it "returns multipart upload" do + is_expected.to have_key(:RemoteObject) + + expect(subject[:RemoteObject]).to have_key(:MultipartUpload) + expect(subject[:RemoteObject][:MultipartUpload]).to have_key(:PartSize) + expect(subject[:RemoteObject][:MultipartUpload]).to have_key(:PartURLs) + expect(subject[:RemoteObject][:MultipartUpload]).to have_key(:CompleteURL) + expect(subject[:RemoteObject][:MultipartUpload]).to have_key(:AbortURL) + expect(subject[:RemoteObject][:MultipartUpload][:PartURLs]).to all(include(described_class::TMP_UPLOAD_PATH)) + expect(subject[:RemoteObject][:MultipartUpload][:CompleteURL]).to include(described_class::TMP_UPLOAD_PATH) + expect(subject[:RemoteObject][:MultipartUpload][:AbortURL]).to include(described_class::TMP_UPLOAD_PATH) + end + end + end + + shared_examples 'uses remote storage without multipart uploads' do + it_behaves_like 'uses remote storage' do + it "does not return multipart upload" do + is_expected.to have_key(:RemoteObject) + expect(subject[:RemoteObject]).not_to have_key(:MultipartUpload) + end end end @@ -416,6 +437,8 @@ describe ObjectStorage do end context 'uses AWS' do + let(:storage_url) { "https://uploads.s3-eu-central-1.amazonaws.com/" } + before do expect(uploader_class).to receive(:object_store_credentials) do { provider: "AWS", @@ -425,18 +448,40 @@ describe ObjectStorage do end end - it_behaves_like 'uses remote storage' do - let(:storage_url) { "https://uploads.s3-eu-central-1.amazonaws.com/" } + context 'for known length' do + it_behaves_like 'uses remote storage without multipart uploads' do + it 'returns links for S3' do + expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url) + end + end + end + + context 'for unknown length' do + let(:has_length) { false } + let(:maximum_size) { 1.gigabyte } - it 'returns links for S3' do - expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url) - expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url) - expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url) + before do + stub_object_storage_multipart_init(storage_url) + end + + it_behaves_like 'uses remote storage with multipart uploads' do + it 'returns links for S3' do + expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:MultipartUpload][:PartURLs]).to all(start_with(storage_url)) + expect(subject[:RemoteObject][:MultipartUpload][:CompleteURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:MultipartUpload][:AbortURL]).to start_with(storage_url) + end end end end context 'uses Google' do + let(:storage_url) { "https://storage.googleapis.com/uploads/" } + before do expect(uploader_class).to receive(:object_store_credentials) do { provider: "Google", @@ -445,36 +490,71 @@ describe ObjectStorage do end end - it_behaves_like 'uses remote storage' do - let(:storage_url) { "https://storage.googleapis.com/uploads/" } + context 'for known length' do + it_behaves_like 'uses remote storage without multipart uploads' do + it 'returns links for Google Cloud' do + expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url) + end + end + end + + context 'for unknown length' do + let(:has_length) { false } + let(:maximum_size) { 1.gigabyte } - it 'returns links for Google Cloud' do - expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url) - expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url) - expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url) + it_behaves_like 'uses remote storage without multipart uploads' do + it 'returns links for Google Cloud' do + expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url) + end end end end context 'uses GDK/minio' do + let(:storage_url) { "http://minio:9000/uploads/" } + before do expect(uploader_class).to receive(:object_store_credentials) do { provider: "AWS", aws_access_key_id: "AWS_ACCESS_KEY_ID", aws_secret_access_key: "AWS_SECRET_ACCESS_KEY", - endpoint: 'http://127.0.0.1:9000', + endpoint: 'http://minio:9000', path_style: true, region: "gdk" } end end - it_behaves_like 'uses remote storage' do - let(:storage_url) { "http://127.0.0.1:9000/uploads/" } + context 'for known length' do + it_behaves_like 'uses remote storage without multipart uploads' do + it 'returns links for S3' do + expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url) + end + end + end + + context 'for unknown length' do + let(:has_length) { false } + let(:maximum_size) { 1.gigabyte } - it 'returns links for S3' do - expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url) - expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url) - expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url) + before do + stub_object_storage_multipart_init(storage_url) + end + + it_behaves_like 'uses remote storage with multipart uploads' do + it 'returns links for S3' do + expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:MultipartUpload][:PartURLs]).to all(start_with(storage_url)) + expect(subject[:RemoteObject][:MultipartUpload][:CompleteURL]).to start_with(storage_url) + expect(subject[:RemoteObject][:MultipartUpload][:AbortURL]).to start_with(storage_url) + end end end end -- cgit v1.2.1 From 7350eb1fa83662d4aaa7541acb387b3742ba9788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20L=C3=B3pez?= Date: Mon, 4 Jun 2018 11:41:37 +0000 Subject: Add ability to search wiki titles --- app/helpers/search_helper.rb | 12 +++- app/models/project_wiki.rb | 4 -- app/views/search/results/_blob.html.haml | 18 ++--- app/views/search/results/_blob_data.html.haml | 9 +++ app/views/search/results/_wiki_blob.html.haml | 15 ++-- .../fj-34526-enabling-wiki-search-by-title.yml | 5 ++ lib/api/search.rb | 4 +- lib/gitlab/file_finder.rb | 26 +++++-- lib/gitlab/project_search_results.rb | 3 +- lib/gitlab/wiki_file_finder.rb | 23 ++++++ spec/lib/gitlab/file_finder_spec.rb | 24 ++----- spec/lib/gitlab/project_search_results_spec.rb | 82 +++++++++------------- spec/lib/gitlab/wiki_file_finder_spec.rb | 20 ++++++ spec/support/shared_examples/file_finder.rb | 21 ++++++ 14 files changed, 159 insertions(+), 107 deletions(-) create mode 100644 app/views/search/results/_blob_data.html.haml create mode 100644 changelogs/unreleased/fj-34526-enabling-wiki-search-by-title.yml create mode 100644 lib/gitlab/wiki_file_finder.rb create mode 100644 spec/lib/gitlab/wiki_file_finder_spec.rb create mode 100644 spec/support/shared_examples/file_finder.rb diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 761c1252fc8..f7dafca7834 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -25,14 +25,22 @@ module SearchHelper return unless collection.count > 0 from = collection.offset_value + 1 - to = collection.offset_value + collection.length + to = collection.offset_value + collection.count count = collection.total_count "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\"" end + def find_project_for_result_blob(result) + @project + end + def parse_search_result(result) - Gitlab::ProjectSearchResults.parse_search_result(result) + result + end + + def search_blob_title(project, filename) + filename end private diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index f799a0b4227..a6f94b3e3b0 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -140,10 +140,6 @@ class ProjectWiki [title, title_array.join("/")] end - def search_files(query) - repository.search_files_by_content(query, default_branch) - end - def repository @repository ||= Repository.new(full_path, @project, disk_path: disk_path, is_wiki: true) end diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index de473c23d66..fdcd126e7a3 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -1,13 +1,5 @@ -- file_name, blob = blob -.blob-result - .file-holder - .js-file-title.file-title - - ref = @search_results.repository_ref - - blob_link = project_blob_path(@project, tree_join(ref, file_name)) - = link_to blob_link do - %i.fa.fa-file - %strong - = file_name - - if blob - .file-content.code.term - = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link +- project = find_project_for_result_blob(blob) +- file_name, blob = parse_search_result(blob) +- blob_link = project_blob_path(project, tree_join(blob.ref, file_name)) + += render partial: 'search/results/blob_data', locals: { blob: blob, project: project, file_name: file_name, blob_link: blob_link } diff --git a/app/views/search/results/_blob_data.html.haml b/app/views/search/results/_blob_data.html.haml new file mode 100644 index 00000000000..0115be41ff1 --- /dev/null +++ b/app/views/search/results/_blob_data.html.haml @@ -0,0 +1,9 @@ +.blob-result + .file-holder + .js-file-title.file-title + = link_to blob_link do + %i.fa.fa-file + = search_blob_title(project, file_name) + - if blob.data + .file-content.code.term + = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml index 16a0e432d62..4346217c230 100644 --- a/app/views/search/results/_wiki_blob.html.haml +++ b/app/views/search/results/_wiki_blob.html.haml @@ -1,10 +1,5 @@ -- wiki_blob = parse_search_result(wiki_blob) -.blob-result - .file-holder - .js-file-title.file-title - = link_to project_wiki_path(@project, wiki_blob.basename) do - %i.fa.fa-file - %strong - = wiki_blob.basename - .file-content.code.term - = render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline +- project = find_project_for_result_blob(wiki_blob) +- file_name, wiki_blob = parse_search_result(wiki_blob) +- wiki_blob_link = project_wiki_path(project, wiki_blob.basename) + += render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: file_name, blob_link: wiki_blob_link } diff --git a/changelogs/unreleased/fj-34526-enabling-wiki-search-by-title.yml b/changelogs/unreleased/fj-34526-enabling-wiki-search-by-title.yml new file mode 100644 index 00000000000..2ae2cf8a23e --- /dev/null +++ b/changelogs/unreleased/fj-34526-enabling-wiki-search-by-title.yml @@ -0,0 +1,5 @@ +--- +title: Added ability to search by wiki titles +merge_request: 19112 +author: +type: added diff --git a/lib/api/search.rb b/lib/api/search.rb index 5d9ec617cb7..37fbabe419c 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -34,9 +34,7 @@ module API def process_results(results) case params[:scope] - when 'wiki_blobs' - paginate(results).map { |blob| Gitlab::ProjectSearchResults.parse_search_result(blob, user_project) } - when 'blobs' + when 'blobs', 'wiki_blobs' paginate(results).map { |blob| blob[1] } else paginate(results) diff --git a/lib/gitlab/file_finder.rb b/lib/gitlab/file_finder.rb index 8c082c0c336..f42088f980e 100644 --- a/lib/gitlab/file_finder.rb +++ b/lib/gitlab/file_finder.rb @@ -32,17 +32,13 @@ module Gitlab end def find_by_filename(query, except: []) - filenames = repository.search_files_by_name(query, ref).first(BATCH_SIZE) - filenames.delete_if { |filename| except.include?(filename) } unless except.empty? + filenames = search_filenames(query, except) - blob_refs = filenames.map { |filename| [ref, filename] } - blobs = Gitlab::Git::Blob.batch(repository, blob_refs, blob_size_limit: 1024) - - blobs.map do |blob| + blobs(filenames).map do |blob| Gitlab::SearchResults::FoundBlob.new( id: blob.id, filename: blob.path, - basename: File.basename(blob.path), + basename: File.basename(blob.path, File.extname(blob.path)), ref: ref, startline: 1, data: blob.data, @@ -50,5 +46,21 @@ module Gitlab ) end end + + def search_filenames(query, except) + filenames = repository.search_files_by_name(query, ref).first(BATCH_SIZE) + + filenames.delete_if { |filename| except.include?(filename) } unless except.empty? + + filenames + end + + def blob_refs(filenames) + filenames.map { |filename| [ref, filename] } + end + + def blobs(filenames) + Gitlab::Git::Blob.batch(repository, blob_refs(filenames), blob_size_limit: 1024) + end end end diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 2e9b6e302f5..38bdc61d8ab 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -106,7 +106,8 @@ module Gitlab project_wiki = ProjectWiki.new(project) unless project_wiki.empty? - project_wiki.search_files(query) + ref = repository_ref || project.wiki.default_branch + Gitlab::WikiFileFinder.new(project, ref).find(query) else [] end diff --git a/lib/gitlab/wiki_file_finder.rb b/lib/gitlab/wiki_file_finder.rb new file mode 100644 index 00000000000..f97278f05cd --- /dev/null +++ b/lib/gitlab/wiki_file_finder.rb @@ -0,0 +1,23 @@ +module Gitlab + class WikiFileFinder < FileFinder + attr_reader :repository + + def initialize(project, ref) + @project = project + @ref = ref + @repository = project.wiki.repository + end + + private + + def search_filenames(query, except) + safe_query = Regexp.escape(query.tr(' ', '-')) + safe_query = Regexp.new(safe_query, Regexp::IGNORECASE) + filenames = repository.ls_files(ref) + + filenames.delete_if { |filename| except.include?(filename) } unless except.empty? + + filenames.grep(safe_query).first(BATCH_SIZE) + end + end +end diff --git a/spec/lib/gitlab/file_finder_spec.rb b/spec/lib/gitlab/file_finder_spec.rb index 07cb10e563e..d6d9e4001a3 100644 --- a/spec/lib/gitlab/file_finder_spec.rb +++ b/spec/lib/gitlab/file_finder_spec.rb @@ -3,27 +3,11 @@ require 'spec_helper' describe Gitlab::FileFinder do describe '#find' do let(:project) { create(:project, :public, :repository) } - let(:finder) { described_class.new(project, project.default_branch) } - it 'finds by name' do - results = finder.find('files') - - filename, blob = results.find { |_, blob| blob.filename == 'files/images/wm.svg' } - expect(filename).to eq('files/images/wm.svg') - expect(blob).to be_a(Gitlab::SearchResults::FoundBlob) - expect(blob.ref).to eq(finder.ref) - expect(blob.data).not_to be_empty - end - - it 'finds by content' do - results = finder.find('files') - - filename, blob = results.find { |_, blob| blob.filename == 'CHANGELOG' } - - expect(filename).to eq('CHANGELOG') - expect(blob).to be_a(Gitlab::SearchResults::FoundBlob) - expect(blob.ref).to eq(finder.ref) - expect(blob.data).not_to be_empty + it_behaves_like 'file finder' do + subject { described_class.new(project, project.default_branch) } + let(:expected_file_by_name) { 'files/images/wm.svg' } + let(:expected_file_by_content) { 'CHANGELOG' } end end end diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index e3f705d2299..50224bde722 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -22,47 +22,57 @@ describe Gitlab::ProjectSearchResults do it { expect(results.query).to eq('hello world') } end - describe 'blob search' do - let(:project) { create(:project, :public, :repository) } - - subject(:results) { described_class.new(user, project, 'files').objects('blobs') } - - context 'when repository is disabled' do - let(:project) { create(:project, :public, :repository, :repository_disabled) } + shared_examples 'general blob search' do |entity_type, blob_kind| + let(:query) { 'files' } + subject(:results) { described_class.new(user, project, query).objects(blob_type) } - it 'hides blobs from members' do + context "when #{entity_type} is disabled" do + let(:project) { disabled_project } + it "hides #{blob_kind} from members" do project.add_reporter(user) is_expected.to be_empty end - it 'hides blobs from non-members' do + it "hides #{blob_kind} from non-members" do is_expected.to be_empty end end - context 'when repository is internal' do - let(:project) { create(:project, :public, :repository, :repository_private) } + context "when #{entity_type} is internal" do + let(:project) { private_project } - it 'finds blobs for members' do + it "finds #{blob_kind} for members" do project.add_reporter(user) is_expected.not_to be_empty end - it 'hides blobs from non-members' do + it "hides #{blob_kind} from non-members" do is_expected.to be_empty end end it 'finds by name' do - expect(results.map(&:first)).to include('files/images/wm.svg') + expect(results.map(&:first)).to include(expected_file_by_name) end it 'finds by content' do - blob = results.select { |result| result.first == "CHANGELOG" }.flatten.last + blob = results.select { |result| result.first == expected_file_by_content }.flatten.last - expect(blob.filename).to eq("CHANGELOG") + expect(blob.filename).to eq(expected_file_by_content) + end + end + + describe 'blob search' do + let(:project) { create(:project, :public, :repository) } + + it_behaves_like 'general blob search', 'repository', 'blobs' do + let(:blob_type) { 'blobs' } + let(:disabled_project) { create(:project, :public, :repository, :repository_disabled) } + let(:private_project) { create(:project, :public, :repository, :repository_private) } + let(:expected_file_by_name) { 'files/images/wm.svg' } + let(:expected_file_by_content) { 'CHANGELOG' } end describe 'parsing results' do @@ -189,40 +199,18 @@ describe Gitlab::ProjectSearchResults do describe 'wiki search' do let(:project) { create(:project, :public, :wiki_repo) } let(:wiki) { build(:project_wiki, project: project) } - let!(:wiki_page) { wiki.create_page('Title', 'Content') } - - subject(:results) { described_class.new(user, project, 'Content').objects('wiki_blobs') } - - context 'when wiki is disabled' do - let(:project) { create(:project, :public, :wiki_repo, :wiki_disabled) } - it 'hides wiki blobs from members' do - project.add_reporter(user) - - is_expected.to be_empty - end - - it 'hides wiki blobs from non-members' do - is_expected.to be_empty - end - end - - context 'when wiki is internal' do - let(:project) { create(:project, :public, :wiki_repo, :wiki_private) } - - it 'finds wiki blobs for guest' do - project.add_guest(user) - - is_expected.not_to be_empty - end - - it 'hides wiki blobs from non-members' do - is_expected.to be_empty - end + before do + wiki.create_page('Files/Title', 'Content') + wiki.create_page('CHANGELOG', 'Files example') end - it 'finds by content' do - expect(results).to include("master:Title.md\x001\x00Content\n") + it_behaves_like 'general blob search', 'wiki', 'wiki blobs' do + let(:blob_type) { 'wiki_blobs' } + let(:disabled_project) { create(:project, :public, :wiki_repo, :wiki_disabled) } + let(:private_project) { create(:project, :public, :wiki_repo, :wiki_private) } + let(:expected_file_by_name) { 'Files/Title.md' } + let(:expected_file_by_content) { 'CHANGELOG.md' } end end diff --git a/spec/lib/gitlab/wiki_file_finder_spec.rb b/spec/lib/gitlab/wiki_file_finder_spec.rb new file mode 100644 index 00000000000..025d1203dc5 --- /dev/null +++ b/spec/lib/gitlab/wiki_file_finder_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::WikiFileFinder do + describe '#find' do + let(:project) { create(:project, :public, :wiki_repo) } + let(:wiki) { build(:project_wiki, project: project) } + + before do + wiki.create_page('Files/Title', 'Content') + wiki.create_page('CHANGELOG', 'Files example') + end + + it_behaves_like 'file finder' do + subject { described_class.new(project, project.wiki.default_branch) } + + let(:expected_file_by_name) { 'Files/Title.md' } + let(:expected_file_by_content) { 'CHANGELOG.md' } + end + end +end diff --git a/spec/support/shared_examples/file_finder.rb b/spec/support/shared_examples/file_finder.rb new file mode 100644 index 00000000000..ef144bdf61c --- /dev/null +++ b/spec/support/shared_examples/file_finder.rb @@ -0,0 +1,21 @@ +shared_examples 'file finder' do + let(:query) { 'files' } + let(:search_results) { subject.find(query) } + + it 'finds by name' do + filename, blob = search_results.find { |_, blob| blob.filename == expected_file_by_name } + expect(filename).to eq(expected_file_by_name) + expect(blob).to be_a(Gitlab::SearchResults::FoundBlob) + expect(blob.ref).to eq(subject.ref) + expect(blob.data).not_to be_empty + end + + it 'finds by content' do + filename, blob = search_results.find { |_, blob| blob.filename == expected_file_by_content } + + expect(filename).to eq(expected_file_by_content) + expect(blob).to be_a(Gitlab::SearchResults::FoundBlob) + expect(blob.ref).to eq(subject.ref) + expect(blob.data).not_to be_empty + end +end -- cgit v1.2.1 From 4f20bf956a179bc4ffdfa23168925c102eb2e88b Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Mon, 4 Jun 2018 12:42:02 +0000 Subject: Backport EE SlashCommand Refactor --- lib/gitlab/slash_commands/command.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/slash_commands/command.rb b/lib/gitlab/slash_commands/command.rb index bb778f37096..c82320a6036 100644 --- a/lib/gitlab/slash_commands/command.rb +++ b/lib/gitlab/slash_commands/command.rb @@ -1,13 +1,15 @@ module Gitlab module SlashCommands class Command < BaseCommand - COMMANDS = [ - Gitlab::SlashCommands::IssueShow, - Gitlab::SlashCommands::IssueNew, - Gitlab::SlashCommands::IssueSearch, - Gitlab::SlashCommands::IssueMove, - Gitlab::SlashCommands::Deploy - ].freeze + def self.commands + [ + Gitlab::SlashCommands::IssueShow, + Gitlab::SlashCommands::IssueNew, + Gitlab::SlashCommands::IssueSearch, + Gitlab::SlashCommands::IssueMove, + Gitlab::SlashCommands::Deploy + ] + end def execute command, match = match_command @@ -37,7 +39,7 @@ module Gitlab private def available_commands - COMMANDS.select do |klass| + self.class.commands.keep_if do |klass| klass.available?(project) end end -- cgit v1.2.1 From 56d70009b1e6fb55870c132209b152bad2a7f086 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 4 Jun 2018 15:52:21 +0100 Subject: Make error in clusters occupy full width --- app/assets/javascripts/clusters/components/application_row.vue | 2 +- app/assets/stylesheets/pages/clusters.scss | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index 30567993322..98c0b9c22a8 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -187,7 +187,7 @@ role="row" >
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index 3e4d123242c..56beb7718a4 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -13,6 +13,10 @@ max-width: 100%; } +.clusters-error-alert { + width: 100%; +} + .clusters-container { .nav-bar-right { padding: $gl-padding-top $gl-padding; -- cgit v1.2.1 From 840ce49d4b682e6f39cd22804be25d3c0311969b Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Mon, 4 Jun 2018 17:02:49 +0200 Subject: Fixes the Terms Page --- app/assets/stylesheets/framework/terms.scss | 8 +++++--- app/views/layouts/terms.html.haml | 6 +++--- app/views/users/terms/index.html.haml | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/framework/terms.scss b/app/assets/stylesheets/framework/terms.scss index 744fd0ff796..7cda674e5c8 100644 --- a/app/assets/stylesheets/framework/terms.scss +++ b/app/assets/stylesheets/framework/terms.scss @@ -11,15 +11,15 @@ padding-top: $gl-padding; } - .panel { - .panel-heading { + .card { + .card-header { display: -webkit-flex; display: flex; align-items: center; justify-content: space-between; line-height: $line-height-base; - .title { + .card-title { display: flex; align-items: center; @@ -34,6 +34,8 @@ .navbar-collapse { padding-right: 0; + flex-grow: 0; + flex-basis: auto; .navbar-nav { margin: 0; diff --git a/app/views/layouts/terms.html.haml b/app/views/layouts/terms.html.haml index a8964b19ba1..977eb350365 100644 --- a/app/views/layouts/terms.html.haml +++ b/app/views/layouts/terms.html.haml @@ -14,9 +14,9 @@ %div{ class: "#{container_class} limit-container-width" } .content{ id: "content-body" } - .panel.panel-default - .panel-heading - .title + .card + .card-header + .card-title = brand_header_logo - logo_text = brand_header_logo_type - if logo_text.present? diff --git a/app/views/users/terms/index.html.haml b/app/views/users/terms/index.html.haml index e0fe551cf36..b9f25a71170 100644 --- a/app/views/users/terms/index.html.haml +++ b/app/views/users/terms/index.html.haml @@ -1,8 +1,8 @@ - redirect_params = { redirect: @redirect } if @redirect -.panel-content.rendered-terms +.card-body.rendered-terms = markdown_field(@term, :terms) -.row-content-block.footer-block.clearfix +.card-footer.footer-block.clearfix - if can?(current_user, :accept_terms, @term) .float-right = button_to accept_term_path(@term, redirect_params), class: 'btn btn-success prepend-left-8' do -- cgit v1.2.1 From 2ae48f1ca910031a77550e1f85492d9786c3069e Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Fri, 18 May 2018 17:33:57 +0200 Subject: Add QA integration test for full Auto DevOps flow --- qa/qa.rb | 12 ++++ qa/qa/factory/resource/kubernetes_cluster.rb | 55 +++++++++++++++++ qa/qa/fixtures/auto_devops_rack/Gemfile | 3 + qa/qa/fixtures/auto_devops_rack/Gemfile.lock | 15 +++++ qa/qa/fixtures/auto_devops_rack/Rakefile | 7 +++ qa/qa/fixtures/auto_devops_rack/config.ru | 1 + qa/qa/page/menu/side.rb | 18 ++++++ qa/qa/page/project/operations/kubernetes/add.rb | 19 ++++++ .../project/operations/kubernetes/add_existing.rb | 39 ++++++++++++ qa/qa/page/project/operations/kubernetes/index.rb | 19 ++++++ qa/qa/page/project/operations/kubernetes/show.rb | 39 ++++++++++++ qa/qa/page/project/pipeline/show.rb | 4 +- qa/qa/page/project/settings/ci_cd.rb | 15 +++++ qa/qa/scenario/test/integration/kubernetes.rb | 11 ++++ qa/qa/service/kubernetes_cluster.rb | 65 ++++++++++++++++++++ qa/qa/service/runner.rb | 1 - qa/qa/specs/features/project/auto_devops_spec.rb | 69 ++++++++++++++++++++++ 17 files changed, 389 insertions(+), 3 deletions(-) create mode 100644 qa/qa/factory/resource/kubernetes_cluster.rb create mode 100644 qa/qa/fixtures/auto_devops_rack/Gemfile create mode 100644 qa/qa/fixtures/auto_devops_rack/Gemfile.lock create mode 100644 qa/qa/fixtures/auto_devops_rack/Rakefile create mode 100644 qa/qa/fixtures/auto_devops_rack/config.ru create mode 100644 qa/qa/page/project/operations/kubernetes/add.rb create mode 100644 qa/qa/page/project/operations/kubernetes/add_existing.rb create mode 100644 qa/qa/page/project/operations/kubernetes/index.rb create mode 100644 qa/qa/page/project/operations/kubernetes/show.rb create mode 100644 qa/qa/scenario/test/integration/kubernetes.rb create mode 100644 qa/qa/service/kubernetes_cluster.rb create mode 100644 qa/qa/specs/features/project/auto_devops_spec.rb diff --git a/qa/qa.rb b/qa/qa.rb index 40e12c8b336..7f2da05dd63 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -41,6 +41,7 @@ module QA autoload :SecretVariable, 'qa/factory/resource/secret_variable' autoload :Runner, 'qa/factory/resource/runner' autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token' + autoload :KubernetesCluster, 'qa/factory/resource/kubernetes_cluster' end module Repository @@ -72,6 +73,7 @@ module QA module Integration autoload :LDAP, 'qa/scenario/test/integration/ldap' + autoload :Kubernetes, 'qa/scenario/test/integration/kubernetes' autoload :Mattermost, 'qa/scenario/test/integration/mattermost' end @@ -150,6 +152,15 @@ module QA autoload :Show, 'qa/page/project/issue/show' autoload :Index, 'qa/page/project/issue/index' end + + module Operations + module Kubernetes + autoload :Index, 'qa/page/project/operations/kubernetes/index' + autoload :Add, 'qa/page/project/operations/kubernetes/add' + autoload :AddExisting, 'qa/page/project/operations/kubernetes/add_existing' + autoload :Show, 'qa/page/project/operations/kubernetes/show' + end + end end module Profile @@ -195,6 +206,7 @@ module QA # module Service autoload :Shellout, 'qa/service/shellout' + autoload :KubernetesCluster, 'qa/service/kubernetes_cluster' autoload :Omnibus, 'qa/service/omnibus' autoload :Runner, 'qa/service/runner' end diff --git a/qa/qa/factory/resource/kubernetes_cluster.rb b/qa/qa/factory/resource/kubernetes_cluster.rb new file mode 100644 index 00000000000..f32cf985e9d --- /dev/null +++ b/qa/qa/factory/resource/kubernetes_cluster.rb @@ -0,0 +1,55 @@ +require 'securerandom' + +module QA + module Factory + module Resource + class KubernetesCluster < Factory::Base + attr_writer :project, :cluster, + :install_helm_tiller, :install_ingress, :install_prometheus, :install_runner + + product :ingress_ip do + Page::Project::Operations::Kubernetes::Show.perform do |page| + page.ingress_ip + end + end + + def fabricate! + @project.visit! + + Page::Menu::Side.act { click_operations_kubernetes } + + Page::Project::Operations::Kubernetes::Index.perform do |page| + page.add_kubernetes_cluster + end + + Page::Project::Operations::Kubernetes::Add.perform do |page| + page.add_existing_cluster + end + + Page::Project::Operations::Kubernetes::AddExisting.perform do |page| + page.set_cluster_name(@cluster.cluster_name) + page.set_api_url(@cluster.api_url) + page.set_ca_certificate(@cluster.ca_certificate) + page.set_token(@cluster.token) + page.add_cluster! + end + + if @install_helm_tiller + Page::Project::Operations::Kubernetes::Show.perform do |page| + # Helm must be installed before everything else + page.install!(:helm) + page.await_installed(:helm) + + page.install!(:ingress) if @install_ingress + page.await_installed(:ingress) if @install_ingress + page.install!(:prometheus) if @install_prometheus + page.await_installed(:prometheus) if @install_prometheus + page.install!(:runner) if @install_runner + page.await_installed(:runner) if @install_runner + end + end + end + end + end + end +end diff --git a/qa/qa/fixtures/auto_devops_rack/Gemfile b/qa/qa/fixtures/auto_devops_rack/Gemfile new file mode 100644 index 00000000000..fc7514242d0 --- /dev/null +++ b/qa/qa/fixtures/auto_devops_rack/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' +gem 'rack' +gem 'rake' diff --git a/qa/qa/fixtures/auto_devops_rack/Gemfile.lock b/qa/qa/fixtures/auto_devops_rack/Gemfile.lock new file mode 100644 index 00000000000..09cf72c48ac --- /dev/null +++ b/qa/qa/fixtures/auto_devops_rack/Gemfile.lock @@ -0,0 +1,15 @@ +GEM + remote: https://rubygems.org/ + specs: + rack (2.0.4) + rake (12.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + rack + rake + +BUNDLED WITH + 1.16.1 diff --git a/qa/qa/fixtures/auto_devops_rack/Rakefile b/qa/qa/fixtures/auto_devops_rack/Rakefile new file mode 100644 index 00000000000..c865c9aaac1 --- /dev/null +++ b/qa/qa/fixtures/auto_devops_rack/Rakefile @@ -0,0 +1,7 @@ +require 'rake/testtask' + +task default: %w[test] + +task :test do + puts "ok" +end diff --git a/qa/qa/fixtures/auto_devops_rack/config.ru b/qa/qa/fixtures/auto_devops_rack/config.ru new file mode 100644 index 00000000000..bde8e15488a --- /dev/null +++ b/qa/qa/fixtures/auto_devops_rack/config.ru @@ -0,0 +1 @@ +run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, StringIO.new("Hello World!\n")] } diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb index 7e028add2ef..3630b7e8568 100644 --- a/qa/qa/page/menu/side.rb +++ b/qa/qa/page/menu/side.rb @@ -7,9 +7,11 @@ module QA element :settings_link, 'link_to edit_project_path' element :repository_link, "title: 'Repository'" element :pipelines_settings_link, "title: 'CI / CD'" + element :operations_kubernetes_link, "title: _('Kubernetes')" element :issues_link, /link_to.*shortcuts-issues/ element :issues_link_text, "Issues" element :top_level_items, '.sidebar-top-level-items' + element :operations_section, "class: 'shortcuts-operations'" element :activity_link, "title: 'Activity'" end @@ -33,6 +35,14 @@ module QA end end + def click_operations_kubernetes + hover_operations do + within_submenu do + click_link('Kubernetes') + end + end + end + def click_ci_cd_pipelines within_sidebar do click_link('CI / CD') @@ -61,6 +71,14 @@ module QA end end + def hover_operations + within_sidebar do + find('.shortcuts-operations').hover + + yield + end + end + def within_sidebar page.within('.sidebar-top-level-items') do yield diff --git a/qa/qa/page/project/operations/kubernetes/add.rb b/qa/qa/page/project/operations/kubernetes/add.rb new file mode 100644 index 00000000000..9b3c482fa6c --- /dev/null +++ b/qa/qa/page/project/operations/kubernetes/add.rb @@ -0,0 +1,19 @@ +module QA + module Page + module Project + module Operations + module Kubernetes + class Add < Page::Base + view 'app/views/projects/clusters/new.html.haml' do + element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add an existing Kubernetes cluster')" + end + + def add_existing_cluster + click_on 'Add an existing Kubernetes cluster' + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/operations/kubernetes/add_existing.rb b/qa/qa/page/project/operations/kubernetes/add_existing.rb new file mode 100644 index 00000000000..eef82b5f329 --- /dev/null +++ b/qa/qa/page/project/operations/kubernetes/add_existing.rb @@ -0,0 +1,39 @@ +module QA + module Page + module Project + module Operations + module Kubernetes + class AddExisting < Page::Base + view 'app/views/projects/clusters/user/_form.html.haml' do + element :cluster_name, 'text_field :name' + element :api_url, 'text_field :api_url' + element :ca_certificate, 'text_area :ca_cert' + element :token, 'text_field :token' + element :add_cluster_button, "submit s_('ClusterIntegration|Add Kubernetes cluster')" + end + + def set_cluster_name(name) + fill_in 'cluster_name', with: name + end + + def set_api_url(api_url) + fill_in 'cluster_platform_kubernetes_attributes_api_url', with: api_url + end + + def set_ca_certificate(ca_certificate) + fill_in 'cluster_platform_kubernetes_attributes_ca_cert', with: ca_certificate + end + + def set_token(token) + fill_in 'cluster_platform_kubernetes_attributes_token', with: token + end + + def add_cluster! + click_on 'Add Kubernetes cluster' + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/operations/kubernetes/index.rb b/qa/qa/page/project/operations/kubernetes/index.rb new file mode 100644 index 00000000000..7261b5645da --- /dev/null +++ b/qa/qa/page/project/operations/kubernetes/index.rb @@ -0,0 +1,19 @@ +module QA + module Page + module Project + module Operations + module Kubernetes + class Index < Page::Base + view 'app/views/projects/clusters/_empty_state.html.haml' do + element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')" + end + + def add_kubernetes_cluster + click_on 'Add Kubernetes cluster' + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb new file mode 100644 index 00000000000..4923304133e --- /dev/null +++ b/qa/qa/page/project/operations/kubernetes/show.rb @@ -0,0 +1,39 @@ +module QA + module Page + module Project + module Operations + module Kubernetes + class Show < Page::Base + view 'app/assets/javascripts/clusters/components/application_row.vue' do + element :application_row, 'js-cluster-application-row-${this.id}' + element :install_button, "s__('ClusterIntegration|Install')" + element :installed_button, "s__('ClusterIntegration|Installed')" + end + + view 'app/assets/javascripts/clusters/components/applications.vue' do + element :ingress_ip_address, 'id="ingress-ip-address"' + end + + def install!(application_name) + within(".js-cluster-application-row-#{application_name}") do + click_on 'Install' + end + end + + def await_installed(application_name) + within(".js-cluster-application-row-#{application_name}") do + page.has_text?('Installed', wait: 300) + end + end + + def ingress_ip + # We need to wait longer since it can take some time before the + # ip address is assigned for the ingress controller + page.find('#ingress-ip-address', wait: 500).value + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index ec61c47b3bb..de849b3eee8 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -24,10 +24,10 @@ module QA::Page end end - def has_build?(name, status: :success) + def has_build?(name, status: :success, wait:) within('.pipeline-graph') do within('.ci-job-component', text: name) do - has_selector?(".ci-status-icon-#{status}") + has_selector?(".ci-status-icon-#{status}", wait: wait) end end end diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb index 145c3d3ddfa..dfb71e0a9f0 100644 --- a/qa/qa/page/project/settings/ci_cd.rb +++ b/qa/qa/page/project/settings/ci_cd.rb @@ -8,6 +8,13 @@ module QA # rubocop:disable Naming/FileName view 'app/views/projects/settings/ci_cd/show.html.haml' do element :runners_settings, 'Runners settings' element :secret_variables, 'Variables' + element :auto_devops_section, 'Auto DevOps' + end + + view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do + element :enable_auto_devops_button, 'Enable Auto DevOps' + element :domain_input, 'Domain' + element :save_changes_button, "submit 'Save changes'" end def expand_runners_settings(&block) @@ -21,6 +28,14 @@ module QA # rubocop:disable Naming/FileName Settings::SecretVariables.perform(&block) end end + + def enable_auto_devops_with_domain(domain) + expand_section('Auto DevOps') do + choose 'Enable Auto DevOps' + fill_in 'Domain', with: domain + click_on 'Save changes' + end + end end end end diff --git a/qa/qa/scenario/test/integration/kubernetes.rb b/qa/qa/scenario/test/integration/kubernetes.rb new file mode 100644 index 00000000000..7479073e979 --- /dev/null +++ b/qa/qa/scenario/test/integration/kubernetes.rb @@ -0,0 +1,11 @@ +module QA + module Scenario + module Test + module Integration + class Kubernetes < Test::Instance + tags :kubernetes + end + end + end + end +end diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb new file mode 100644 index 00000000000..01310900f7d --- /dev/null +++ b/qa/qa/service/kubernetes_cluster.rb @@ -0,0 +1,65 @@ +require 'securerandom' +require 'mkmf' + +module QA + module Service + class KubernetesCluster + include Service::Shellout + + attr_reader :api_url, :ca_certificate, :token + + def cluster_name + @cluster_name ||= "qa-cluster-#{SecureRandom.hex(4)}-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}" + end + + def create! + validate_dependencies + login_if_not_already_logged_in + + shell <<~CMD.tr("\n", ' ') + gcloud container clusters + create #{cluster_name} + --enable-legacy-authorization + && gcloud container clusters + get-credentials #{cluster_name} + CMD + + @api_url = `kubectl config view --minify -o jsonpath='{.clusters[].cluster.server}'` + @ca_certificate = Base64.decode64(`kubectl get secrets -o jsonpath="{.items[0].data['ca\\.crt']}"`) + @token = Base64.decode64(`kubectl get secrets -o jsonpath='{.items[0].data.token}'`) + self + end + + def remove! + shell("gcloud container clusters delete #{cluster_name} --quiet --async") + end + + private + + def validate_dependencies + find_executable('gcloud') || raise("You must first install `gcloud` executable to run these tests.") + find_executable('kubectl') || raise("You must first install `kubectl` executable to run these tests.") + end + + def login_if_not_already_logged_in + account = `gcloud auth list --filter=status:ACTIVE --format="value(account)"` + if account.empty? + attempt_login_with_env_vars + else + puts "gcloud account found. Using: #{account} for creating K8s cluster." + end + end + + def attempt_login_with_env_vars + puts "No gcloud account. Attempting to login from env vars GCLOUD_ACCOUNT_EMAIL and GCLOUD_ACCOUNT_KEY." + gcloud_account_key = Tempfile.new('gcloud-account-key') + gcloud_account_key.write(ENV.fetch("GCLOUD_ACCOUNT_KEY")) + gcloud_account_key.close + gcloud_account_email = ENV.fetch("GCLOUD_ACCOUNT_EMAIL") + shell("gcloud auth activate-service-account #{gcloud_account_email} --key-file #{gcloud_account_key.path}") + ensure + gcloud_account_key && gcloud_account_key.unlink + end + end + end +end diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb index c0352e0467a..9417c707105 100644 --- a/qa/qa/service/runner.rb +++ b/qa/qa/service/runner.rb @@ -3,7 +3,6 @@ require 'securerandom' module QA module Service class Runner - include Scenario::Actable include Service::Shellout attr_accessor :token, :address, :tags, :image diff --git a/qa/qa/specs/features/project/auto_devops_spec.rb b/qa/qa/specs/features/project/auto_devops_spec.rb new file mode 100644 index 00000000000..360a6f3552d --- /dev/null +++ b/qa/qa/specs/features/project/auto_devops_spec.rb @@ -0,0 +1,69 @@ +module QA + feature 'Auto Devops', :kubernetes do + let(:executor) { "qa-runner-#{Time.now.to_i}" } + + after do + @cluster&.remove! + end + + scenario 'user creates a new project and runs auto devops' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + project = Factory::Resource::Project.fabricate! do |p| + p.name = 'project-with-autodevops' + p.description = 'Project with Auto Devops' + end + + # Create Auto Devops compatible repo + project.visit! + Git::Repository.perform do |repository| + repository.uri = Page::Project::Show.act do + choose_repository_clone_http + repository_location.uri + end + + repository.use_default_credentials + repository.clone + repository.configure_identity('GitLab QA', 'root@gitlab.com') + + repository.checkout_new_branch('master') + repository.add_file('config.ru', File.read(File.join(__dir__, "../../../fixtures/auto_devops_rack/config.ru"))) + repository.add_file('Gemfile', File.read(File.join(__dir__, "../../../fixtures/auto_devops_rack/Gemfile"))) + repository.add_file('Gemfile.lock', File.read(File.join(__dir__, "../../../fixtures/auto_devops_rack/Gemfile.lock"))) + repository.add_file('Rakefile', File.read(File.join(__dir__, "../../../fixtures/auto_devops_rack/Rakefile"))) + repository.commit('Create auto devops repo') + repository.push_changes("master:master") + end + + # Create and connect K8s cluster + @cluster = Service::KubernetesCluster.new.create! + kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |c| + c.project = project + c.cluster = @cluster + c.install_helm_tiller = true + c.install_ingress = true + c.install_prometheus = true + c.install_runner = true + end + + project.visit! + Page::Menu::Side.act { click_ci_cd_settings } + Page::Project::Settings::CICD.perform do |p| + p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io") + end + + project.visit! + Page::Menu::Side.act { click_ci_cd_pipelines } + Page::Project::Pipeline::Index.act { go_to_latest_pipeline } + + Page::Project::Pipeline::Show.perform do |pipeline| + expect(pipeline).to have_build('build', status: :success, wait: 600) + expect(pipeline).to have_build('test', status: :success, wait: 600) + expect(pipeline).to have_build('sast', status: :success, wait: 600) + expect(pipeline).to have_build('production', status: :success, wait: 600) + expect(pipeline).to have_build('performance', status: :success, wait: 600) + end + end + end +end -- cgit v1.2.1 From 282c09ed91fac544e1f7e9b9430ef9e2e82e9d29 Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Mon, 4 Jun 2018 16:36:27 +0200 Subject: Do not wait for sast job to succeed in auto devops spec --- qa/qa/specs/features/project/auto_devops_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qa/qa/specs/features/project/auto_devops_spec.rb b/qa/qa/specs/features/project/auto_devops_spec.rb index 360a6f3552d..4ab4135a11c 100644 --- a/qa/qa/specs/features/project/auto_devops_spec.rb +++ b/qa/qa/specs/features/project/auto_devops_spec.rb @@ -60,9 +60,8 @@ module QA Page::Project::Pipeline::Show.perform do |pipeline| expect(pipeline).to have_build('build', status: :success, wait: 600) expect(pipeline).to have_build('test', status: :success, wait: 600) - expect(pipeline).to have_build('sast', status: :success, wait: 600) expect(pipeline).to have_build('production', status: :success, wait: 600) - expect(pipeline).to have_build('performance', status: :success, wait: 600) + expect(pipeline).to have_build('performance', status: :success, wait: 800) end end end -- cgit v1.2.1 From 2f50b206f2921faf47637af526d810bc10ffb3ef Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 4 Jun 2018 10:41:56 +0200 Subject: Hide archived projects from `shared_projects` Since we don't show the archived projects, we shouldnot load them and pass them to the fronted to be filtered out again. --- app/controllers/groups/shared_projects_controller.rb | 4 +++- spec/controllers/groups/shared_projects_controller_spec.rb | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/controllers/groups/shared_projects_controller.rb b/app/controllers/groups/shared_projects_controller.rb index f2f835767e0..7dec1f5f402 100644 --- a/app/controllers/groups/shared_projects_controller.rb +++ b/app/controllers/groups/shared_projects_controller.rb @@ -24,7 +24,9 @@ module Groups # Make the `search` param consistent for the frontend, # which will be using `filter`. params[:search] ||= params[:filter] if params[:filter] - params.permit(:sort, :search) + # Don't show archived projects + params[:non_archived] = true + params.permit(:sort, :search, :non_archived) end end end diff --git a/spec/controllers/groups/shared_projects_controller_spec.rb b/spec/controllers/groups/shared_projects_controller_spec.rb index d8fa41abb18..003c8c262e7 100644 --- a/spec/controllers/groups/shared_projects_controller_spec.rb +++ b/spec/controllers/groups/shared_projects_controller_spec.rb @@ -38,7 +38,7 @@ describe Groups::SharedProjectsController do end it 'allows filtering shared projects' do - project = create(:project, :archived, namespace: user.namespace, name: "Searching for") + project = create(:project, namespace: user.namespace, name: "Searching for") share_project(project) get_shared_projects(filter: 'search') @@ -55,5 +55,14 @@ describe Groups::SharedProjectsController do expect(json_project_ids).to eq([second_project.id, shared_project.id]) end + + it 'does not include archived projects' do + archived_project = create(:project, :archived, namespace: user.namespace) + share_project(archived_project) + + get_shared_projects + + expect(json_project_ids).to contain_exactly(shared_project.id) + end end end -- cgit v1.2.1 From fb71c46805e14419897912f6338f635a21d1d097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarka=20Kadlecov=C3=A1?= Date: Mon, 4 Jun 2018 10:38:18 +0200 Subject: Fix repository_storage spec for Rails5 --- spec/models/application_setting_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 968267a6d24..3e6656e0f12 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -110,10 +110,9 @@ describe ApplicationSetting do # Upgraded databases will have this sort of content context 'repository_storages is a String, not an Array' do before do - setting.__send__(:raw_write_attribute, :repository_storages, 'default') + described_class.where(id: setting.id).update_all(repository_storages: 'default') end - it { expect(setting.repository_storages_before_type_cast).to eq('default') } it { expect(setting.repository_storages).to eq(['default']) } end -- cgit v1.2.1 From 4e322d0778ddce02749de8973a4fe0b3b4dfec77 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 31 May 2018 11:18:21 +0100 Subject: Fixed alignment issues with IDE sidebar Fixed sidebar tooltips not hiding after clicking --- app/assets/javascripts/ide/components/activity_bar.vue | 14 +++++++++++--- app/assets/stylesheets/pages/repo.scss | 17 +++++++++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/ide/components/activity_bar.vue b/app/assets/javascripts/ide/components/activity_bar.vue index 05dbc1410de..6efcad6adea 100644 --- a/app/assets/javascripts/ide/components/activity_bar.vue +++ b/app/assets/javascripts/ide/components/activity_bar.vue @@ -1,4 +1,5 @@ @@ -122,4 +208,16 @@ export default { .ide-tree-header .btn { display: flex; } - \ No newline at end of file + +.ide-job-details { + display: flex; +} + +.ide-job-details .ci-status-icon { + height: 0; +} + +.build-trace { + margin-bottom: 0; +} + diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/utils.js b/app/assets/javascripts/ide/stores/modules/pipelines/utils.js index 4f40e8766e0..4ee030731e3 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/utils.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/utils.js @@ -5,4 +5,5 @@ export const normalizeJob = job => ({ status: job.status, path: job.build_path, started: job.started, + output: '', }); diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index 095d2ff9942..611e8200b4d 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -22,8 +22,6 @@ export default class Job { this.$window = $(window); this.logBytes = 0; this.updateDropdown = this.updateDropdown.bind(this); - this.redirectToJob = - this.options.redirectToJob !== undefined ? this.options.redirectToJob : true; this.$buildTrace = $('#build-trace'); this.$buildRefreshAnimation = $('.js-build-refresh'); @@ -46,23 +44,31 @@ export default class Job { .off('click', '.js-sidebar-build-toggle') .on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this)); - this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); + this.$document + .off('click', '.stage-item') + .on('click', '.stage-item', this.updateDropdown); // add event listeners to the scroll buttons - this.$scrollTopBtn.off('click').on('click', this.scrollToTop.bind(this)); + this.$scrollTopBtn + .off('click') + .on('click', this.scrollToTop.bind(this)); - this.$scrollBottomBtn.off('click').on('click', this.scrollToBottom.bind(this)); + this.$scrollBottomBtn + .off('click') + .on('click', this.scrollToBottom.bind(this)); this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100); - this.$window.off('scroll').on('scroll', () => { - if (!this.isScrolledToBottom()) { - this.toggleScrollAnimation(false); - } else if (this.isScrolledToBottom() && !this.isLogComplete) { - this.toggleScrollAnimation(true); - } - this.scrollThrottled(); - }); + this.$window + .off('scroll') + .on('scroll', () => { + if (!this.isScrolledToBottom()) { + this.toggleScrollAnimation(false); + } else if (this.isScrolledToBottom() && !this.isLogComplete) { + this.toggleScrollAnimation(true); + } + this.scrollThrottled(); + }); this.$window .off('resize.build') @@ -73,10 +79,6 @@ export default class Job { this.getBuildTrace(); } - destroy() { - clearTimeout(this.timeout); - } - initAffixTopArea() { /** If the browser does not support position sticky, it returns the position as static. @@ -100,8 +102,9 @@ export default class Job { const windowHeight = $(window).height(); if (this.canScroll()) { - if (currentPosition > 0 && scrollHeight - currentPosition !== windowHeight) { - // User is in the middle of the log + if (currentPosition > 0 && + (scrollHeight - currentPosition !== windowHeight)) { + // User is in the middle of the log this.toggleDisableButton(this.$scrollTopBtn, false); this.toggleDisableButton(this.$scrollBottomBtn, false); @@ -166,11 +169,10 @@ export default class Job { } getBuildTrace() { - return axios - .get(`${this.pagePath}/trace.json`, { - params: { state: this.state }, - }) - .then(res => { + return axios.get(`${this.pagePath}/trace.json`, { + params: { state: this.state }, + }) + .then((res) => { const log = res.data; if (!this.fetchingStatusFavicon) { @@ -220,7 +222,7 @@ export default class Job { this.toggleScrollAnimation(false); } - if (log.status !== this.buildStatus && this.redirectToJob) { + if (log.status !== this.buildStatus) { visitUrl(this.pagePath); } }) -- cgit v1.2.1 From 9827f3db10fc46d902f6670c183c09661ea5da07 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Jun 2018 12:59:56 +0100 Subject: moved fetching trace to store improved components by making them unaware of the store removed need for extra CSS - all achieved with BS classes --- .../javascripts/ide/components/jobs/detail.vue | 122 ++++++--------------- .../javascripts/ide/components/jobs/item.vue | 15 ++- .../javascripts/ide/components/jobs/list.vue | 3 +- .../javascripts/ide/components/jobs/stage.vue | 4 + .../ide/stores/modules/pipelines/actions.js | 22 +++- .../ide/stores/modules/pipelines/mutation_types.js | 4 + .../ide/stores/modules/pipelines/mutations.js | 12 +- .../ide/stores/modules/pipelines/utils.js | 2 + app/assets/stylesheets/pages/builds.scss | 1 + 9 files changed, 87 insertions(+), 98 deletions(-) diff --git a/app/assets/javascripts/ide/components/jobs/detail.vue b/app/assets/javascripts/ide/components/jobs/detail.vue index 330d2a08eec..bacf08c5b5c 100644 --- a/app/assets/javascripts/ide/components/jobs/detail.vue +++ b/app/assets/javascripts/ide/components/jobs/detail.vue @@ -1,14 +1,13 @@ - - diff --git a/app/assets/javascripts/ide/components/jobs/item.vue b/app/assets/javascripts/ide/components/jobs/item.vue index 0baeb4bb04f..597b65fa392 100644 --- a/app/assets/javascripts/ide/components/jobs/item.vue +++ b/app/assets/javascripts/ide/components/jobs/item.vue @@ -18,6 +18,11 @@ export default { return `#${this.job.id}`; }, }, + methods: { + clickViewLog() { + this.$emit('clickViewLog', this.job); + }, + }, }; @@ -43,16 +48,10 @@ export default {
- - diff --git a/app/assets/javascripts/ide/components/jobs/list.vue b/app/assets/javascripts/ide/components/jobs/list.vue index bdd0364c9b9..3b16b860ecd 100644 --- a/app/assets/javascripts/ide/components/jobs/list.vue +++ b/app/assets/javascripts/ide/components/jobs/list.vue @@ -19,7 +19,7 @@ export default { }, }, methods: { - ...mapActions('pipelines', ['fetchJobs', 'toggleStageCollapsed']), + ...mapActions('pipelines', ['fetchJobs', 'toggleStageCollapsed', 'setDetailJob']), }, }; @@ -38,6 +38,7 @@ export default { :stage="stage" @fetch="fetchJobs" @toggleCollapsed="toggleStageCollapsed" + @clickViewLog="setDetailJob" />
diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue index 5b24bb1f5a7..b1428f885fb 100644 --- a/app/assets/javascripts/ide/components/jobs/stage.vue +++ b/app/assets/javascripts/ide/components/jobs/stage.vue @@ -48,6 +48,9 @@ export default { toggleCollapsed() { this.$emit('toggleCollapsed', this.stage.id); }, + clickViewLog(job) { + this.$emit('clickViewLog', job); + }, }, }; @@ -101,6 +104,7 @@ export default { v-for="job in stage.jobs" :key="job.id" :job="job" + @clickViewLog="clickViewLog" /> diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js index b112bc0e1ac..deb84212fc3 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js @@ -4,6 +4,7 @@ import { __ } from '../../../../locale'; import flash from '../../../../flash'; import Poll from '../../../../lib/utils/poll'; import service from '../../../services'; +import { rightSidebarViews } from '../../../constants'; import * as types from './mutation_types'; let eTagPoll; @@ -77,6 +78,25 @@ export const fetchJobs = ({ dispatch }, stage) => { export const toggleStageCollapsed = ({ commit }, stageId) => commit(types.TOGGLE_STAGE_COLLAPSE, stageId); -export const setDetailJob = ({ commit }, job) => commit(types.SET_DETAIL_JOB, job); +export const setDetailJob = ({ commit, dispatch }, job) => { + commit(types.SET_DETAIL_JOB, job); + dispatch('setRightPane', job ? rightSidebarViews.jobsDetail : rightSidebarViews.pipelines, { + root: true, + }); +}; + +export const requestJobTrace = ({ commit }) => commit(types.REQUEST_JOB_TRACE); +export const receiveJobTraceError = ({ commit }) => commit(types.RECEIVE_JOB_TRACE_ERROR); +export const receiveJobTraceSuccess = ({ commit }, data) => + commit(types.RECEIVE_JOB_TRACE_SUCCESS, data); + +export const fetchJobTrace = ({ dispatch, state }) => { + dispatch('requestJobTrace'); + + return axios + .get(`${state.detailJob.path}/trace`, { params: { format: 'json' } }) + .then(({ data }) => dispatch('receiveJobTraceSuccess', data)) + .catch(() => dispatch('requestJobTraceError')); +}; export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js b/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js index d95ce7c7440..f4c36b9d96f 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js @@ -9,3 +9,7 @@ export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS'; export const TOGGLE_STAGE_COLLAPSE = 'TOGGLE_STAGE_COLLAPSE'; export const SET_DETAIL_JOB = 'SET_DETAIL_JOB'; + +export const REQUEST_JOB_TRACE = 'REQUEST_JOB_TRACE'; +export const RECEIVE_JOB_TRACE_ERROR = 'RECEIVE_JOB_TRACE_ERROR'; +export const RECEIVE_JOB_TRACE_SUCCESS = 'RECEIVE_JOB_TRACE_SUCCESS'; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js index bb49af1f45b..5a2213bbe89 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js @@ -64,6 +64,16 @@ export default { })); }, [types.SET_DETAIL_JOB](state, job) { - state.detailJob = job; + state.detailJob = { ...job }; + }, + [types.REQUEST_JOB_TRACE](state) { + state.detailJob.isLoading = true; + }, + [types.RECEIVE_JOB_TRACE_ERROR](state) { + state.detailJob.isLoading = false; + }, + [types.RECEIVE_JOB_TRACE_SUCCESS](state, data) { + state.detailJob.isLoading = false; + state.detailJob.output = data.html; }, }; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/utils.js b/app/assets/javascripts/ide/stores/modules/pipelines/utils.js index 4ee030731e3..a6caca2d2dc 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/utils.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/utils.js @@ -4,6 +4,8 @@ export const normalizeJob = job => ({ name: job.name, status: job.status, path: job.build_path, + rawPath: `${job.build_path}/raw`, started: job.started, output: '', + isLoading: false, }); diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 9ee02ca1d83..9213ccd4cdf 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -75,6 +75,7 @@ .top-bar { height: 35px; + min-height: 35px; background: $gray-light; border: 1px solid $border-color; color: $gl-text-color; -- cgit v1.2.1 From 1d43689e5aa6bcd4f14bdff9e03540d3b3d8ca37 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Jun 2018 14:14:33 +0100 Subject: reduced duplication of job details --- .../javascripts/ide/components/jobs/detail.vue | 75 ++++++---------------- .../ide/components/jobs/detail/description.vue | 47 ++++++++++++++ .../ide/components/jobs/detail/scroll_button.vue | 61 ++++++++++++++++++ .../javascripts/ide/components/jobs/item.vue | 26 ++------ .../ide/stores/modules/pipelines/actions.js | 5 +- 5 files changed, 134 insertions(+), 80 deletions(-) create mode 100644 app/assets/javascripts/ide/components/jobs/detail/description.vue create mode 100644 app/assets/javascripts/ide/components/jobs/detail/scroll_button.vue diff --git a/app/assets/javascripts/ide/components/jobs/detail.vue b/app/assets/javascripts/ide/components/jobs/detail.vue index bacf08c5b5c..72daba5486b 100644 --- a/app/assets/javascripts/ide/components/jobs/detail.vue +++ b/app/assets/javascripts/ide/components/jobs/detail.vue @@ -1,9 +1,10 @@ + + diff --git a/app/assets/javascripts/ide/components/jobs/detail/scroll_button.vue b/app/assets/javascripts/ide/components/jobs/detail/scroll_button.vue new file mode 100644 index 00000000000..3ecb99c581b --- /dev/null +++ b/app/assets/javascripts/ide/components/jobs/detail/scroll_button.vue @@ -0,0 +1,61 @@ + + + diff --git a/app/assets/javascripts/ide/components/jobs/item.vue b/app/assets/javascripts/ide/components/jobs/item.vue index 597b65fa392..224e4d5158b 100644 --- a/app/assets/javascripts/ide/components/jobs/item.vue +++ b/app/assets/javascripts/ide/components/jobs/item.vue @@ -1,11 +1,9 @@ + + + + diff --git a/app/assets/javascripts/ide/components/merge_requests/item.vue b/app/assets/javascripts/ide/components/merge_requests/item.vue new file mode 100644 index 00000000000..11eb5c8b31b --- /dev/null +++ b/app/assets/javascripts/ide/components/merge_requests/item.vue @@ -0,0 +1,59 @@ + + + diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue new file mode 100644 index 00000000000..04b3848f3e2 --- /dev/null +++ b/app/assets/javascripts/ide/components/merge_requests/list.vue @@ -0,0 +1,91 @@ + + + diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js index d3050183bd3..2b32c0c0b55 100644 --- a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js +++ b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js @@ -22,4 +22,6 @@ export const fetchMergeRequests = ({ dispatch, state: { scope, state } }, search export const resetMergeRequests = ({ commit }) => commit(types.RESET_MERGE_REQUESTS); +export const setScope = ({ commit }, scope) => commit(types.SET_SCOPE, scope); + export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/mutation_types.js b/app/assets/javascripts/ide/stores/modules/merge_requests/mutation_types.js index 0badddcbae7..6d163491209 100644 --- a/app/assets/javascripts/ide/stores/modules/merge_requests/mutation_types.js +++ b/app/assets/javascripts/ide/stores/modules/merge_requests/mutation_types.js @@ -3,3 +3,5 @@ export const RECEIVE_MERGE_REQUESTS_ERROR = 'RECEIVE_MERGE_REQUESTS_ERROR'; export const RECEIVE_MERGE_REQUESTS_SUCCESS = 'RECEIVE_MERGE_REQUESTS_SUCCESS'; export const RESET_MERGE_REQUESTS = 'RESET_MERGE_REQUESTS'; + +export const SET_SCOPE = 'SET_SCOPE'; diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js b/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js index 98102a68e08..5c0c502b0d1 100644 --- a/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js +++ b/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js @@ -23,4 +23,7 @@ export default { [types.RESET_MERGE_REQUESTS](state) { state.mergeRequests = []; }, + [types.SET_SCOPE](state, scope) { + state.scope = scope; + }, }; diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/state.js b/app/assets/javascripts/ide/stores/modules/merge_requests/state.js index 2947b686c1c..64ca24b1af6 100644 --- a/app/assets/javascripts/ide/stores/modules/merge_requests/state.js +++ b/app/assets/javascripts/ide/stores/modules/merge_requests/state.js @@ -3,6 +3,6 @@ import { scopes, states } from './constants'; export default () => ({ isLoading: false, mergeRequests: [], - scope: scopes.assignedToMe, + scope: scopes.createdByMe, state: states.opened, }); diff --git a/app/assets/javascripts/vue_shared/components/tabs/tabs.js b/app/assets/javascripts/vue_shared/components/tabs/tabs.js index 4362264caa5..8497c3e851f 100644 --- a/app/assets/javascripts/vue_shared/components/tabs/tabs.js +++ b/app/assets/javascripts/vue_shared/components/tabs/tabs.js @@ -1,4 +1,11 @@ export default { + props: { + stopPropagation: { + type: Boolean, + required: false, + default: false, + }, + }, data() { return { currentIndex: 0, @@ -13,11 +20,17 @@ export default { this.tabs = this.$children.filter(child => child.isTab); this.currentIndex = this.tabs.findIndex(tab => tab.localActive); }, - setTab(index) { + setTab(e, index) { + if (this.stopPropagation) { + e.stopPropagation(); + } + this.tabs[this.currentIndex].localActive = false; this.tabs[index].localActive = true; this.currentIndex = index; + + this.$emit('changed', this.currentIndex); }, }, render(h) { @@ -36,7 +49,7 @@ export default { href: '#', }, on: { - click: () => this.setTab(i), + click: e => this.setTab(e, i), }, }, tab.$slots.title || tab.title, -- cgit v1.2.1 From 0b278a93f96ca4dd760c069e42d5485460d7b738 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Jun 2018 17:30:50 +0100 Subject: style updates [ci skip] --- .../ide/components/merge_requests/dropdown.vue | 6 ++- .../ide/components/merge_requests/list.vue | 52 ++++++++++++++++++---- app/assets/stylesheets/pages/repo.scss | 14 ++++++ 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/ide/components/merge_requests/dropdown.vue b/app/assets/javascripts/ide/components/merge_requests/dropdown.vue index 106f66b764a..1bcd550c713 100644 --- a/app/assets/javascripts/ide/components/merge_requests/dropdown.vue +++ b/app/assets/javascripts/ide/components/merge_requests/dropdown.vue @@ -39,7 +39,7 @@ export default { diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue index 631c995b59e..fdf191b3352 100644 --- a/app/assets/javascripts/ide/components/ide_side_bar.vue +++ b/app/assets/javascripts/ide/components/ide_side_bar.vue @@ -65,6 +65,8 @@ export default { watch: { currentBranchId() { this.$nextTick(() => { + if (!this.$refs.branchId) return; + this.showTooltip = this.$refs.branchId.scrollWidth > this.$refs.branchId.offsetWidth; }); }, @@ -145,6 +147,7 @@ export default {
{{ item[key] }}